You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(runtimes): add unified Node.js and Python runtime extensions (#400)
* feat(runtimes): add unified Node.js and Python runtime extensions
Add Python and Node.js runtimes with consistent architecture matching
the existing Lean runtime pattern:
- Python: UsePythonVersion@0, PipAuthenticate@1, PIP_INDEX_URL/UV_DEFAULT_INDEX env vars
- Node.js: NodeTool@0 (inline, decoupled from ado-script), npmAuthenticate@0, NPM_CONFIG_REGISTRY env var
- Both use flat feed-url: field with env var injection via agent_env_vars()
- Both accept config: field (recognized but errors if used, reserved for AWF proxy-auth)
- Shared validate_feed_url() in validate.rs for injection checks
- agent_env_vars() trait method on CompilerExtension with BLOCKED_ENV_KEYS validation
- No AWF mounts/PATH prepends needed (hostedtoolcache auto-mounted by AWF)
Unifies the approaches from PRs #398 and #399 into a single consistent implementation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(runtimes): ensure .npmrc exists before npmAuthenticate@0
npmAuthenticate@0 requires workingFile to point at an existing file,
unlike PipAuthenticate@1. Emit a bash step that creates a minimal
.npmrc (with the configured registry or default npmjs) when one does
not already exist, preserving any repo-checked-in .npmrc.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(runtimes): fix dead-code validation ordering, accept node config with warning
- Python: swap mutual-exclusivity check before not-yet-supported error
so both paths are reachable
- Node: accept config: with a warning that .npmrc won't be available
inside AWF yet (instead of hard error), check mutual exclusivity first
- Add tests for mutual-exclusivity errors on both runtimes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(runtimes): reject double-quote in feed URLs and agent env var values
A double-quote in a feed-url or extension env var value would produce
malformed YAML in the generated pipeline (the value is emitted as
KEY: "value"). Reject at validation time in both validate_feed_url()
and collect_agent_env_vars() with clear error messages.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(runtimes): Python config: produces warning instead of error
Align with Node.js behavior — accept config: with a warning that the
config file will not be available inside the AWF agent environment yet,
rather than a hard compile error.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(runtimes): make auth tasks conditional on feed-url or config
PipAuthenticate@1 and npmAuthenticate@0 (plus ensure-npmrc) are now
only emitted when feed-url: or config: is set. Users who enable
runtimes: python: true or runtimes: node: true without an internal
feed no longer get unnecessary auth steps in their pipeline.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(runtimes): reject single-quote in feed URLs, fix doc comments, soften uv prompt
Three fixes:
1. validate_feed_url() now rejects single-quote characters alongside
double-quotes — a single quote in the feed URL would break the bash
single-quoted string in generate_ensure_npmrc's echo command.
2. Doc comments on generate_pip_authenticate() and
generate_npm_authenticate() corrected to say "emitted when feed-url
or config is set" instead of "emitted unconditionally", matching the
actual conditional behavior in prepare_steps().
3. Python prompt supplement no longer claims uv is "pre-installed" —
ADO hosted runners don't ship uv. Now says "install it first with
pip install uv" to avoid command-not-found errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(runtimes): harden env var collection, fix docs and bash quoting
Security & correctness fixes:
1. collect_agent_env_vars now calls reject_pipeline_injection (covers
ADO expressions, pipeline commands, template markers, newlines)
instead of only contains_pipeline_command. Also rejects single
quotes alongside double quotes.
2. collect_agent_env_vars now deduplicates env var keys — bails on
collision instead of silently emitting duplicate YAML keys.
3. generate_ensure_npmrc diagnostic echo lines switched from
double-quotes to single-quotes, preventing ${VAR} shell expansion
if a feed URL contained that pattern.
4. docs/runtimes.md corrected: auth tasks are conditional on feed-url
or config being set, not unconditional. Config field descriptions
updated to reflect warning-not-error behavior. Added note about
PipAuthenticate@1 empty artifactFeeds limitation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(runtimes): PipAuthenticate requires feed-url, not config alone
PipAuthenticate@1 with empty artifactFeeds doesn't authenticate to any
specific feed. Only emit it when feed-url is set — config alone is not
sufficient since the config file won't be available in AWF yet anyway.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: docs/runtimes.md
+84Lines changed: 84 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,3 +33,87 @@ When enabled, the compiler:
33
33
- Emits a compile-time warning if `tools.bash` is empty (Lean requires bash access)
34
34
35
35
**Note:** In the 1ES target, the bash command allow-list is updated but elan installation must be done manually via `steps:` front matter. The 1ES target handles network isolation separately.
36
+
37
+
### Python (`python:`)
38
+
39
+
Python runtime. Auto-installs Python via `UsePythonVersion@0`, emits `PipAuthenticate@1` for internal feed access, adds Python ecosystem domains to the AWF network allowlist, extends the bash command allow-list, and optionally injects feed URL env vars for pip and uv.
| `version` | string | Python version to install (e.g., `"3.12"`, `"3.11"`). Passed to `UsePythonVersion@0` `versionSpec`. Defaults to latest 3.x. |
58
+
| `feed-url` | string | Internal PyPI feed URL. Injects `PIP_INDEX_URL` and `UV_DEFAULT_INDEX` env vars into the agent environment. |
59
+
| `config` | string | Path to a pip/uv config file. Accepted with a warning — the file will not be available inside the AWF agent environment until proxy-auth support lands. |
60
+
61
+
When enabled, the compiler:
62
+
- Injects `UsePythonVersion@0` into `{{ prepare_steps }}` (runs before AWF)
63
+
- If `feed-url` is set, also injects `PipAuthenticate@1` to authenticate the ADO build service identity for internal feeds
64
+
- Auto-adds `python`, `python3`, `pip`, `pip3`, `uv` to the bash command allow-list
65
+
- Adds Python ecosystem domains to the network allowlist (pypi.org, pythonhosted.org, etc.)
66
+
- If `feed-url` is set, injects `PIP_INDEX_URL` and `UV_DEFAULT_INDEX` env vars into the agent environment
67
+
- Appends a prompt supplement informing the agent about Python availability
68
+
- No AWF mounts or PATH prepends needed — `UsePythonVersion@0` installs to `/opt/hostedtoolcache` (auto-mounted by AWF) and publishes PATH entries that AWF merges via `$GITHUB_PATH`
69
+
70
+
**Note:** `PipAuthenticate@1` is currently emitted with an empty `artifactFeeds` input, which configures credentials for all feeds accessible to the build service identity. If your internal feed requires scoped authentication to a specific Azure Artifacts feed, this may need future refinement.
71
+
72
+
### Node.js (`node:`)
73
+
74
+
Node.js runtime. Auto-installs Node.js via `NodeTool@0`, emits `npmAuthenticate@0` for internal feed access, adds Node ecosystem domains to the AWF network allowlist, extends the bash command allow-list, and optionally injects feed URL env vars for npm.
| `version` | string | Node.js version to install (e.g., `"22.x"`, `"20.x"`). Passed to `NodeTool@0` `versionSpec`. Defaults to `"22.x"`. |
93
+
| `feed-url` | string | Internal npm registry URL. Injects `NPM_CONFIG_REGISTRY` env var into the agent environment. |
94
+
| `config` | string | Path to an .npmrc config file. Accepted with a warning — the file will not be available inside the AWF agent environment until proxy-auth support lands. |
95
+
96
+
When enabled, the compiler:
97
+
- Injects `NodeTool@0` into `{{ prepare_steps }}` (runs before AWF)
98
+
- If `feed-url` or `config` is set, also injects `npmAuthenticate@0` (and an ensure-`.npmrc` step) to authenticate the ADO build service identity for internal feeds
99
+
- Auto-adds `node`, `npm`, `npx` to the bash command allow-list
100
+
- Adds Node ecosystem domains to the network allowlist (npmjs.org, nodejs.org, etc.)
101
+
- If `feed-url` is set, injects `NPM_CONFIG_REGISTRY` env var into the agent environment
102
+
- Appends a prompt supplement informing the agent about Node.js availability
103
+
- No AWF mounts or PATH prepends needed — `NodeTool@0` installs to `/opt/hostedtoolcache` (auto-mounted by AWF) and publishes PATH entries that AWF merges via `$GITHUB_PATH`
104
+
- Note: AWF overlays `~/.npmrc` with `/dev/null` for credential security — the `NPM_CONFIG_REGISTRY` env var approach avoids conflicting with this overlay
105
+
106
+
### Combining Runtimes
107
+
108
+
Multiple runtimes can be enabled simultaneously:
109
+
110
+
```yaml
111
+
runtimes:
112
+
python:
113
+
version: "3.12"
114
+
node:
115
+
version: "22.x"
116
+
lean: true
117
+
```
118
+
119
+
All runtime extensions are sorted into `ExtensionPhase::Runtime` and execute before tool extensions (`ExtensionPhase::Tool`), ensuring language toolchains are available before any tools that depend on them.
0 commit comments