@@ -114,18 +114,40 @@ export class WindowsProjectService
114114 ) ,
115115 platformProjectService : < any > this ,
116116 projectRoot : projectRoot ,
117- getBuildOutputPath : ( _options ?: any ) : string => {
117+ getBuildOutputPath : ( options ?: any ) : string => {
118+ // Release builds are packaged into AppPackages/ (.msix/.msixupload),
119+ // while debug builds run from the plain `dotnet build` output in bin/.
120+ if ( options ?. release ) {
121+ return path . join (
122+ projectRoot ,
123+ projectData . projectName ,
124+ "AppPackages" ,
125+ ) ;
126+ }
118127 return path . join ( projectRoot , projectData . projectName , "bin" ) ;
119128 } ,
120- getValidBuildOutputData : ( ) : IValidBuildOutputData => {
129+ getValidBuildOutputData : (
130+ options ?: any ,
131+ ) : IValidBuildOutputData => {
132+ if ( options ?. release ) {
133+ // Packaged artifacts live in versioned subfolders of AppPackages/
134+ // (e.g. App_1.0.0.0_x64_Test/App_1.0.0.0_x64.msix), so match by
135+ // extension rather than an exact file name. A representative
136+ // package name is kept so callers that read packageNames[0]
137+ // (e.g. for error messages) have a valid value.
138+ return {
139+ packageNames : [
140+ `${ projectData . projectName } .msixupload` ,
141+ `${ projectData . projectName } .msix` ,
142+ ] ,
143+ regexes : [
144+ / \. ( m s i x u p l o a d | a p p x u p l o a d | m s i x b u n d l e | a p p x b u n d l e | m s i x | a p p x ) $ / i,
145+ ] ,
146+ } ;
147+ }
148+ // AppxManifest.xml triggers Add-AppxPackage -Register (dev flow).
121149 return {
122- // AppxManifest.xml triggers Add-AppxPackage -Register (dev flow).
123- // msix/appx handle release builds.
124- packageNames : [
125- "AppxManifest.xml" ,
126- `${ projectData . projectName } .msix` ,
127- `${ projectData . projectName } .appx` ,
128- ] ,
150+ packageNames : [ "AppxManifest.xml" ] ,
129151 } ;
130152 } ,
131153 configurationFileName : "Package.appxmanifest" ,
@@ -275,19 +297,25 @@ export class WindowsProjectService
275297 projectData : IProjectData ,
276298 buildConfig : any ,
277299 ) : Promise < void > {
278- const config = buildConfig ?. release
279- ? Configurations . Release
280- : Configurations . Debug ;
300+ const isRelease = ! ! buildConfig ?. release ;
301+ const config = isRelease ? Configurations . Release : Configurations . Debug ;
281302 const arch = buildConfig ?. architectures ?. [ 0 ] ?? "x64" ;
282303 const csproj = path . join (
283304 projectRoot ,
284305 projectData . projectName ,
285306 `${ projectData . projectName } .csproj` ,
286307 ) ;
287308 const outputPath = path . join ( projectRoot , projectData . projectName , "bin" ) ;
309+ const appPackagesDir = path . join (
310+ projectRoot ,
311+ projectData . projectName ,
312+ "AppPackages" ,
313+ ) ;
288314
289315 this . $logger . info (
290- `Building Windows project: ${ csproj } [${ config } |${ arch } ]` ,
316+ `Building Windows project: ${ csproj } [${ config } |${ arch } ]${
317+ isRelease ? " (release package)" : ""
318+ } `,
291319 ) ;
292320
293321 // If the app project provides an App_Resources/Windows/Package.appxmanifest,
@@ -323,15 +351,61 @@ export class WindowsProjectService
323351 }
324352 }
325353
326- const dotnetArgs = [
327- "build" ,
328- csproj ,
329- "-c" ,
330- config ,
331- `-p:Platform=${ arch } ` ,
332- "--output" ,
333- outputPath ,
334- ] ;
354+ const dotnetArgs = [ "build" , csproj , "-c" , config , `-p:Platform=${ arch } ` ] ;
355+
356+ if ( isRelease ) {
357+ // Produce an MSIX package (sideload-signed by default) or a Microsoft
358+ // Store upload bundle when --store-upload is passed, instead of a plain
359+ // run-from-bin build.
360+ const storeUpload = ! ! buildConfig ?. storeUpload ;
361+ const bundle = ! ! buildConfig ?. msixBundle || storeUpload ;
362+ const certificate : string = buildConfig ?. certificate ;
363+ const certificatePassword : string = buildConfig ?. certificatePassword ;
364+ const certificateThumbprint : string =
365+ buildConfig ?. certificateThumbprint ;
366+
367+ dotnetArgs . push (
368+ "-p:GenerateAppxPackageOnBuild=true" ,
369+ `-p:AppxPackageDir=${ appPackagesDir } ${ path . sep } ` ,
370+ `-p:UapAppxPackageBuildMode=${
371+ storeUpload ? "StoreUpload" : "SideloadOnly"
372+ } `,
373+ `-p:AppxBundle=${ bundle ? "Always" : "Never" } ` ,
374+ ) ;
375+
376+ if ( bundle ) {
377+ dotnetArgs . push ( `-p:AppxBundlePlatforms=${ arch } ` ) ;
378+ }
379+
380+ if ( storeUpload ) {
381+ // The Store re-signs the package on submission, so don't sign locally.
382+ dotnetArgs . push ( "-p:AppxPackageSigningEnabled=false" ) ;
383+ } else if ( certificateThumbprint ) {
384+ dotnetArgs . push (
385+ "-p:AppxPackageSigningEnabled=true" ,
386+ `-p:PackageCertificateThumbprint=${ certificateThumbprint } ` ,
387+ ) ;
388+ } else if ( certificate ) {
389+ const certPath = path . isAbsolute ( certificate )
390+ ? certificate
391+ : path . join ( projectData . projectDir , certificate ) ;
392+ dotnetArgs . push (
393+ "-p:AppxPackageSigningEnabled=true" ,
394+ `-p:PackageCertificateKeyFile=\"${ certPath } \"` ,
395+ ) ;
396+ if ( certificatePassword ) {
397+ dotnetArgs . push (
398+ `-p:PackageCertificatePassword=${ certificatePassword } ` ,
399+ ) ;
400+ }
401+ } else {
402+ // No signing material provided — emit an unsigned package.
403+ dotnetArgs . push ( "-p:AppxPackageSigningEnabled=false" ) ;
404+ }
405+ } else {
406+ dotnetArgs . push ( "--output" , outputPath ) ;
407+ }
408+
335409 if ( appResourcesManifestArg ) {
336410 dotnetArgs . push ( appResourcesManifestArg ) ;
337411 }
@@ -354,14 +428,15 @@ export class WindowsProjectService
354428 }
355429
356430 // Add-AppxPackage -Register requires the manifest to be named AppxManifest.xml
357- // with $targetnametoken$ expanded to the actual EXE name.
431+ // with $targetnametoken$ expanded to the actual EXE name. This is only used by
432+ // the dev (debug) register flow; release builds produce a packaged .msix.
358433 const manifestSrc = path . join (
359434 projectRoot ,
360435 projectData . projectName ,
361436 "Package.appxmanifest" ,
362437 ) ;
363438 const manifestDest = path . join ( outputPath , "AppxManifest.xml" ) ;
364- if ( this . $fs . exists ( manifestSrc ) ) {
439+ if ( ! isRelease && this . $fs . exists ( manifestSrc ) ) {
365440 const raw = fs . readFileSync ( manifestSrc , "utf8" ) ;
366441 const expanded = raw . split ( "$targetnametoken$" ) . join ( projectData . projectName ) ;
367442 fs . writeFileSync ( manifestDest , expanded , "utf8" ) ;
0 commit comments