Pin Corepack explicitly for the VS Code extension build - #17630
Pin Corepack explicitly for the VS Code extension build#17630Adam Ratzman (adamint) merged 20 commits into
Conversation
Replaces the implicit 'corepack is somewhere on PATH' assumption with an explicit 'npm install -g corepack@0.34.7' step in extension/build.sh, extension/build.ps1, and the three AzDO pipelines that build the extension. The Yarn version is now pinned in extension/package.json via the standard 'packageManager' field (yarn@1.22.22), removing duplicate @1.22.22 pins from build.sh, build.ps1, and Extension.proj. The build scripts default COREPACK_NPM_REGISTRY to the dnceng dotnet-public-npm mirror and disable the Corepack download prompt, and the same defaults are set as AzDO pipeline variables in common-variables.yml and public-pipeline-template.yml so Corepack downloads Yarn from an approved internal feed rather than npmjs.org. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17630Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17630" |
There was a problem hiding this comment.
Pull request overview
This PR makes the VS Code extension build more reproducible and less agent-image-dependent by explicitly pinning Corepack (installed via npm) and centralizing the Yarn version pin via the packageManager field, while also routing Corepack’s downloads through the internal dotnet-public-npm mirror in CI.
Changes:
- Add
packageManager: yarn@1.22.22to centralize the Yarn pin for Corepack. - Update extension build scripts and MSBuild packaging to use
corepack prepare --activate+corepack yarn ...(no inline Yarn version pin). - Update AzDO pipelines/templates to install a pinned Corepack and set Corepack registry/prompt environment variables.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| extension/package.json | Adds packageManager to make the Yarn version a single source of truth for Corepack. |
| extension/Extension.proj | Removes inline yarn@... pins and makes Corepack resolve Yarn via packageManager from the extension directory. |
| extension/CONTRIBUTING.MD | Updates contributor guidance to the Corepack-based flow and documents how to bump Yarn. |
| extension/build.sh | Installs pinned Corepack via npm, enables Corepack shims, and activates Yarn from packageManager. |
| extension/build.ps1 | PowerShell equivalent of the pinned Corepack + prepare --activate flow. |
| eng/pipelines/templates/public-pipeline-template.yml | Adds Corepack env vars so CI uses the internal npm mirror and doesn’t hang on prompts. |
| eng/pipelines/common-variables.yml | Adds shared Corepack env vars for internal/unofficial/codeql/release pipeline usage. |
| eng/pipelines/azure-pipelines.yml | Replaces “install yarn” with “install Corepack + prepare --activate” from extension/. |
| eng/pipelines/azure-pipelines-unofficial.yml | Same Corepack installation/activation changes as the official pipeline. |
| eng/pipelines/azure-pipelines-codeql.yml | Same Corepack installation/activation changes for CodeQL pipeline. |
- Add npmAuthenticate@0 + NPM_CONFIG_USERCONFIG setup to the CodeQL pipeline before the Install Corepack step. The previous CodeQL pipeline worked anonymously against dnceng dotnet-public-npm only because yarn@1.22.22 was already cached there; corepack@0.34.7 is not, and the pipeline would have started failing on the first run. - After 'npm install -g corepack@<pin>' in build.sh, build.ps1, and all three AzDO pipeline PowerShell steps, run 'corepack --version' and fail loudly if the version doesn't match the pin. On Windows the bundled corepack.cmd under %ProgramFiles%\nodejs can shadow the npm-global shim under %APPDATA%\npm, so a successful install does not guarantee the pinned Corepack is what 'corepack enable' actually runs. - In build.sh and build.ps1 only (not the pipelines), force the public npm registry for the Corepack install via --registry=https://registry.npmjs.org so first-time OSS contributors are not blocked on dnceng cache misses. Corepack is build tooling and never ships in the extension VSIX, so registry choice is local-dev ergonomics only. - Document the 'EACCES from npm install --global' and 'corepack version mismatch' troubleshooting steps in extension/CONTRIBUTING.MD, plus a note that bumping Yarn requires the new tarball to be pulled through dotnet-public-npm at least once with credentials. - Drop the misleading 'update Extension.proj inline pin' comment from the build scripts (no such inline pin remains). - Drop the redundant DependsOnTargets='ValidateYarnLockRegistries' from CheckYarnInstalled; the parent BuildAndPackageExtension target already declares the same dependency. - Normalize the workingDirectory path separator across the three AzDO pipelines to backslash, matching the convention used elsewhere in those Windows-only files. - Add trailing newline to extension/CONTRIBUTING.MD. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
corepack@0.34.7 is already cached in the dnceng dotnet-public-npm feed and serves anonymously, so the build scripts don't need to bypass the internal mirror to install Corepack. Verified anonymously: GET .../dotnet-public-npm/.../corepack/-/corepack-0.34.7.tgz -> 200 OK, 229 KB The earlier comment overstated the problem: only versions that have never been requested from the feed return 401 (the feed's pull-through behavior requires auth for the very first fetch, then anyone can read the cached copy). The same caveat applies to bumping the pinned Yarn or Corepack version, so the heads-up about pre-seeding the feed now lives in extension/CONTRIBUTING.MD rather than the build-script comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure Corepack and Yarn setup use the configured npm registry and authenticated Azure Artifacts credentials across local scripts and CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Azure Artifacts does not support the npm /package/version metadata endpoint Corepack uses when COREPACK_NPM_REGISTRY is set. Keep the internal feed for npm's Corepack install, but let Corepack prepare Yarn without that registry override. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Hosted Windows images can already have a Yarn shim in npm's global prefix, and the npm Corepack package owns that shim. Use --force only in CI tool setup so the pinned Corepack install can replace ephemeral runner shims without changing local developer scripts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After installing the pinned Corepack package, hosted Windows runners can still resolve the bundled Corepack first. Prepend npm's global prefix for the current CI step and subsequent steps so Corepack 0.34.7 is the shim that prepares and runs Yarn. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the duplicate ATS capability parser/test/baseline shape from PR microsoft#17631 to avoid merge conflicts between the branches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the TypeScript API compatibility fix in the dedicated Foundry API PR instead of duplicating it in this Corepack CI fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Validation update after merging latest main (SHA
ADO run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2987334&view=results |
Applies the dedicated Corepack pinning and Yarn cache seeding diff from microsoft#17630 instead of keeping a local source-build workaround on this branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ling - extension/build.sh: export NPM_REGISTRY so the child scripts/prepareCorepackYarn.mjs process actually inherits it. Without the export, the script silently fell back to its DefaultNpmRegistry constant and any user override of NPM_REGISTRY would be ignored when seeding Corepack's Yarn cache. COREPACK_ENABLE_DOWNLOAD_PROMPT on the next line was already exported; this restores symmetry. - extension/scripts/prepareCorepackYarn.mjs: add a comment in getCorepackHome() documenting the implicit coupling to corepack 0.34.x's own cache-path resolution. If COREPACK_VERSION is later bumped to a release that switches schemes (e.g., env-paths, which would relocate the macOS cache to ~/Library/Caches/node/corepack), this fallback would silently seed the wrong directory. The AzDO pipelines already set COREPACK_HOME explicitly to avoid this; this comment flags the same hardening as the simplest fix when the pin is updated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ankit Jain (radical)
left a comment
There was a problem hiding this comment.
One HIGH-severity robustness issue from a multi-model review pass (Opus 4.8 + GPT 5.5, validated by Opus 4.7 high). The local extension/build.sh and extension/build.ps1 are missing --force on the npm install --global corepack@... call, which all CI paths already pass. This will break on developer machines that have any of yarn/pnpm/yarnpkg/pnpx already installed globally (which is most devs who have ever touched this repo — the project itself shipped npm install -g yarn@1.22.22 until this PR). Details inline.
Will follow up with additional MEDIUM/LOW findings once they're re-validated.
Ankit Jain (radical)
left a comment
There was a problem hiding this comment.
Follow-up: 5 MEDIUM findings from the same multi-model review pass (Opus 4.8 + GPT 5.5, validated by Opus 4.7 high; two more independent validators are still running and I'll add any net-new findings they surface).
None of these are blockers — the PR's primary goal is achieved — but several weaken the reproducibility/maintainability story it's building:
- CI inconsistency (#2): GH Actions Windows fetches Yarn from yarnpkg.com, bypassing dnceng, and the seed script has zero non-Windows CI coverage.
- Documented dev flow gap (#3): root
./build.sh /p:BuildExtension=true(the Arcade flow) never runs the new bootstrap, so a clean machine following the documented build instructions will fail in a way the new error message doesn't actually explain. - Concurrency hazard (#4): local builds racing on the shared user-default
COREPACK_HOMEcan corrupt each other; the script's own comment already calls out the fix. - Canonical-form mismatch (#5): the
packageManagerregex rejects the integrity-suffixed form thatcorepack use yarn@<v>itself writes, which is the only thing CONTRIBUTING.MD now points contributors at. - Drift surface (#6): five files own
0.34.7and three pipelines own the same ~30-line install block.
Details inline.
Six follow-ups from the review on microsoft#17630: * Add --force to the local 'npm install --global corepack@<version>' in extension/build.sh and extension/build.ps1. Without --force, npm refuses to overwrite the yarn / yarnpkg / pnpm / pnpx bin entries owned by any pre-existing global yarn or pnpm install (the state this repo itself shipped before the bootstrap existed), aborting with EEXIST. The CI pipelines already pass --force for the same reason. * Switch the GitHub Actions 'extension_tests_win' job to use prepareCorepackYarn.mjs instead of 'corepack prepare --activate'. The built-in Corepack prepare path downloads Yarn 1.x from registry.yarnpkg.com (hardcoded in Corepack 0.34's config.json and not redirectable via COREPACK_NPM_REGISTRY), bypassing the dnceng feed this workflow exists to validate. Also scope COREPACK_HOME to runner.temp. * Add an 'extension_bootstrap_linux' GH Actions job so the non-Windows branches of prepareCorepackYarn.mjs (POSIX npm invocation, no node.exe wrapping) are exercised on a fresh CI image, not only on contributor machines. * Update the 'CheckYarnInstalled' error in extension/Extension.proj to name the actual supported entry points (the root Arcade flow './build.sh -build-extension' and direct 'dotnet build extension/Extension.proj' both require running extension/build.sh or extension/build.ps1 first) instead of pointing developers at a path that isn't part of the documented root build flow. * Scope COREPACK_HOME to '$SCRIPT_DIR/.corepack-cache' in the local build entrypoints. prepareCorepackYarn.mjs rewrites the cache in place via rmSync + renameSync, so concurrent builds (multiple worktrees, parallel invocations) sharing the user's default cache can corrupt each other. The CI pipelines already scope this per-job via Agent.TempDirectory / runner.temp; do the same locally. New cache directory is gitignored. * Loosen PackageManagerPattern in prepareCorepackYarn.mjs to accept the optional integrity suffix that 'corepack use yarn@<v>' writes ('yarn@1.22.22+sha512.<hex>'). CONTRIBUTING.MD points contributors at 'corepack use' for updating the pin, so rejecting the canonical spec-conformant value would have broken that flow. * Centralize the pinned Corepack version in extension/scripts/corepack-version.txt. The bash and PowerShell build scripts, the GitHub Actions workflow, and all three AzDO pipelines (azure-pipelines.yml, azure-pipelines-unofficial.yml, azure-pipelines-codeql.yml) now read from this single file, removing the six-place version-drift hazard. Validated by running extension/build.sh end-to-end from a clean state: npm install (with --force), corepack enable, prepareCorepackYarn.mjs against $SCRIPT_DIR/.corepack-cache, corepack yarn install --frozen-lockfile, corepack yarn compile, dotnet build Aspire.Cli. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The runner context is not available in job-level env evaluation, so
'COREPACK_HOME: ${{ runner.temp }}/corepack' caused the workflow file
to be rejected before any job could run ('This run likely failed because
of a workflow file issue', latest_check_runs_count: 0).
Forward COREPACK_HOME from inside the Install Corepack step using
$RUNNER_TEMP (which is exposed as an env var on the runner) and
$GITHUB_ENV. The value reaches all subsequent steps the same way a
job-level env entry would have, so the corepack cache stays isolated
to the job.
Verified with actionlint: previously two 'context "runner" is not
allowed here' errors at lines 320 and 395; now clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run microsoft-aspire |
Ankit Jain (radical)
left a comment
There was a problem hiding this comment.
Multi-model review pass (Opus 4.8 + GPT 5.5, validated by Opus 4.8). 7 inline findings on the code, plus 1 finding on the PR description below.
[MEDIUM] PR description is materially out of date
The "What changed" section still says:
Both scripts default
COREPACK_NPM_REGISTRYto the dncengdotnet-public-npmmirror …then
corepack prepare --activateAzDO pipeline variables: added
COREPACK_NPM_REGISTRY(dnceng mirror)
All three are false in the current code:
- Scripts use
NPM_REGISTRY, notCOREPACK_NPM_REGISTRY.extension/CONTRIBUTING.MD:46explicitly warns against pointingCOREPACK_NPM_REGISTRYat the Azure Artifacts feed (Corepack's/<pkg>/<ver>metadata route 404s there). - The flow is
node ./scripts/prepareCorepackYarn.mjs, notcorepack prepare --activate(which would pull Yarn fromregistry.yarnpkg.com, the exact thing this PR is supposed to avoid). - The pipeline var added is
NPM_REGISTRY(eng/pipelines/common-variables.yml:15,templates/public-pipeline-template.yml:37).
The "downloads route through the approved internal feed" claim is security-relevant. A reviewer trusting the body sees a mechanism (COREPACK_NPM_REGISTRY + corepack prepare --activate) that the implementation specifically avoids because it doesn't work against Azure Artifacts.
Fix: rewrite the bullets to describe NPM_REGISTRY, prepareCorepackYarn.mjs, per-job COREPACK_HOME, and scripts/corepack-version.txt. Drop the COREPACK_NPM_REGISTRY / corepack prepare --activate references.
|
After your fixes could you also please do a fresh azdo build for validation? |
- prepareCorepackYarn.mjs: correct the misleading Corepack hash-verification comment to explain that integrity rests on the npm pack fetch from the dnceng feed, not on Corepack re-verifying a pre-seeded cache. Cite the corepackUtils.ts source at v0.34.7. - prepareCorepackYarn.mjs: catch both EEXIST and ENOTEMPTY in the renameSync race handler (platform-specific rename(2) collision codes) and document why both are needed. - prepareCorepackYarn.mjs: bump source citation v0.34.0 -> v0.34.7 in getCorepackHome to match the version pin in corepack-version.txt. - extension/build.sh, extension/build.ps1: tighten the over-claimed isolation comment to cover only multi-worktree setups; same-worktree concurrent builds still race. - extension/Extension.proj: set EnvironmentVariables=COREPACK_HOME=<extension>/.corepack-cache on every Exec that invokes corepack, so the documented recovery path (./build.sh -build-extension, or direct dotnet build extension/Extension.proj after running extension/build.sh once) works without depending on parent-shell env propagation. - extension/Extension.proj and .github/workflows/tests.yml: switch lockfile registry validation from a denylist of npmjs.org/yarnpkg.com to an allowlist requiring the internal dotnet-public-npm feed, scoped to lines starting with 'resolved' (yarn.lock's only URL-bearing lines). - .github/workflows/tests.yml: add the same allowlist validation step to the extension_bootstrap_linux job before Install dependencies. - eng/pipelines/templates/install-corepack.yml: extract the triplicated 'Install Corepack' PowerShell block; parameterize displayPrefix. azure-pipelines.yml, azure-pipelines-unofficial.yml, and azure-pipelines-codeql.yml now reference the template. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-3 validation update (HEAD
|
Mitch Denny (mitchdenny)
left a comment
There was a problem hiding this comment.
Code review pass on the current state of the PR (most earlier findings already addressed). 1 HIGH, 2 LOW, 1 NIT below.
- HIGH:
Extension.projforcibly overridesCOREPACK_HOMEto a local path the AzDO pipelines never seed, so AzDO CI silently falls back toregistry.yarnpkg.com. - LOW:
prepareCorepackYarn.mjshas a second race window beyond the documented EEXIST/ENOTEMPTY one — an unconditionalrmSynccan destroy a sibling's just-completed cache. - LOW: The new
extension_bootstrap_linuxjob lacks thecorepack --versionshadowing check that the Windows job has. - NIT:
corepackMetadataPathForhelper duplicatescorepackMetadataPath.
- HIGH: Extension.proj _CorepackHome now inherits COREPACK_HOME from the parent environment when set (AzDO install-corepack.yml + GitHub Actions extension jobs both export it), falling back to <extension>/.corepack-cache only for local recovery. Previously the unconditional override defeated the AzDO seed and forced corepack to fall back to registry.yarnpkg.com. - LOW: prepareCorepackYarn.mjs now guards the rmSync with an immediate re-check of the .corepack metadata path, narrowing the race window where a concurrent winner's just-renamed cache could be destroyed. - NIT: Removed the single-use corepackMetadataPathFor helper; inlined the join(stagingDirectory, '.corepack') call.
Re-imports the latest Corepack install template + pinned version + Yarn preparation script from the sibling PR. Required to share the same Corepack pin between the npm-publishing release pipeline and the extension build, and to keep Bash 3.2-safe wiring in place. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round-4 review fixes + CI/build validationPushed
Verifying that fix #1 actually matteredThe previous internal build …because The new internal build CI results on
|
* Add npm packages for Aspire CLI
Create pointer and RID-specific npm packages from the native CLI archives and wire npm packaging, verification, and staging into the existing native CLI package build.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Document Aspire CLI npm package POC
Add a design spec for the npm package POC and targeted comments explaining the launcher cache, generated package map, npm metadata, and package verification assumptions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add npm prior art to CLI package spec
Document the npm and native package examples that shaped the Aspire CLI npm package POC, including optional platform packages, libc-specific packages, and writable-cache guidance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add npm publish workflow with provenance and OIDC support
- Create .github/workflows/publish-npm.yml for manual npm package publishing
- Support workflow_dispatch with inputs for version, run_id, dist_tag, pr_number, only_rid, skip_meta, and dry_run
- Enable npm provenance via id-token: write permission for Trusted Publisher OIDC
- Require admin/maintain permission for non-dry-run publishes
- Download artifacts from specified GitHub Actions run_id
- Publish RID packages before meta package with fail-fast: false matrix
- Wait for RID packages to propagate on npm before publishing meta package
- Support fallback to NPM_TOKEN secret until Trusted Publisher OIDC is configured
- Include recovery options via only_rid and skip_meta inputs
- Generate workflow summary with job status and next steps
- Update docs/specs/npm-cli-package.md with publishing section and prerequisites
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix shell subshell variable scope bug in npm publish tarball verification
The MISSING_PACKAGES counter was incremented inside a pipeline subshell
and never propagated to the parent shell, causing the verification check
to always see 0 and never fail on missing tarballs.
Changed from pipeline (echo | jq | while) to process substitution
(while < <(echo | jq)) so the while loop runs in the main shell and
variable updates are visible to the later check.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix npm workflow: ensure meta package waits for RID packages
When dry_run=true, wait-for-packages is skipped, which previously
allowed publish-meta-package to start without waiting for
publish-rid-packages to complete. This violated the spec requirement
that RID packages must be published/validated before the meta package.
Changes:
- Add publish-rid-packages to publish-meta-package job needs
- Update if condition to require publish-rid-packages.result == 'success'
- Preserve existing behavior: wait-for-packages can be skipped (dry run)
but only after RID packages complete successfully
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix npm publish workflow: exact version matching and configurable timeout
Address code review findings:
1. Exact version matching for tarballs:
- Replace broad patterns (microsoft-aspire-cli-$RID-*.tgz) with exact
version patterns (microsoft-aspire-cli-$RID-$VERSION.tgz)
- Apply to download verification, publish-rid-packages, and
publish-meta-package steps
- Improve error messages to show exact expected filename when missing
2. Configurable propagation timeout:
- Add propagation_timeout_seconds workflow input (default 900s = 15min)
- Validate input is positive integer in validate job
- Compute MAX_ATTEMPTS from timeout/sleep interval, rounding up
- Include timeout in workflow summary and parameter output
- Preserve bounded polling and dry-run skip behavior
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Move @microsoft/aspire-cli npm publishing into the AzDO release pipeline
Publishing the @microsoft/aspire-cli scoped npm packages now happens via the
AzDO release pipeline (eng/pipelines/release-publish-nuget.yml) using the
MicroBuild ESRP publish template, instead of the previous GitHub Actions
publish-npm.yml workflow. The release pipeline extends from
1ES.Official.Publish.yml@MicroBuildTemplate so it has access to the
DevDivEsrpAzDoSrvConn service connection.
Per-platform artifacts and the pointer package are produced and verified
during the source build (azure-pipelines.yml + build_sign_native.yml), then
flat-shipped via BlobArtifacts (eng/Publishing.props). The release pipeline
splits them into RID and pointer pipeline-artifact folders, attaches SBOMs,
and submits two MicroBuild.Publish.yml invocations (RID packages first, then
pointer) with a configurable propagation delay so the pointer never resolves
to a missing optional dependency.
Also fixed a cross-platform path bug in eng/clipack/Common.projitems where
$(RepoRoot)eng\\scripts\\pack-cli-npm-package.ps1 mixed separators in a
way that breaks pwsh resolution on Linux/macOS, and added the actions/setup-node
step missing from .github/workflows/build-cli-native-archives.yml now that
PackDotnetTool depends on PackNpmPackage (which calls npm pack).
Note: the npm contentType for MicroBuild.Publish.yml is documented in the
ESRP onboarding doc but the task itself is not yet shipped for npm. The
release pipeline is wired against the documented parameter shape so it can
go live the moment the task is available; today's release flow can run with
SkipNpmPublish=true.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Promote @microsoft/aspire-cli npm package from POC to production
Brings the npm package path to parity with the brew cask / winget manifest
flows:
* Add Aspire.Cli.Utils.NpmInstallDetection so 'aspire update --self' and
update notifications detect global npm installs via the
ASPIRE_NPM_PACKAGE/_VERSION/_RID env vars the launcher already sets, and
print 'npm install -g @microsoft/aspire-cli@latest' instead of running the
GitHub-binary downloader against npm-owned files. Wired into UpdateCommand
(--self path and post-project-update prompt) and CliUpdateNotifier.
* Tighten launcher cache freshness in eng/clipack/npm/aspire.js: compare
both size and mtime so a stale cache from a prior same-version install
cannot shadow a freshly extracted native binary.
* Add eng/pipelines/templates/prepare-npm-cli-packages.yml that runs a real
'npm install -g <rid>.tgz && npm install -g --omit=optional <pointer>.tgz'
against the just-built tarballs on a scratch npm prefix, asserts
'aspire --version' matches the build version, verifies the launcher's
cache layout, uninstalls, and emits validation-summary.json. Wired into
the Prepare Installers stage in azure-pipelines.yml alongside Homebrew
and WinGet.
* Gate release-publish-nuget.yml on the validation summary before invoking
MicroBuild.Publish for npm. Download the summary from the source build,
re-publish with SBOM in stage 1, then refuse to submit unless
validatedByPreparePipeline is true and every required check passed.
* Fix a pre-existing here-string parse bug in the 'Prepare npm Artifacts
for Publishing' step. PowerShell requires the closing terminator at
column 0, but YAML block scalars require every line to stay at or above
the block indent. Compose the error message from an array joined with
[Environment]::NewLine instead.
* Drop POC framing from docs/specs/npm-cli-package.md and document the
Sigstore-provenance tradeoff honestly (ESRP's npm publish path does not
currently emit Sigstore attestations; integrity is anchored at the
signed binary and the Microsoft1ES maintainer identity).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden npm prepare/publish validation against partial-failure leakage
Two adversarial-review findings from local repro of the install-test:
1) prepare-npm-cli-packages: initialize NpmCheck* task variables to
'failed' at the start of the install-test step. If a subsequent
bash command exits via 'set -e' before the matching pass marker
runs, the summary JSON now records 'failed' instead of an
unexpanded '$(NpmCheckXxx)' AzDO token. The release-side gate
already rejects anything that is not 'passed', but 'failed' is a
much higher-signal diagnostic.
2) prepare-npm-cli-packages: tighten 'aspire --version' parsing. Use a
semver-shaped regex against the full output and print the raw
output for diagnostics, instead of blindly trusting 'tail -n 1'.
System.CommandLine's VersionOption normally just prints the
version and exits, but defending against a stray warn/info line
makes failures self-explanatory.
3) release-publish-nuget: defense-in-depth — the gate now explicitly
rejects status values that still look like an unexpanded
'$(SomeVar)' token, in addition to the '!= passed' check. This
catches future schema drift in the prepare template too.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix npm package release validation
Block prerelease npm publishes until non-latest dist-tags are supported, validate npm install summaries across Windows/Linux/macOS, and make launcher cache replacement avoid deleting the previous executable before rename.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update CI Node version to 22
Use Node 22 for native CLI npm packaging and install-validation CI paths.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add npm package signing coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Validate npm release parameters before publishing
Fail npm publish preflight validation before any irreversible NuGet publishing step so release runs cannot partially publish NuGet packages and then fail on npm ESRP/prerelease prerequisites.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use offline Homebrew validation for release dry runs
Release dry-runs do not upload GitHub release assets, so LiveRelease Homebrew validation cannot pass for a new version without mutating the release. Switch Homebrew validation to LiveArchives when DryRun=true while preserving LiveRelease for non-dry releases.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert "Use offline Homebrew validation for release dry runs"
This reverts commit ddfea5c.
* Harden npm release validation
Add ESRP publish-template and owner invariant coverage, live npm registry smoke validation before channel promotion, a pointer-publish skip for safe reruns, and npm-installed CLI update tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Use Corepack changes from PR 17630
Applies the dedicated Corepack pinning and Yarn cache seeding diff from #17630 instead of keeping a local source-build workaround on this branch.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address adversarial review findings
- aspire.js: when the atomic rename of a freshly copied native binary
fails (e.g. concurrent first-runs racing on Windows where the cached
executable is already loaded), check whether the existing target is
already a valid copy of the source via needsCopy(). If it is, the
other process won the race and our tmp file can be discarded without
failing the launcher. Only unexpected errors propagate.
- release-publish-nuget.yml: wrap the post-smoke Remove-Item cleanup in
the finally block in a try/catch with -ErrorAction Stop and a Write-
Warning fallback so a transient Windows file-handle lock during temp-
directory teardown does not mask a successful npm registry smoke test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make npm install validation script Bash 3.2 compatible
The macOS AzDO runner executes Bash@3 tasks with /bin/bash which is
still Bash 3.2 on every shipping macOS release. The 'Locate pointer
and RID tarballs' step in 'npm install validation (macOS native RID)'
was failing with:
/Users/runner/work/_temp/<id>.sh: line 10: shopt: globstar: invalid shell option name
Bash exited with code '1'.
Two constructs in the script require Bash 4+:
- 'shopt -s globstar' (not in 3.2; we never used '**' anyway since
we enumerate with 'find')
- 'mapfile -t' (not in 3.2)
Replace both with a portable 'find | while IFS= read' loop that works
on Bash 3.2 and survives filenames containing spaces. Verified locally
under GNU bash 3.2.57.
Add a regression test in Infrastructure.Tests that fails the build if
'shopt -s globstar', 'mapfile', 'readarray', or 'declare -A' are
reintroduced into the npm install validation template.
Dry-run AzDO build 2987449 (Build def 1602) caught this on the macOS
'Prepare Installers' phase; linux-x64 and win-x64 are unaffected
because their bash is already 4+.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Accept SemVer 2.0 +buildmetadata in npm install validation
Dry-run AzDO build 2987514 progressed past the macOS Bash 3.2 fix and
then failed the install/verify/uninstall smoke on macOS with:
Raw output: 13.5.0-preview.1.26279.34+727fe3ca9dcecbcc6d10d8b4373ae6f5779b25b4
Reported version:
##[error]aspire --version reported '' but expected '13.5.0-preview.1.26279.34'
The CLI's --version prints the InformationalVersion which is full SemVer 2.0
(MAJOR.MINOR.PATCH-PRE+BUILDMETA where BUILDMETA is the source commit SHA).
The previous regex required the line to end at the pre-release segment so
the +<sha> suffix made the whole match fail and actualVersion became empty.
Extend the regex to optionally accept '+<buildmeta>', then strip it with the
portable POSIX expansion '${var%+*}' before comparing to the npm package
version (npm SemVer intentionally ignores build metadata for equality per
https://semver.org/#spec-item-10).
The PowerShell post-publish smoke in release-publish-nuget.yml already
handles this case (line ~1298, regex ends with '(\\+.*)?$') so no change
needed there.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Skip npm registry resolution in CLI install validation to avoid 9m pool hang
The pointer package declares every supported RID as an optionalDependency
pinned to the just-built version. Even with --omit=optional, npm still
resolves optional dep metadata from the registry while building the
dependency tree, and in network-isolated 1ES Linux/Windows pools each of
the 7 lookups burns the full fetch-timeout. Dry-run build 2987581 hit a
9-minute hang on the pointer install step for that reason while macOS
(unrestricted egress, fast 404) completed in 3 seconds.
Pair --omit=optional with --offline so npm never touches the network for
this validation: optional deps are skipped without a resolution attempt
and the local tarball installs straight from disk. A short
--fetch-timeout=15000 is set as belt-and-suspenders. NPM_CONFIG_CACHE
already points at a fresh empty directory so --offline cannot reuse a
poisoned cache. Local verification against a synthetic 8-dep pointer
shows installation completes in 121ms with optional deps marked UNMET
(not fetched, not installed); uninstall completes in 88ms.
Apply the same args to the uninstall step so an audit/funding call
cannot hang the cleanup the way it almost hung the install.
Add a PrepareNpmCliPackagesScriptInstallsOfflineWithTimeout regression
test to prevent a refactor from quietly removing the flags.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Resync Corepack changes with latest PR #17630 (head 0e2136e)
Re-imports the latest Corepack install template + pinned version + Yarn
preparation script from the sibling PR. Required to share the same
Corepack pin between the npm-publishing release pipeline and the
extension build, and to keep Bash 3.2-safe wiring in place.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix npm launcher: lazy package map load, musl arm64, signal forwarding
* loadRidPackageNames() now runs lazily from main() and surfaces a
friendly 'installation is corrupted ... Reinstall' error rather than
a raw Node stack trace when the JSON map is missing or malformed.
Previously the read happened at module top-level, bypassing the
launcher's outer try/catch.
* Detect Linux musl on arm64 and throw 'Unsupported platform' rather
than silently falling through to the glibc-linked linux-arm64 binary
(which crashes at exec with a dynamic-linker error).
* Forward SIGINT, SIGTERM, SIGHUP and SIGQUIT to the child process.
Previously `kill <wrapper-pid>` orphaned the native CLI process,
which broke programmatic shutdown of long-running commands like
'aspire run' that keep an AppHost alive.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix RID README rendering: use literal here-string for PowerShell
The previous expandable here-string (@"..."@) interpreted both the
markdown code-span backticks AND $Rid / $PackageName as PowerShell
escape sequences and interpolations. Result: the shipped README on
npmjs.org rendered as 'Native Aspire CLI binary for $Rid.' with no
backticks visible.
Switch to a non-expanding here-string (@'...'@) plus -replace for
__RID__, __PACKAGE_NAME__ and __RID_PACKAGE_NAME__ placeholders so the
markdown code spans survive verbatim.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Verify RID packages on npm before publishing pointer; reject empty version
* Add 'Verify npm RID Packages Present Before Pointer Publish' step
that extracts the pointer tgz, walks its optionalDependencies, and
runs 'npm view <dep>@<version>' for each. If any RID dep is missing
on the registry, fail the publish before ESRP submits the pointer.
This closes a window where SkipNpmRidPublish=true, an operator
partial run, or an ESRP RID-publish failure could ship a pointer
package that resolves on install but throws 'native package was not
installed' on first 'aspire' invocation.
* The preflight reads RIDs from the pointer's own optionalDependencies
so it does not drift if RIDs are added or removed.
* In the post-publish version smoke, explicitly reject the case where
'aspire --version' exits 0 with empty stdout. The previous
@(...) -notmatch sequence produced an empty array on no output,
which is falsy in PowerShell and let the bad install slip past the
version regex check.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add regression tests for npm launcher, README rendering, and preflight
Six new asserts pinning the fixes from the multi-model code review:
* RID preflight is wired before the pointer publish step
* Empty 'aspire --version' stdout is rejected by the post-publish smoke
* Launcher throws on Linux musl arm64
* Launcher forwards SIGINT/SIGTERM/SIGHUP/SIGQUIT to the child process
* Launcher loads the RID package map lazily and emits a friendly
'installation is corrupted ... Reinstall' error
* pack-cli-npm-package.ps1 uses a literal here-string so markdown
backticks survive in the published RID README
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix MicroBuild extends template for ESRP authorization
Use 'MicroBuild.1ES.Official.Publish.yml@MicroBuildTemplate' (composite)
instead of '1ES.Official.Publish.yml@MicroBuildTemplate' (plain).
Without the 'MicroBuild.' prefix, the MicroBuildAuthorizePublishPlugin
task is auto-injected without credential context, causing a 401 against
devdiv.pkgs.visualstudio.com/_packaging/MicroBuildToolset.
Pattern verified against microsoft/vscode-azuretools, microsoft/pyright,
microsoft/vscode-python-environments, microsoft/vscode-deviceid, and
microsoft/vscode-common-python-lsp release pipelines — all of which use
the composite template for ESRP-based npm publishing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update regression test for MicroBuild.1ES.Official.Publish.yml extends template
The MicroBuild. prefix is required so the auto-injected
MicroBuildAuthorizePublishPlugin task inherits credential context for the
devdiv MicroBuildToolset feed. Without it, the task fails with a 401 in the
PrepareArtifacts stage before any publishing can begin.
Verified against microsoft/vscode-azuretools, microsoft/pyright, and
microsoft/vscode-python-environments — all use the MicroBuild. prefix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix MicroBuildAuthorize 401 and Telemetry TeamName errors
The MicroBuild.1ES.Official.Publish.yml@MicroBuildTemplate extends template auto-injects
two tasks into every job, both of which were failing in release 2987726:
1. MicroBuildAuthorizePublishPlugin@0 (start of every job)
- Defaulted to fetching its nuget package from devdiv.pkgs.visualstudio.com/_packaging/MicroBuildToolset
- This pipeline runs in the dnceng collection which does not have devdiv feed credentials
- Result: HTTP 401 fails the stage before any customer step runs
2. MicroBuildCleanup@1 (end of every job, displayed as 'MicroBuild Telemetry')
- Hard-requires a pipeline variable literally named TeamName
- We had _TeamName (Arcade convention) but not TeamName
Fixes (both surfaced from MicroBuildTemplate Jobs/PublishJob.yml + Jobs/Job.yml):
* Add 'TeamName: dotnet-aspire' at pipeline-scope variables so MicroBuildCleanup@1
succeeds on every job.
* On the only job that actually performs an ESRP publish (ReleaseJob via
1ES.PublishNuget@1 / MicroBuild.Publish.yml), set
templateContext.mb.publish.feedSource to the dnceng MicroBuildToolset mirror
(https://pkgs.dev.azure.com/dnceng/_packaging/MicroBuildToolset/nuget/v3/index.json).
This is the same pattern dotnet/roslyn uses. The dnceng feed is accessible
to builds in this collection automatically.
* On every other job (PrepareJob, WinGetJob, DispatchGitHubTasksJob,
PublishReleaseAssetsJob, HomebrewValidateJob), set
templateContext.mb.publish.enabled: false. These jobs only download
artifacts, push to GitHub via app tokens, or run validation; none of them
publish via ESRP, so the publish-authorize plugin should not run at all.
Adds two regression tests in ReleasePublishNugetPipelineTests so a future edit
that removes TeamName or removes the publish auth overrides fails before the
pipeline is queued again.
Verified against MicroBuild template source pulled from
dev.azure.com/devdiv/MicroBuildTemplates/MicroBuildTemplates (Stages/PublishStage.yml,
Jobs/PublishJob.yml, Jobs/Job.yml).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* harden npm publish: registry pin, retry preflight, semver parse, sig sanity, Node 20, CRLF strip
Multi-model code review (Aspire arch, Opus 4.8, Opus 4.7) surfaced eight items.
The highest-severity issues fixed here are:
1. CRLF regression in Windows install validation (opus-4.7 — blocker)
- prepare-npm-cli-packages.yml bash captures 'aspire --version' on Win
runners under Git Bash. System.CommandLine 2.x writes CRLF on Windows,
bash $() strips LF but NOT CR, anchored semver regex fails on \r.
- Fix: pipe through 'tr -d "\r"' before capture. This regressed in
commit debf4eb when 'tr -d [:space:]' was replaced with 'grep -Eo'.
Dry run 2987740 did NOT exercise this path because SkipNpmPublish=true
skips the consumer; Monday's real publish would have failed.
2. Preflight registry pin (opus-4.8)
- 'npm view $spec version' in pointer preflight relied on the agent's
ambient registry. 1ES images may have internal mirrors via .npmrc /
npm_config_registry which (a) could spuriously fail after a successful
public publish or (b) pass against a stale mirror and ship a broken
pointer to npmjs.
- Fix: add explicit '--registry=https://registry.npmjs.org/' pin.
3. Preflight retry loop (opus-4.8)
- Single-shot preflight could fail closed AFTER 7 RID packages are
already published, forcing manual SkipNpmRidPublish=true re-run.
- Fix: wrap in 10x30s retry, matching post-publish smoke.
4. Strict semver-shape filter on 'npm view' output (opus-4.8)
- 'npm view --loglevel=warn 2>&1' merges deprecation / peer-dep /
EBADENGINE warnings onto stdout. 'Select-Object -First 1' could latch
a warning as the version.
- Fix: filter to lines matching strict semver regex, and switch metadata
'npm view' to 2>$null.
5. PGP .sig content sanity check (opus-4.8)
- Earlier validation only checked .tgz.sig EXISTS. If Arcade SignTool
silently produced an empty/garbage sidecar (signing service hiccup,
plugin misconfig), the release would publish unverifiable sidecars.
- Fix: assert each sig >=64 bytes AND contains an OpenPGP marker
(ASCII-armored '-----BEGIN PGP SIGNATURE-----' OR binary packet
tag 2 per RFC 9580 — old-format 0x88-0x8B / new-format 0xC2).
Added to BOTH source build (BuildAndTest.yml) AND release pipeline
so failures surface at PR-time, not just release-time.
6. Node >=20 minimum (Aspire arch review)
- aspire.js wrapper uses '{ cause: error }' (Node 16.9+) and rid->arch
map covers musl-libc selector (npm >=10.7). 'engines.node = >=16'
was technically permissive enough to install on Node 16.x where
'cause' is rejected.
- Fix: bump to '>=20' (covers all Node 16.9+ + libc + ESM-safe
subprocess + Node 18 EOL was 2025-04-30).
Validation:
- All 21 Infrastructure.Tests pass locally (was 14, added 7 regression tests).
- Dry-run on release pipeline 2987740 already validated MicroBuild paths;
this batch hardens against issues that would only have surfaced when
SkipNpmPublish=false on Monday.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden npm CLI release: launcher cache, dry-run validation, summary integrity
Address findings from the adversarial review of the npm CLI release path:
- Launcher (aspire.js): lstat the cached native binary and reject symlinks /
non-regular files before trusting it, closing the cheap cache-redirect
vector. Keep the size+mtime fast path; deliberately skip per-launch
byte-compare since it would tax the hot startup path for a same-user threat
that already defeats the process trust boundary.
- release-publish-nuget.yml: run ESRP owner/approver alias validation on dry
runs too (not just real publishes) so misconfiguration fails the cheap dry
run; keep the prerelease hard-block gated on DryRun=false.
- prepare-npm-cli-packages.yml: derive validatedByPreparePipeline from the
actual four check results instead of hardcoding true, so the release-side
gate that reads it is meaningful.
- release-publish-nuget.yml: cross-check the pointer package version about to
be submitted to ESRP against the prepare-stage validated version, failing
closed if the validated-version variable is missing/unexpanded.
Add a launcher regression test for the non-regular-file cache rejection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden npm launcher and release pipeline from second review
- aspire.js: build the forwarded-signal list per platform so SIGQUIT is
never registered on Windows (process.once('SIGQUIT') throws
uv_signal_start EINVAL there, which crashed the launcher and orphaned the
already-spawned CLI on every Windows invocation); guard each registration.
- aspire.js: register signal handlers before spawning so a signal in the
spawn/registration window can't orphan the child.
- aspire.js: detect musl via process.report.sharedObjects and a
/lib/ld-musl-*.so filesystem probe when ldd is missing, with ldd
authoritative in both directions to avoid false musl on glibc hosts.
- aspire.js: create the binary cache dir owner-only (0700).
- release-publish-nuget.yml: assert every staged RID and pointer tarball's
package.json version equals NpmValidatedExpectedVersion before the RID
publish, since an npm publish is unrevocable and the pointer preflight
version check only ran after RID packages were already submitted.
- release-publish-nuget.yml: drop a duplicated '# 7.' header comment left by
the main merge.
- Tests for all of the above.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add npm launcher behavioral tests and fix version mismatch/cleanup issues
- Add AspireJsLauncherTests with real Node execution through fake npm layout
- Test version mismatch detection between pointer and RID packages
- Test temp file cleanup when chmod fails on POSIX systems
- Test happy path: caching, environment variables, and argv passthrough
- Test RID coverage against pack-cli-npm-package.ps1
- Update aspire.js to check RID package version matches pointer version
- Update aspire.js to wrap copy/chmod in try-finally for temp file cleanup
- Ensure temp files are removed even when copyFileSync or chmodSync fails
Fixes #17297 (Task 1)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden npm release pipeline validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: David Fowler <davidfowl@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Adam Ratzman <adamratzman@github.com>
Description
The VS Code extension build previously assumed that
corepack(and therefore Yarn) was already present on the build agent'sPATH. That worked on most agents because Corepack ships with Node.js, but it was brittle:1.22.22) was duplicated as an inlinecorepack yarn@1.22.22 …pin in four places (extension/build.sh,extension/build.ps1, two paths inextension/Extension.proj) plus three AzDO pipelines that bypassed Corepack entirely withnpm install -g yarn@1.22.22. Bumping Yarn meant editing seven files in lockstep and hoping you didn't miss one.registry.npmjs.orgby default; it does not read the project's.npmrc. That means even though the rest of the extension build is wired to thedotnet-public-npmmirror, the Yarn tarball itself was coming from the public registry.This PR makes the Corepack and Yarn versions explicit and centralized, routes Corepack downloads through the approved internal feed, and adds GitHub Actions coverage of the bootstrap flow so the next regression is caught on PR.
What changed
extension/package.json: added"packageManager": "yarn@1.22.22". This is the canonical Corepack hook so the Yarn version now has a single source of truth.extension/scripts/corepack-version.txt: new single source of truth for the pinned Corepack shim version.extension/build.sh,extension/build.ps1, the GitHub Actions workflow, and every AzDO pipeline read from it, so a Corepack bump is one file edit instead of seven.extension/scripts/prepareCorepackYarn.mjs: new Node script that does the dnceng-feed work Corepack itself can't do. Corepack's built-inprepare --activatealways hitsregistry.npmjs.org, so the script insteadnpm packs the pinned Yarn release through the project's.npmrc(which is wired todotnet-public-npm), unpacks it into the path Corepack expects (<COREPACK_HOME>/v1/yarn/<version>/), and atomically renames into place. Handles theEEXIST/ENOTEMPTYrename race so concurrent builds in the same worktree don't corrupt the cache.extension/build.shandextension/build.ps1: nownpm install -g corepack@<corepack-version.txt>first, thencorepack enable, thennode ./scripts/prepareCorepackYarn.mjs. Both scripts pinCOREPACK_HOMEto a build-scopedextension/.corepack-cache(gitignored) so multi-worktree setups stay isolated and a stale user-default cache doesn't break a fresh checkout.extension/Extension.proj: the fourcorepack yarn@1.22.22 …invocations are nowcorepack yarn …. Every<Exec>that invokescorepacksetsEnvironmentVariables="COREPACK_HOME=$(_CorepackHome)", so the documented recovery path (runextension/build.shonce, then./build.sh -build-extensionor directdotnet build extension/Extension.proj) works regardless of whether the parent shell propagated the env var.CheckYarnInstalledruns from$(ExtensionSrcDir)so Corepack picks up thepackageManagerfield, and depends onValidateYarnLockRegistriesso we fail fast if the lockfile drifted off the internal mirror.ValidateYarnLockRegistries(Extension.proj) +Validate lockfile registries(tests.yml): scanyarn.lockand require everyresolved "<url>"line to point atpkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm. An allowlist (not a denylist) so we catch drift to any public mirror —npmmirror.com,jsr.io, GitHub tarballs, etc. — not only the two most popular ones.azure-pipelines.yml,azure-pipelines-unofficial.yml,azure-pipelines-codeql.yml): theInstall yarnstep (which previously didnpm install -g yarn@1.22.22and bypassed Corepack) is replaced with a reference to a new shared templateeng/pipelines/templates/install-corepack.yml. The template installs the pinnedcorepack@<corepack-version.txt>, verifies the version onPATHmatches the pin (Windowscorepack.cmdshadowing is a real hazard), and runsprepareCorepackYarn.mjs. SingledisplayPrefixparameter preserves the 🟣 prefix used by the internal-1ES jobs vs. the unprefixed CodeQL job. The existingnpmAuthenticate@0step that runs immediately before this provides the credentials needed for the dnceng pull-through.COREPACK_NPM_REGISTRY(dnceng mirror) andCOREPACK_ENABLE_DOWNLOAD_PROMPT=0to botheng/pipelines/common-variables.ymlandeng/pipelines/templates/public-pipeline-template.ymlso unattended runs don't block on the "download Yarn?" prompt..github/workflows/tests.yml): added two new jobs so the bootstrap is covered on PR, not only in scheduled AzDO runs.extension_tests_win(Windows) — runsextension/build.ps1from a clean cache, thenvscode-testagainst the built extension.extension_bootstrap_linux— runsprepareCorepackYarn.mjsdirectly, validates the lockfile, thencorepack yarn install --frozen-lockfile --non-interactive. Catches Linux/macOS regressions in the bootstrap script even though the extension itself only ships from the Windows job.Both jobs derive
COREPACK_HOMEfrom$RUNNER_TEMPinside the first step (job-levelenv:can't reference therunnercontext).extension/CONTRIBUTING.md: prerequisites and dependency-override sections updated to describe the Corepack flow, plus a new "Updating the Yarn version" section pointing at thepackageManagerfield.Why pin Corepack via
npminstead of letting Node ship itCorepack ships with Node.js, but the version that ships is whatever was current when that Node release was cut. With Corepack 0.31+ enforcing signature verification using keys baked into the shim, the Corepack version is now part of our build's reproducibility surface. Pinning it via
npm install -g corepack@<version>matches how we pin every other tool in CI.There is no
package.jsonfield that controls which Corepack version is installed (only whichyarn/pnpm/npmCorepack provisions), so the version lives inextension/scripts/corepack-version.txt.Why a custom
prepareCorepackYarn.mjsinstead ofcorepack prepare --activatecorepack prepare --activateandcorepack enableboth fetch the Yarn tarball from a hardcoded list of registries that does not include our internal mirror, and Corepack does not honor.npmrc. Pre-seeding Corepack's cache bynpm pack-ing through the project's.npmrcis the only way to keep the Yarn tarball on the dnceng mirror without forking Corepack. Integrity rests on the dnceng feed's published packages — Corepack will not re-verify a pre-seeded cache on reuse.Why a separate "follow-up" comment about Yarn 4
Bumping to Yarn 4 is the natural next step now that Corepack is in place, but it's a real dependency upgrade (new lockfile format,
--frozen-lockfile→--immutable,.yarnrc→.yarnrc.yml,vsceinteraction with the chosen linker mode, possibly PnP vsnode-modules). I'm keeping that out of this PR so the build-infra fix can land on its own risk profile, and will follow up with a dedicated upgrade PR.Validation
actionlintis clean ontests.yml.ValidateYarnLockRegistriesMSBuild target smoke-tested locally against a good lockfile (passes, 0 disallowed) and a bad lockfile with a singleregistry.npmjs.orgentry (fails with the offending line in the error message).extension/andeng/pipelines/is clean — the only remaining1.22.22references are the canonicalpackageManagerfield inextension/package.jsonand the entries inextension/yarn.lock.yarn-1.22.22.tgzis already mirrored in dncengdotnet-public-npm(returns 200, 1.2 MB).corepack@<pinned>is not yet mirrored, but the existingnpmAuthenticate@0step in the AzDO pipelines provides the credentials Corepack needs to trigger the first-run pull-through. After the first successful pipeline run the package is permanently cached.Checklist
<remarks />and<code />elements on your triple slash comments?