Skip to content

Commit a22271a

Browse files
adamintCopilot
andcommitted
[release/13.4] Add Aspire CLI npm package release integration
Backport of #17297 to release/13.4. Adds npm packaging and release-pipeline publishing for the Aspire CLI: pack/sign/verify of the @microsoft/aspire-cli pointer package and its seven RID packages, npm install validation steps, npm publish + registry validation stages in release-publish-nuget.yml, npm pipeline variables, and CLI npm-install detection/update messaging. The docs/release-process.md changes from the source PR are intentionally omitted: on main they are interleaved with VS Code extension / Marketplace release documentation that does not apply to release/13.4 (the VS Code extension is not released from this branch). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e138509 commit a22271a

42 files changed

Lines changed: 4797 additions & 70 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-cli-native-archives.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ jobs:
5353
- name: Checkout code
5454
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5555

56+
# Node.js is required because PackDotnetTool now depends on PackNpmPackage,
57+
# which shells out to `npm pack` to produce the @microsoft/aspire-cli npm
58+
# tarballs alongside the existing dotnet tool packages. GitHub-hosted
59+
# runners ship Node by default, but installing it explicitly pins the
60+
# version and avoids relying on the runner image.
61+
- name: Install node.js
62+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
63+
with:
64+
node-version: 22.x
65+
5666
# Build RID-specific NuGet packages (DCP + Dashboard) locally instead of
5767
# downloading them from the build_packages job. This allows the CLI archive
5868
# build to run in parallel with build_packages.
@@ -121,15 +131,33 @@ jobs:
121131
-Rid $rid `
122132
-ArchivePath $archive[0].FullName
123133
124-
# Upload DCP, Dashboard, and CLI tool NuGets so test/polyglot jobs can download them
125-
- name: Upload RID-specific NuGets
134+
- name: Verify CLI npm package
135+
shell: pwsh
136+
run: |
137+
$ErrorActionPreference = 'Stop'
138+
139+
$rid = '${{ matrix.targets.rids }}'
140+
$archiveExtension = if ($rid.StartsWith('win-')) { 'zip' } else { 'tar.gz' }
141+
$archive = @(Get-ChildItem -Path 'artifacts/packages/${{ inputs.configuration }}' -Filter "aspire-cli-$rid-*.$archiveExtension" -Recurse -File -ErrorAction SilentlyContinue)
142+
if ($archive.Count -ne 1) {
143+
throw "Expected exactly one CLI archive for $rid, but found $($archive.Count): $($archive.FullName -join ', ')"
144+
}
145+
146+
eng/scripts/verify-cli-npm-package.ps1 `
147+
-PackagesDir artifacts/packages/${{ inputs.configuration }} `
148+
-Rid $rid `
149+
-ArchivePath $archive[0].FullName
150+
151+
# Upload DCP, Dashboard, and CLI tool packages so test/polyglot jobs can download them
152+
- name: Upload RID-specific packages
126153
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
127154
with:
128155
name: built-nugets-for-${{ matrix.targets.rids }}
129156
path: |
130157
artifacts/packages/${{ inputs.configuration }}/Shipping/Aspire.Hosting.Orchestration.${{ matrix.targets.rids }}.*.nupkg
131158
artifacts/packages/${{ inputs.configuration }}/Shipping/Aspire.Dashboard.Sdk.${{ matrix.targets.rids }}.*.nupkg
132159
artifacts/packages/${{ inputs.configuration }}/Shipping/Aspire.Cli.*.nupkg
160+
artifacts/packages/${{ inputs.configuration }}/Shipping/microsoft-aspire-cli*.tgz
133161
if-no-files-found: error
134162
retention-days: 15
135163

docs/specs/npm-cli-package.md

Lines changed: 212 additions & 0 deletions
Large diffs are not rendered by default.

eng/Publishing.props

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
<_ExtensionSignatureFiles Include="$(ArtifactsPackagesDir)**\aspire-vscode-*.signature.p7s" />
4141
<_CliToolPackagesToPublish Include="$(ArtifactsShippingPackagesDir)**\Aspire.Cli*.nupkg"
4242
Exclude="$(ArtifactsShippingPackagesDir)**\*.symbols.nupkg" />
43+
<_CliNpmPackagesToPublish Include="$(ArtifactsShippingPackagesDir)**\microsoft-aspire-cli*.tgz" />
44+
<_CliNpmPackageSignaturesToPublish Include="$(ArtifactsShippingPackagesDir)**\microsoft-aspire-cli*.tgz.sig" />
4345
</ItemGroup>
4446

4547
<!-- Validate VS Code extension artifacts: we expect exactly 1 VSIX and 1 manifest file.
@@ -87,11 +89,28 @@
8789
<_CliPointerToolPackages Include="@(_CliToolPackagesToPublish)" Exclude="@(_CliRidSpecificToolPackageFilesWithRid)" />
8890
</ItemGroup>
8991

92+
<ItemGroup Label="Validation of CLI npm packages">
93+
<_CliRidSpecificNpmPackageFiles Include="@(_CliNpmPackagesToPublish)"
94+
Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch('%(_CliNpmPackagesToPublish.FileName)', '^microsoft-aspire-cli-\d'))" />
95+
96+
<!-- Extract the rid from filenames like microsoft-aspire-cli-linux-x64-9.0-dev and microsoft-aspire-cli-linux-arm64-9.4.0-preview.1.25358.11 -->
97+
<_CliRidSpecificNpmPackageFilesWithRid Include="@(_CliRidSpecificNpmPackageFiles)">
98+
<ExtractedRid>$([System.Text.RegularExpressions.Regex]::Match('%(_CliRidSpecificNpmPackageFiles.FileName)', '^microsoft-aspire-cli-(.*?)-\d+.*').Groups[1].Value)</ExtractedRid>
99+
</_CliRidSpecificNpmPackageFilesWithRid>
100+
101+
<_MissingCliNpmPackageRids Include="@(_ExpectedCliRids)" Exclude="@(_CliRidSpecificNpmPackageFilesWithRid -> '%(ExtractedRid)')" />
102+
<_UnexpectedCliNpmPackageRids Include="@(_CliRidSpecificNpmPackageFilesWithRid -> '%(ExtractedRid)')" Exclude="@(_ExpectedCliRids)" />
103+
<_CliPointerNpmPackages Include="@(_CliNpmPackagesToPublish)" Exclude="@(_CliRidSpecificNpmPackageFilesWithRid)" />
104+
</ItemGroup>
105+
90106
<Warning Condition="@(_UnexpectedArchiveRids->Count()) > 0" Text="Found unexpected CLI archives for @(_UnexpectedArchiveRids, ',') . These are all the cli archives found - @(_ArchiveFiles, ', ')" />
91107
<Warning Condition="@(_UnexpectedCliPackageRids->Count()) > 0" Text="Found unexpected Aspire.Cli RID-specific packages for @(_UnexpectedCliPackageRids, ',') . These are all the CLI tool packages found - @(_CliToolPackagesToPublish, ', ')" />
108+
<Warning Condition="@(_UnexpectedCliNpmPackageRids->Count()) > 0" Text="Found unexpected Aspire CLI npm RID-specific packages for @(_UnexpectedCliNpmPackageRids, ',') . These are all the CLI npm packages found - @(_CliNpmPackagesToPublish, ', ')" />
92109
<Error Condition="@(_MissingArchiveRids->Count()) > 0" Text="Missing CLI archive(s) for runtime identifiers: @(_MissingArchiveRids, ', '). These are all the cli archives found - @(_ArchiveFiles, ', ')" />
93110
<Error Condition="@(_MissingCliPackageRids->Count()) > 0" Text="Missing Aspire.Cli RID-specific package(s) for runtime identifiers: @(_MissingCliPackageRids, ', '). These are all the CLI tool packages found - @(_CliToolPackagesToPublish, ', ')" />
111+
<Error Condition="@(_MissingCliNpmPackageRids->Count()) > 0" Text="Missing Aspire CLI npm RID-specific package(s) for runtime identifiers: @(_MissingCliNpmPackageRids, ', '). These are all the CLI npm packages found - @(_CliNpmPackagesToPublish, ', ')" />
94112
<Error Condition="@(_CliPointerToolPackages->Count()) != 1" Text="Expected exactly one Aspire.Cli pointer package but found @(_CliPointerToolPackages->Count()). Files: @(_CliPointerToolPackages, ', ')" />
113+
<Error Condition="@(_CliPointerNpmPackages->Count()) != 1" Text="Expected exactly one Aspire CLI npm pointer package but found @(_CliPointerNpmPackages->Count()). Files: @(_CliPointerNpmPackages, ', ')" />
95114

96115
<!-- Validate aspire-cli archive file extensions match expected format for each RID -->
97116
<ItemGroup>
@@ -177,6 +196,16 @@
177196
<IsShipping>true</IsShipping>
178197
<Kind>Package</Kind>
179198
</ItemsToPushToBlobFeed>
199+
<ItemsToPushToBlobFeed Include="@(_CliNpmPackagesToPublish)">
200+
<IsShipping>true</IsShipping>
201+
<PublishFlatContainer>true</PublishFlatContainer>
202+
<RelativeBlobPath>$(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension)</RelativeBlobPath>
203+
</ItemsToPushToBlobFeed>
204+
<ItemsToPushToBlobFeed Include="@(_CliNpmPackageSignaturesToPublish)">
205+
<IsShipping>true</IsShipping>
206+
<PublishFlatContainer>true</PublishFlatContainer>
207+
<RelativeBlobPath>$(_UploadPathRoot)/$(_PackageVersion)/%(Filename)%(Extension)</RelativeBlobPath>
208+
</ItemsToPushToBlobFeed>
180209
<ItemsToPushToBlobFeed Include="@(_ExtensionFilesToPublish)">
181210
<IsShipping>false</IsShipping>
182211
<PublishFlatContainer>true</PublishFlatContainer>
@@ -211,6 +240,8 @@
211240
<Message Importance="High" Text="Aspire publish items to push: @(ItemsToPushToBlobFeed->Count()) total; @(_PackageItemsToPush->Count()) NuGet package(s); @(_FlatBlobItemsToPush->Count()) flat blob artifact(s)." />
212241
<Message Importance="High" Text="Aspire NuGet packages: @(_PackageItemsToPush->Count()) total; @(_ShippingPackageItemsToPush->Count()) shipping." Condition="@(_PackageItemsToPush->Count()) > 0" />
213242
<Message Importance="High" Text="Aspire CLI packages: @(_CliPointerToolPackages->Count()) pointer; @(_CliRidSpecificToolPackageFilesWithRid->Count())/@(_ExpectedCliRids->Count()) RID-specific; RIDs=@(_CliRidSpecificToolPackageFilesWithRid -> '%(ExtractedRid)', ', ')." Condition="@(_CliToolPackagesToPublish->Count()) > 0" />
243+
<Message Importance="High" Text="Aspire CLI npm packages: @(_CliPointerNpmPackages->Count()) pointer; @(_CliRidSpecificNpmPackageFilesWithRid->Count())/@(_ExpectedCliRids->Count()) RID-specific; RIDs=@(_CliRidSpecificNpmPackageFilesWithRid -> '%(ExtractedRid)', ', ')." Condition="@(_CliNpmPackagesToPublish->Count()) > 0" />
244+
<Message Importance="High" Text="Aspire CLI npm detached signatures: @(_CliNpmPackageSignaturesToPublish->Count()) sidecar(s)." Condition="@(_CliNpmPackageSignaturesToPublish->Count()) > 0" />
214245
<Message Importance="High" Text="Aspire flat blob artifacts: @(_FlatBlobItemsToPush->Count()) total; @(_ShippingFlatBlobItemsToPush->Count()) shipping; @(_NonShippingFlatBlobItemsToPush->Count()) non-shipping." Condition="@(_FlatBlobItemsToPush->Count()) > 0" />
215246
<Message Importance="High"
216247
Text="Aspire flat blob item: %(_FlatBlobItemsToPush.Filename)%(_FlatBlobItemsToPush.Extension) | IsShipping=%(_FlatBlobItemsToPush.IsShipping) | RelativeBlobPath=%(_FlatBlobItemsToPush.RelativeBlobPath)" />

eng/Signing.props

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@
6666

6767
<!-- Sign the manifest catalog on Windows -->
6868
<FileSignInfo Condition="$([System.OperatingSystem]::IsWindows())" Include="manifest.cat" CertificateName="MicrosoftDotNet500" />
69+
70+
<!--
71+
npm packages are tarballs, but only the Aspire CLI npm tarballs should get
72+
detached signatures. Scope both the .tgz rule and the nested JavaScript
73+
launcher override to the npm package ItemsToSign collision id so the global
74+
.tgz/.js defaults remain unchanged for unrelated artifacts.
75+
-->
76+
<FileExtensionSignInfo Include=".tgz" CertificateName="LinuxSign500180PGP" CollisionPriorityId="AspireCliNpmPackage" />
77+
<FileSignInfo Include="aspire.js" CertificateName="MicrosoftDotNet500" CollisionPriorityId="AspireCliNpmPackage" />
6978
</ItemGroup>
7079

7180
<ItemGroup>
@@ -75,6 +84,7 @@
7584
<ItemsToSign Include="$(VisualStudioSetupInsertionPath)\**\*.zip" Condition="'$(PostBuildSign)' != 'true'" />
7685
<ItemsToSign Include="$(ArtifactsPackagesDir)**\aspire-cli-*.zip" />
7786
<ItemsToSign Include="$(ArtifactsPackagesDir)**\aspire-cli-*.tar.gz" />
87+
<ItemsToSign Include="$(ArtifactsShippingPackagesDir)**\microsoft-aspire-cli*.tgz" CollisionPriorityId="AspireCliNpmPackage" />
7888
<ItemsToSign Include="$(RepoRoot)eng\scripts\get-aspire-cli.ps1" Condition="$([System.OperatingSystem]::IsWindows())" />
7989

8090
<!-- Sign only the final NativeAOT CLI publish output; CLI archives are signed separately. -->

eng/clipack/Common.projitems

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@
165165
-->
166166

167167
<Target Name="PackDotnetTool"
168-
DependsOnTargets="_InitializeDotnetToolPackProperties;_ExtractNativeBinaryFromArchive;_PackRidSpecificDotnetTool;_PackPointerDotnetTool" />
168+
DependsOnTargets="_InitializeDotnetToolPackProperties;_ExtractNativeBinaryFromArchive;_PackRidSpecificDotnetTool;_PackPointerDotnetTool;PackNpmPackage" />
169169

170170
<Target Name="_InitializeDotnetToolPackProperties">
171171
<Error Condition="'$(CliRuntime)' == ''"
@@ -183,6 +183,10 @@
183183
<_CliToolPublishBaseDir>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'tool-publish'))</_CliToolPublishBaseDir>
184184
<_CliToolPublishDir>$([MSBuild]::NormalizeDirectory($(_CliToolPublishBaseDir), '$(CliRuntime)'))</_CliToolPublishDir>
185185
<_CliToolPointerPublishDir>$([MSBuild]::NormalizeDirectory($(_CliToolPublishBaseDir), 'pointer'))</_CliToolPointerPublishDir>
186+
<_CliNpmPackageStagingDir>$([MSBuild]::NormalizeDirectory($(IntermediateOutputPath), 'npm-packages', '$(CliRuntime)'))</_CliNpmPackageStagingDir>
187+
<_CliNpmPackageStagingDirArg>$(_CliNpmPackageStagingDir.TrimEnd('\').TrimEnd('/'))</_CliNpmPackageStagingDirArg>
188+
<_PackageOutputPathArg>$(PackageOutputPath.TrimEnd('\').TrimEnd('/'))</_PackageOutputPathArg>
189+
<NpmCliPackageName Condition="'$(NpmCliPackageName)' == ''">@microsoft/aspire-cli</NpmCliPackageName>
186190
</PropertyGroup>
187191

188192
<ItemGroup>
@@ -258,4 +262,19 @@
258262
RemoveProperties="OutputPath;TargetFramework" />
259263
</Target>
260264

265+
<Target Name="PackNpmPackage"
266+
DependsOnTargets="_InitializeDotnetToolPackProperties;_ExtractNativeBinaryFromArchive">
267+
<Message Importance="High"
268+
Text="Packing CLI npm packages for $(CliRuntime) from '$(_ExtractedNativeBinaryPath)' extracted from '$(_CliArchivePath)'." />
269+
270+
<PropertyGroup>
271+
<!-- Build the script path with MSBuild path helpers so it uses the correct separator
272+
on every platform. The native CLI archives are built on Windows, Linux, and macOS,
273+
and pwsh on non-Windows does not silently normalize mixed-separator paths. -->
274+
<_PackCliNpmPackageScript>$([MSBuild]::NormalizePath($(RepoRoot), 'eng', 'scripts', 'pack-cli-npm-package.ps1'))</_PackCliNpmPackageScript>
275+
</PropertyGroup>
276+
277+
<Exec Command="pwsh -NoProfile -NonInteractive -File &quot;$(_PackCliNpmPackageScript)&quot; -Rid &quot;$(CliRuntime)&quot; -Version &quot;$(Version)&quot; -NativeBinaryPath &quot;$(_ExtractedNativeBinaryPath)&quot; -OutputPath &quot;$(_PackageOutputPathArg)&quot; -StagingRoot &quot;$(_CliNpmPackageStagingDirArg)&quot; -PackageName &quot;$(NpmCliPackageName)&quot;" />
278+
</Target>
279+
261280
</Project>

0 commit comments

Comments
 (0)