@mnci/nx-python-pip 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +120 -0
  2. package/dist/executors/build/executor.d.ts +25 -0
  3. package/dist/executors/build/executor.d.ts.map +1 -0
  4. package/dist/executors/build/executor.js +81 -0
  5. package/dist/executors/build/executor.js.map +1 -0
  6. package/dist/executors/build/schema.json +10 -0
  7. package/dist/executors/lint/executor.d.ts +20 -0
  8. package/dist/executors/lint/executor.d.ts.map +1 -0
  9. package/dist/executors/lint/executor.js +26 -0
  10. package/dist/executors/lint/executor.js.map +1 -0
  11. package/dist/executors/lint/schema.json +9 -0
  12. package/dist/executors/publish/executor.d.ts +30 -0
  13. package/dist/executors/publish/executor.d.ts.map +1 -0
  14. package/dist/executors/publish/executor.js +42 -0
  15. package/dist/executors/publish/executor.js.map +1 -0
  16. package/dist/executors/publish/schema.json +16 -0
  17. package/dist/executors/test/executor.d.ts +26 -0
  18. package/dist/executors/test/executor.d.ts.map +1 -0
  19. package/dist/executors/test/executor.js +38 -0
  20. package/dist/executors/test/executor.js.map +1 -0
  21. package/dist/executors/test/schema.json +15 -0
  22. package/dist/generators/application/generator.d.ts +20 -0
  23. package/dist/generators/application/generator.d.ts.map +1 -0
  24. package/dist/generators/application/generator.js +29 -0
  25. package/dist/generators/application/generator.js.map +1 -0
  26. package/dist/generators/application/schema.json +22 -0
  27. package/dist/generators/functionApplication/generator.d.ts +21 -0
  28. package/dist/generators/functionApplication/generator.d.ts.map +1 -0
  29. package/dist/generators/functionApplication/generator.js +43 -0
  30. package/dist/generators/functionApplication/generator.js.map +1 -0
  31. package/dist/generators/functionApplication/schema.json +22 -0
  32. package/dist/generators/internalLibrary/generator.d.ts +20 -0
  33. package/dist/generators/internalLibrary/generator.d.ts.map +1 -0
  34. package/dist/generators/internalLibrary/generator.js +39 -0
  35. package/dist/generators/internalLibrary/generator.js.map +1 -0
  36. package/dist/generators/internalLibrary/schema.json +22 -0
  37. package/dist/generators/library/generator.d.ts +23 -0
  38. package/dist/generators/library/generator.d.ts.map +1 -0
  39. package/dist/generators/library/generator.js +33 -0
  40. package/dist/generators/library/generator.js.map +1 -0
  41. package/dist/generators/library/schema.json +22 -0
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +3 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/internal/azureFunctionApp.d.ts +53 -0
  47. package/dist/internal/azureFunctionApp.d.ts.map +1 -0
  48. package/dist/internal/azureFunctionApp.js +89 -0
  49. package/dist/internal/azureFunctionApp.js.map +1 -0
  50. package/dist/internal/executorContext.d.ts +17 -0
  51. package/dist/internal/executorContext.d.ts.map +1 -0
  52. package/dist/internal/executorContext.js +26 -0
  53. package/dist/internal/executorContext.js.map +1 -0
  54. package/dist/internal/generateProject.d.ts +43 -0
  55. package/dist/internal/generateProject.d.ts.map +1 -0
  56. package/dist/internal/generateProject.js +58 -0
  57. package/dist/internal/generateProject.js.map +1 -0
  58. package/dist/internal/pythonProject.d.ts +57 -0
  59. package/dist/internal/pythonProject.d.ts.map +1 -0
  60. package/dist/internal/pythonProject.js +94 -0
  61. package/dist/internal/pythonProject.js.map +1 -0
  62. package/dist/internal/vendor.d.ts +42 -0
  63. package/dist/internal/vendor.d.ts.map +1 -0
  64. package/dist/internal/vendor.js +64 -0
  65. package/dist/internal/vendor.js.map +1 -0
  66. package/dist/release/versionActions.d.ts +107 -0
  67. package/dist/release/versionActions.d.ts.map +1 -0
  68. package/dist/release/versionActions.js +136 -0
  69. package/dist/release/versionActions.js.map +1 -0
  70. package/executors.json +25 -0
  71. package/generators.json +25 -0
  72. package/package.json +63 -0
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # @mnci/nx-python-pip
2
+
3
+ An Nx plugin for **pip-native** Python projects — plain `pip`, Ruff, pytest
4
+ and the standard PyPA `build`/`twine` tools. No uv, no Poetry, no lock file.
5
+
6
+ ## Why
7
+
8
+ No maintained, Nx-23-compatible Python plugin supports pip: the closest
9
+ existing option, [`@nxlv/python`](https://github.com/lucasvieirasilva/nx-plugins),
10
+ ships only `uv` and Poetry providers, and every alternative found on npm is
11
+ either the same uv/Poetry architecture or years stale. If your organization
12
+ standardizes on plain pip (no uv, no Poetry), this plugin fills that gap.
13
+
14
+ It was built for and is used by [`@mnci/cli`](../cli)'s `mnci2 add python-*`
15
+ commands, but has no dependency on `@mnci/cli` — any Nx 21+ workspace can
16
+ install and use it directly.
17
+
18
+ ## Install
19
+
20
+ ```sh
21
+ npm install --save-dev @mnci/nx-python-pip
22
+ ```
23
+
24
+ No `nx.json` `plugins` registration needed — its generators and executors
25
+ are explicit (resolved via `generators.json`/`executors.json`, plain Node
26
+ module lookup), not inference-based.
27
+
28
+ You will also need the actual Python tools this plugin's executors shell out
29
+ to: `python3 -m pip install build twine ruff pytest` (or pin them in your own
30
+ `requirements-dev.txt` / `requirements-dev.in`). The plugin has no opinion on
31
+ *how* those land on a machine — same way `@nx/js`'s executors assume `node`
32
+ is already there.
33
+
34
+ ## Generators
35
+
36
+ | Generator | Location default | Writes |
37
+ | --- | --- | --- |
38
+ | `application` | `apps/<name>` | `pyproject.toml` (hatchling) + `project.json` (`lint`/`test`/`build`) + a sample module and pytest |
39
+ | `library` | `libs/<name>` | Same as `application`, plus `nx-release-publish` (twine) and a project-level `release.version.versionActions` override |
40
+ | `internal-library` | `libs/<name>` | `lint`/`test` only — no `build`/publish; meant to be **vendored** into a consumer's wheel, not built or released on its own |
41
+ | `function-application` | `apps/<name>` | Azure Functions **v2** programming model (`function_app.py` + `host.json` + `requirements.txt` + a tested pure helper) — no `pyproject.toml`/build target, since the deployable is source, not a wheel |
42
+
43
+ ```sh
44
+ nx g @mnci/nx-python-pip:application my-app
45
+ nx g @mnci/nx-python-pip:library my-lib --directory=packages/my-lib
46
+ nx g @mnci/nx-python-pip:internal-library my-shared-lib
47
+ nx g @mnci/nx-python-pip:function-application my-function-app
48
+ ```
49
+
50
+ ## Executors
51
+
52
+ | Executor | Runs |
53
+ | --- | --- |
54
+ | `build` | `python -m build` — vendoring-aware (see below) |
55
+ | `test` | `python -m pip install -e .` (unless `installEditable: false`) then `python -m pytest` |
56
+ | `lint` | `python -m ruff check .` |
57
+ | `publish` | `python -m twine upload --skip-existing dist/*`, reading `TWINE_USERNAME`/`TWINE_PASSWORD`/`TWINE_REPOSITORY_URL` from the environment |
58
+
59
+ Every command is invoked as `python3 -m <tool>`, never a hard-coded venv
60
+ path, so the exact same command works whether or not you've activated a
61
+ virtualenv — this plugin never creates or manages one itself.
62
+
63
+ `publish` accepts a real, typed `dryRun` option — `nx release publish
64
+ --dry-run` sets it automatically on every `nx-release-publish` executor, so
65
+ a dry run cleanly previews the twine command instead of running it.
66
+
67
+ ## Internal-lib vendoring
68
+
69
+ Plain pip has no bundled-local-dependency feature (the equivalent of
70
+ `@nxlv/python`'s `bundleLocalDependencies`). To have a project's built wheel
71
+ bundle an internal library's module as a real top-level package, hand-add a
72
+ `vendor` entry to the consuming project's `pyproject.toml`:
73
+
74
+ ```toml
75
+ [tool.mnci-python-pip]
76
+ vendor = ["my-shared-lib"]
77
+ ```
78
+
79
+ The `build` executor resolves `my-shared-lib`'s root via the **real Nx
80
+ project graph** (not a hard-coded path), copies its module directory into a
81
+ staged copy of the project being built, patches the staged
82
+ `pyproject.toml`'s `[tool.hatch.build.targets.wheel] packages` list to
83
+ include it, and builds from there. No cross-project dependency is ever wired
84
+ automatically — you always add this entry by hand, mirroring how you'd wire
85
+ any other cross-project dependency.
86
+
87
+ Verified empirically that vendoring an internal lib and declaring a real
88
+ external PyPI dependency on the *same* project works correctly together —
89
+ the combination that silently dropped the external dependency's metadata
90
+ under `@nxlv/python`'s `bundleLocalDependencies` does not reproduce here.
91
+
92
+ ## Versioning (`nx release`)
93
+
94
+ The `library` generator sets:
95
+
96
+ ```json
97
+ "release": { "version": { "versionActions": "@mnci/nx-python-pip/release/version-actions" } }
98
+ ```
99
+
100
+ on the generated project. This is a hand-written implementation of Nx's
101
+ `VersionActions` interface that reads/writes the `version = "..."` line
102
+ under `pyproject.toml`'s `[project]` table — verified empirically against a
103
+ real `nx release version --dry-run`, both the disk-fallback and
104
+ git-tag-based resolution paths. Internal-lib dependencies are vendored, not
105
+ registry references, so dependency-version tracking is a no-op (the same
106
+ branch `@nxlv/python`'s own reference implementation takes for bundled
107
+ dependencies).
108
+
109
+ ## Known gaps
110
+
111
+ - No lock file — plain pip has none. A published wheel's `Requires-Dist`
112
+ mirrors whatever specifier `pyproject.toml` declares (e.g. `tomli>=2.0.0`)
113
+ verbatim, not a resolved/pinned version.
114
+ - Vendored internal-lib imports are only resolvable inside the **built**
115
+ wheel — not from a plain `pip install -e .` dev environment, since
116
+ vendoring happens at `build` time only. A project whose pytest-covered
117
+ code imports a vendored internal lib needs its own test-isolation
118
+ strategy; the `test` executor makes no attempt to solve this.
119
+ - venv management is left to the user — same spirit as never managing
120
+ `node_modules` beyond `npm install`.
@@ -0,0 +1,25 @@
1
+ import type { ExecutorContext } from '@nx/devkit';
2
+ import type { BuildExecutorSchema } from './schema.d';
3
+ /**
4
+ * Builds a Python project's wheel/sdist via `python -m build`.
5
+ *
6
+ * @remarks
7
+ * When the project's `pyproject.toml` declares a `vendor` entry (see
8
+ * `parseVendorEntries`), each named project's module directory is copied
9
+ * into a staged copy of the project before building, and the staged
10
+ * `pyproject.toml`'s wheel `packages` list is patched to include it — so the
11
+ * built wheel contains the vendored module as a real top-level package, with
12
+ * no separate install needed by whoever installs the published wheel.
13
+ * Projects with no `vendor` entry build straight from their own directory,
14
+ * no staging.
15
+ *
16
+ * @param _options - Unused (vendoring is driven by `pyproject.toml`, not options).
17
+ * @param context - The Nx executor context.
18
+ * @returns `{ success: true }` when `python -m build` exits 0.
19
+ * @throws Never - failures surface through the returned `success: false`.
20
+ * @typeParam None - this function has no generic type parameters.
21
+ */
22
+ export default function buildExecutor(_options: BuildExecutorSchema, context: ExecutorContext): Promise<{
23
+ success: boolean;
24
+ }>;
25
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/executors/build/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAQjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAoBrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAA8B,aAAa,CAAE,QAAQ,EAAE,mBAAmB,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAqCnI"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = buildExecutor;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_os_1 = require("node:os");
7
+ const node_path_1 = require("node:path");
8
+ const executorContext_1 = require("../../internal/executorContext");
9
+ const pythonProject_1 = require("../../internal/pythonProject");
10
+ const vendor_1 = require("../../internal/vendor");
11
+ /**
12
+ * Resolves a vendored project's workspace-relative root via the Nx project
13
+ * graph (not a hard-coded `libs/<name>` path) — the same lookup
14
+ * {@link projectRootFrom} uses for the project under build itself.
15
+ *
16
+ * @param name - The vendored project's Nx project name.
17
+ * @param context - The Nx executor context.
18
+ * @returns The workspace-relative root, or `undefined` when no project with
19
+ * that name is registered.
20
+ * @throws Never - an unresolvable name yields `undefined`, not a throw; the
21
+ * caller surfaces it as a normal `python -m build` failure (a missing
22
+ * source directory), not a confusing internal error.
23
+ * @typeParam None - this function has no generic type parameters.
24
+ */
25
+ function vendoredProjectRoot(name, context) {
26
+ return context.projectsConfigurations?.projects[name]?.root;
27
+ }
28
+ /**
29
+ * Builds a Python project's wheel/sdist via `python -m build`.
30
+ *
31
+ * @remarks
32
+ * When the project's `pyproject.toml` declares a `vendor` entry (see
33
+ * `parseVendorEntries`), each named project's module directory is copied
34
+ * into a staged copy of the project before building, and the staged
35
+ * `pyproject.toml`'s wheel `packages` list is patched to include it — so the
36
+ * built wheel contains the vendored module as a real top-level package, with
37
+ * no separate install needed by whoever installs the published wheel.
38
+ * Projects with no `vendor` entry build straight from their own directory,
39
+ * no staging.
40
+ *
41
+ * @param _options - Unused (vendoring is driven by `pyproject.toml`, not options).
42
+ * @param context - The Nx executor context.
43
+ * @returns `{ success: true }` when `python -m build` exits 0.
44
+ * @throws Never - failures surface through the returned `success: false`.
45
+ * @typeParam None - this function has no generic type parameters.
46
+ */
47
+ async function buildExecutor(_options, context) {
48
+ const projectRoot = (0, executorContext_1.projectRootFrom)(context);
49
+ const absoluteProjectRoot = (0, node_path_1.join)(context.root, projectRoot);
50
+ const outDirectory = (0, node_path_1.join)(absoluteProjectRoot, 'dist');
51
+ const pyprojectPath = (0, node_path_1.join)(absoluteProjectRoot, 'pyproject.toml');
52
+ const pyprojectToml = (0, node_fs_1.readFileSync)(pyprojectPath, 'utf8');
53
+ const vendorNames = (0, vendor_1.parseVendorEntries)(pyprojectToml);
54
+ if (vendorNames.length === 0) {
55
+ const result = (0, node_child_process_1.spawnSync)('python3', ['-m', 'build', '--outdir', outDirectory, absoluteProjectRoot], { stdio: 'inherit' });
56
+ return { success: result.status === 0 };
57
+ }
58
+ const stagingRoot = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'nx-python-pip-build-'));
59
+ try {
60
+ (0, node_fs_1.cpSync)(absoluteProjectRoot, stagingRoot, { recursive: true });
61
+ const moduleDirectories = [];
62
+ for (const name of vendorNames) {
63
+ const vendoredRoot = vendoredProjectRoot(name, context);
64
+ if (!vendoredRoot) {
65
+ console.error(`nx-python-pip build: vendored project "${name}" is not registered in this workspace.`);
66
+ return { success: false };
67
+ }
68
+ const moduleDirectory = (0, pythonProject_1.pythonModuleDirectory)(name);
69
+ (0, node_fs_1.cpSync)((0, node_path_1.join)(context.root, vendoredRoot, moduleDirectory), (0, node_path_1.join)(stagingRoot, moduleDirectory), { recursive: true });
70
+ moduleDirectories.push(moduleDirectory);
71
+ }
72
+ const stagedPyprojectPath = (0, node_path_1.join)(stagingRoot, 'pyproject.toml');
73
+ (0, node_fs_1.writeFileSync)(stagedPyprojectPath, (0, vendor_1.addPackagesToWheelTarget)((0, node_fs_1.readFileSync)(stagedPyprojectPath, 'utf8'), moduleDirectories));
74
+ const result = (0, node_child_process_1.spawnSync)('python3', ['-m', 'build', '--outdir', outDirectory, stagingRoot], { stdio: 'inherit' });
75
+ return { success: result.status === 0 };
76
+ }
77
+ finally {
78
+ (0, node_fs_1.rmSync)(stagingRoot, { recursive: true, force: true });
79
+ }
80
+ }
81
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/executors/build/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "MnciNxPythonPipBuild",
5
+ "title": "Build a Python wheel/sdist with python -m build",
6
+ "description": "Runs `python -m build`. When the project's pyproject.toml declares [tool.mnci-python-pip] vendor = [...], the named libs/<name> modules are copied into a staged copy of the project first, so the built wheel bundles them as real top-level packages.",
7
+ "type": "object",
8
+ "properties": {},
9
+ "required": []
10
+ }
@@ -0,0 +1,20 @@
1
+ import type { ExecutorContext } from '@nx/devkit';
2
+ import type { LintExecutorSchema } from './schema.d';
3
+ /**
4
+ * Runs `python3 -m ruff check .` in the project's own directory.
5
+ *
6
+ * @remarks
7
+ * `python3 -m ruff` (not a bare `ruff`) so it resolves whatever `python3`
8
+ * the caller has active — no hard-coded venv path, matching every other
9
+ * executor in this package.
10
+ *
11
+ * @param _options - Unused (ruff needs no per-project configuration here).
12
+ * @param context - The Nx executor context.
13
+ * @returns `{ success: true }` when ruff exits 0.
14
+ * @throws Never - failures surface through the returned `success: false`.
15
+ * @typeParam None - this function has no generic type parameters.
16
+ */
17
+ export default function lintExecutor(_options: LintExecutorSchema, context: ExecutorContext): Promise<{
18
+ success: boolean;
19
+ }>;
20
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/executors/lint/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;;;;;;;;;;GAaG;AACH,wBAA8B,YAAY,CAAE,QAAQ,EAAE,kBAAkB,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAIjI"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = lintExecutor;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_path_1 = require("node:path");
6
+ const executorContext_1 = require("../../internal/executorContext");
7
+ /**
8
+ * Runs `python3 -m ruff check .` in the project's own directory.
9
+ *
10
+ * @remarks
11
+ * `python3 -m ruff` (not a bare `ruff`) so it resolves whatever `python3`
12
+ * the caller has active — no hard-coded venv path, matching every other
13
+ * executor in this package.
14
+ *
15
+ * @param _options - Unused (ruff needs no per-project configuration here).
16
+ * @param context - The Nx executor context.
17
+ * @returns `{ success: true }` when ruff exits 0.
18
+ * @throws Never - failures surface through the returned `success: false`.
19
+ * @typeParam None - this function has no generic type parameters.
20
+ */
21
+ async function lintExecutor(_options, context) {
22
+ const cwd = (0, node_path_1.join)(context.root, (0, executorContext_1.projectRootFrom)(context));
23
+ const result = (0, node_child_process_1.spawnSync)('python3', ['-m', 'ruff', 'check', '.'], { cwd, stdio: 'inherit' });
24
+ return { success: result.status === 0 };
25
+ }
26
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/executors/lint/executor.ts"],"names":[],"mappings":";;;AACA,2DAA8C;AAC9C,yCAAgC;AAChC,oEAAgE;AAGhE;;;;;;;;;;;;;GAaG;AACY,KAAK,uBAAwB,QAA4B,EAAE,OAAwB;IAChG,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,IAAI,EAAE,IAAA,iCAAe,EAAC,OAAO,CAAC,CAAC,CAAA;IACxD,MAAM,MAAM,GAAG,IAAA,8BAAS,EAAC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IAC5F,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAA;AACzC,CAAC"}
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "MnciNxPythonPipLint",
5
+ "title": "Lint a Python project with ruff",
6
+ "type": "object",
7
+ "properties": {},
8
+ "required": []
9
+ }
@@ -0,0 +1,30 @@
1
+ import type { ExecutorContext } from '@nx/devkit';
2
+ import type { PublishExecutorSchema } from './schema.d';
3
+ /**
4
+ * Runs `python -m twine upload --skip-existing dist/*`.
5
+ *
6
+ * @remarks
7
+ * `--skip-existing` mirrors npm's own idempotent-publish behaviour (Nx's
8
+ * `@nx/js:release-publish` also tolerates a version already on the
9
+ * registry), so a re-run after a partial release failure does not
10
+ * hard-error on the packages that already made it. Credentials
11
+ * (`TWINE_USERNAME`/`TWINE_PASSWORD`/`TWINE_REPOSITORY_URL`) are read from
12
+ * the environment by twine itself — never written to disk here.
13
+ *
14
+ * `dryRun` is a real, typed executor option (unlike a plain
15
+ * `nx:run-commands` target, which would only see the `--dry-run` flag as an
16
+ * opaque, appended `--dryRun=true` string on the shell command line).
17
+ * `nx release publish` sets `dryRun` automatically for every
18
+ * `nx-release-publish` executor, custom or not, so no argv-parsing trick is
19
+ * needed to preview instead of uploading for real.
20
+ *
21
+ * @param options - Whether to preview instead of actually uploading.
22
+ * @param context - The Nx executor context.
23
+ * @returns `{ success: true }` on a successful (real or previewed) publish.
24
+ * @throws Never - failures surface through the returned `success: false`.
25
+ * @typeParam None - this function has no generic type parameters.
26
+ */
27
+ export default function publishExecutor(options: PublishExecutorSchema, context: ExecutorContext): Promise<{
28
+ success: boolean;
29
+ }>;
30
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/executors/publish/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAA8B,eAAe,CAAE,OAAO,EAAE,qBAAqB,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAWtI"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = publishExecutor;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_path_1 = require("node:path");
6
+ const executorContext_1 = require("../../internal/executorContext");
7
+ /**
8
+ * Runs `python -m twine upload --skip-existing dist/*`.
9
+ *
10
+ * @remarks
11
+ * `--skip-existing` mirrors npm's own idempotent-publish behaviour (Nx's
12
+ * `@nx/js:release-publish` also tolerates a version already on the
13
+ * registry), so a re-run after a partial release failure does not
14
+ * hard-error on the packages that already made it. Credentials
15
+ * (`TWINE_USERNAME`/`TWINE_PASSWORD`/`TWINE_REPOSITORY_URL`) are read from
16
+ * the environment by twine itself — never written to disk here.
17
+ *
18
+ * `dryRun` is a real, typed executor option (unlike a plain
19
+ * `nx:run-commands` target, which would only see the `--dry-run` flag as an
20
+ * opaque, appended `--dryRun=true` string on the shell command line).
21
+ * `nx release publish` sets `dryRun` automatically for every
22
+ * `nx-release-publish` executor, custom or not, so no argv-parsing trick is
23
+ * needed to preview instead of uploading for real.
24
+ *
25
+ * @param options - Whether to preview instead of actually uploading.
26
+ * @param context - The Nx executor context.
27
+ * @returns `{ success: true }` on a successful (real or previewed) publish.
28
+ * @throws Never - failures surface through the returned `success: false`.
29
+ * @typeParam None - this function has no generic type parameters.
30
+ */
31
+ async function publishExecutor(options, context) {
32
+ if (options.dryRun) {
33
+ console.log('[dry-run] would run: python3 -m twine upload --skip-existing dist/*');
34
+ return { success: true };
35
+ }
36
+ const cwd = (0, node_path_1.join)(context.root, (0, executorContext_1.projectRootFrom)(context));
37
+ // No shell: true needed for the dist/* glob — twine globs its own path
38
+ // arguments internally, so this stays free of a shell-injection surface.
39
+ const result = (0, node_child_process_1.spawnSync)('python3', ['-m', 'twine', 'upload', '--skip-existing', 'dist/*'], { cwd, stdio: 'inherit' });
40
+ return { success: result.status === 0 };
41
+ }
42
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/executors/publish/executor.ts"],"names":[],"mappings":";;;AACA,2DAA8C;AAC9C,yCAAgC;AAChC,oEAAgE;AAGhE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACY,KAAK,0BAA2B,OAA8B,EAAE,OAAwB;IACrG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAA;QAClF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC1B,CAAC;IAED,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,IAAI,EAAE,IAAA,iCAAe,EAAC,OAAO,CAAC,CAAC,CAAA;IACxD,uEAAuE;IACvE,yEAAyE;IACzE,MAAM,MAAM,GAAG,IAAA,8BAAS,EAAC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IACtH,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAA;AACzC,CAAC"}
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "MnciNxPythonPipPublish",
5
+ "title": "Publish a Python wheel/sdist with twine",
6
+ "description": "Runs `python -m twine upload --skip-existing dist/*`. Credentials (TWINE_USERNAME/TWINE_PASSWORD/TWINE_REPOSITORY_URL) are read from the environment by twine itself. Detects the --dryRun flag `nx release publish --dry-run` appends and previews instead of uploading for real.",
7
+ "type": "object",
8
+ "properties": {
9
+ "dryRun": {
10
+ "type": "boolean",
11
+ "description": "Preview instead of actually uploading. Set automatically by `nx release publish --dry-run`.",
12
+ "default": false
13
+ }
14
+ },
15
+ "required": []
16
+ }
@@ -0,0 +1,26 @@
1
+ import type { ExecutorContext } from '@nx/devkit';
2
+ import type { TestExecutorSchema } from './schema.d';
3
+ /**
4
+ * Runs pytest for a Python project, optionally installing it in editable
5
+ * mode first so its own declared dependencies are importable.
6
+ *
7
+ * @remarks
8
+ * `pip install -e .` (when `installEditable` is not `false`) makes the
9
+ * project's `pyproject.toml` `dependencies` (real external PyPI packages)
10
+ * importable at test time. It does **not** make vendored internal-lib
11
+ * imports importable — those are woven in only at `build` time (see the
12
+ * `build` executor's remarks) — so a project whose pytest-covered code
13
+ * imports a vendored internal lib needs its own test isolation strategy;
14
+ * this executor makes no attempt to solve that.
15
+ *
16
+ * @param options - Whether to `pip install -e .` before pytest.
17
+ * @param context - The Nx executor context.
18
+ * @returns `{ success: true }` when pytest exits 0 (and, when requested, the
19
+ * editable install also succeeded).
20
+ * @throws Never - failures surface through the returned `success: false`.
21
+ * @typeParam None - this function has no generic type parameters.
22
+ */
23
+ export default function testExecutor(options: TestExecutorSchema, context: ExecutorContext): Promise<{
24
+ success: boolean;
25
+ }>;
26
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/executors/test/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAA8B,YAAY,CAAE,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAYhI"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = testExecutor;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_path_1 = require("node:path");
6
+ const executorContext_1 = require("../../internal/executorContext");
7
+ /**
8
+ * Runs pytest for a Python project, optionally installing it in editable
9
+ * mode first so its own declared dependencies are importable.
10
+ *
11
+ * @remarks
12
+ * `pip install -e .` (when `installEditable` is not `false`) makes the
13
+ * project's `pyproject.toml` `dependencies` (real external PyPI packages)
14
+ * importable at test time. It does **not** make vendored internal-lib
15
+ * imports importable — those are woven in only at `build` time (see the
16
+ * `build` executor's remarks) — so a project whose pytest-covered code
17
+ * imports a vendored internal lib needs its own test isolation strategy;
18
+ * this executor makes no attempt to solve that.
19
+ *
20
+ * @param options - Whether to `pip install -e .` before pytest.
21
+ * @param context - The Nx executor context.
22
+ * @returns `{ success: true }` when pytest exits 0 (and, when requested, the
23
+ * editable install also succeeded).
24
+ * @throws Never - failures surface through the returned `success: false`.
25
+ * @typeParam None - this function has no generic type parameters.
26
+ */
27
+ async function testExecutor(options, context) {
28
+ const cwd = (0, node_path_1.join)(context.root, (0, executorContext_1.projectRootFrom)(context));
29
+ if (options.installEditable !== false) {
30
+ const install = (0, node_child_process_1.spawnSync)('python3', ['-m', 'pip', 'install', '--quiet', '-e', '.'], { cwd, stdio: 'inherit' });
31
+ if (install.status !== 0) {
32
+ return { success: false };
33
+ }
34
+ }
35
+ const result = (0, node_child_process_1.spawnSync)('python3', ['-m', 'pytest'], { cwd, stdio: 'inherit' });
36
+ return { success: result.status === 0 };
37
+ }
38
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/executors/test/executor.ts"],"names":[],"mappings":";;;AACA,2DAA8C;AAC9C,yCAAgC;AAChC,oEAAgE;AAGhE;;;;;;;;;;;;;;;;;;;GAmBG;AACY,KAAK,uBAAwB,OAA2B,EAAE,OAAwB;IAC/F,MAAM,GAAG,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,IAAI,EAAE,IAAA,iCAAe,EAAC,OAAO,CAAC,CAAC,CAAA;IAExD,IAAI,OAAO,CAAC,eAAe,KAAK,KAAK,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,IAAA,8BAAS,EAAC,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;QAC/G,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,8BAAS,EAAC,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IAChF,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAA;AACzC,CAAC"}
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "MnciNxPythonPipTest",
5
+ "title": "Test a Python project with pytest",
6
+ "type": "object",
7
+ "properties": {
8
+ "installEditable": {
9
+ "type": "boolean",
10
+ "description": "Run `pip install -e .` before pytest, so the project's own declared dependencies are importable. Set to false for projects with no pyproject.toml (e.g. an Azure Function app, which deploys as source, not a wheel).",
11
+ "default": true
12
+ }
13
+ },
14
+ "required": []
15
+ }
@@ -0,0 +1,20 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ import type { AppGeneratorSchema } from './schema.d';
3
+ /**
4
+ * Generates a pip-native Python application: `pyproject.toml` + `project.json`
5
+ * (lint/test/build) + a sample module and pytest.
6
+ *
7
+ * @remarks
8
+ * No `nx-release-publish` target and no `release.version.versionActions`
9
+ * override — apps are never released by `nx release` (packed into a deploy
10
+ * artifact instead, by whatever `package`-style target the calling workspace
11
+ * layers on top).
12
+ *
13
+ * @param tree - The Nx virtual file system.
14
+ * @param options - The project name and directory.
15
+ * @returns A promise that resolves once generated files are formatted.
16
+ * @throws Never - pure Tree writes.
17
+ * @typeParam None - this function has no generic type parameters.
18
+ */
19
+ export default function appGenerator(tree: Tree, options: AppGeneratorSchema): Promise<void>;
20
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/generators/application/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,wBAA8B,YAAY,CAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlG"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = appGenerator;
4
+ const generateProject_1 = require("../../internal/generateProject");
5
+ /**
6
+ * Generates a pip-native Python application: `pyproject.toml` + `project.json`
7
+ * (lint/test/build) + a sample module and pytest.
8
+ *
9
+ * @remarks
10
+ * No `nx-release-publish` target and no `release.version.versionActions`
11
+ * override — apps are never released by `nx release` (packed into a deploy
12
+ * artifact instead, by whatever `package`-style target the calling workspace
13
+ * layers on top).
14
+ *
15
+ * @param tree - The Nx virtual file system.
16
+ * @param options - The project name and directory.
17
+ * @returns A promise that resolves once generated files are formatted.
18
+ * @throws Never - pure Tree writes.
19
+ * @typeParam None - this function has no generic type parameters.
20
+ */
21
+ async function appGenerator(tree, options) {
22
+ (0, generateProject_1.generateBuildableProject)(tree, {
23
+ name: options.name,
24
+ directory: options.directory ?? `apps/${options.name}`,
25
+ projectType: 'application',
26
+ });
27
+ await (0, generateProject_1.formatFiles)(tree);
28
+ }
29
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../src/generators/application/generator.ts"],"names":[],"mappings":";;;AACA,oEAAsF;AAGtF;;;;;;;;;;;;;;;GAeG;AACY,KAAK,uBAAwB,IAAU,EAAE,OAA2B;IACjF,IAAA,0CAAwB,EAAC,IAAI,EAAE;QAC7B,IAAI,EAAS,OAAO,CAAC,IAAI;QACzB,SAAS,EAAI,OAAO,CAAC,SAAS,IAAI,QAAQ,OAAO,CAAC,IAAI,EAAE;QACxD,WAAW,EAAE,aAAa;KAC3B,CAAC,CAAA;IACF,MAAM,IAAA,6BAAW,EAAC,IAAI,CAAC,CAAA;AACzB,CAAC"}
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "MnciNxPythonPipApplication",
5
+ "title": "Create a Python application",
6
+ "description": "Hand-authors a pip-native Python app: pyproject.toml (hatchling) + project.json (lint/test/build targets) + a sample module and pytest.",
7
+ "type": "object",
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "description": "The project name.",
12
+ "$default": { "$source": "argv", "index": 0 },
13
+ "x-prompt": "What name would you like to use?",
14
+ "pattern": "^[a-zA-Z][^:]*$"
15
+ },
16
+ "directory": {
17
+ "type": "string",
18
+ "description": "Workspace-relative directory for the project (defaults to apps/<name>)."
19
+ }
20
+ },
21
+ "required": ["name"]
22
+ }
@@ -0,0 +1,21 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ import type { FunctionAppGeneratorSchema } from './schema.d';
3
+ /**
4
+ * Generates a Python Azure Function app (v2 programming model): `function_app.py`
5
+ * + `host.json` + `requirements.txt` + a tested pure helper module.
6
+ *
7
+ * @remarks
8
+ * No `pyproject.toml`/`build` target: the deployable is the **source tree**
9
+ * (Azure's Python worker installs `requirements.txt` and runs
10
+ * `function_app.py` directly — Oryx build at deploy time, no wheel), so this
11
+ * generator writes no build system at all. `func` CLI is not needed to
12
+ * generate — only for local `func start`.
13
+ *
14
+ * @param tree - The Nx virtual file system.
15
+ * @param options - The project name and directory.
16
+ * @returns A promise that resolves once generated files are formatted.
17
+ * @throws Never - pure Tree writes.
18
+ * @typeParam None - this function has no generic type parameters.
19
+ */
20
+ export default function functionAppGenerator(tree: Tree, options: FunctionAppGeneratorSchema): Promise<void>;
21
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/generators/functionApplication/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,IAAI,EAAE,MAAM,YAAY,CAAA;AAS5E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAA;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAA8B,oBAAoB,CAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlH"}