mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-01 07:59:36 +02:00
Compare commits
3 Commits
dependabot
...
user/yeela
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d977ae4f8e | ||
|
|
e3eb69b6c5 | ||
|
|
c790d39af7 |
@@ -1 +0,0 @@
|
||||
../.github/copilot-instructions.md
|
||||
@@ -1 +0,0 @@
|
||||
../.github/agents
|
||||
@@ -1 +0,0 @@
|
||||
../.github/prompts
|
||||
@@ -1 +0,0 @@
|
||||
../.github/instructions
|
||||
@@ -1 +0,0 @@
|
||||
../.github/skills
|
||||
@@ -1,5 +1,5 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.Windows.Settings/WindowsSettings
|
||||
@@ -13,11 +13,11 @@ properties:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2026 Enterprise (Any edition will work)
|
||||
description: Install Visual Studio 2022 Enterprise (Any edition will work)
|
||||
# Requires elevation for the set operation
|
||||
securityContext: elevated
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.Enterprise
|
||||
id: Microsoft.VisualStudio.2022.Enterprise
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
@@ -29,7 +29,7 @@ properties:
|
||||
securityContext: elevated
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.Enterprise
|
||||
channelId: VisualStudio.18.Release
|
||||
channelId: VisualStudio.17.Release
|
||||
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.Windows.Settings/WindowsSettings
|
||||
@@ -13,11 +13,11 @@ properties:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2026 Professional (Any edition will work)
|
||||
description: Install Visual Studio 2022 Professional (Any edition will work)
|
||||
# Requires elevation for the set operation
|
||||
securityContext: elevated
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.Professional
|
||||
id: Microsoft.VisualStudio.2022.Professional
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
@@ -29,7 +29,7 @@ properties:
|
||||
securityContext: elevated
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.Professional
|
||||
channelId: VisualStudio.18.Release
|
||||
channelId: VisualStudio.17.Release
|
||||
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#getting-started
|
||||
# Reference: https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md#compiling-powertoys
|
||||
properties:
|
||||
resources:
|
||||
- resource: Microsoft.Windows.Settings/WindowsSettings
|
||||
@@ -13,11 +13,11 @@ properties:
|
||||
- resource: Microsoft.WinGet.DSC/WinGetPackage
|
||||
id: vsPackage
|
||||
directives:
|
||||
description: Install Visual Studio 2026 Community (Any edition will work)
|
||||
description: Install Visual Studio 2022 Community (Any edition will work)
|
||||
# Requires elevation for the set operation
|
||||
securityContext: elevated
|
||||
settings:
|
||||
id: Microsoft.VisualStudio.Community
|
||||
id: Microsoft.VisualStudio.2022.Community
|
||||
source: winget
|
||||
- resource: Microsoft.VisualStudio.DSC/VSComponents
|
||||
dependsOn:
|
||||
@@ -29,7 +29,7 @@ properties:
|
||||
securityContext: elevated
|
||||
settings:
|
||||
productId: Microsoft.VisualStudio.Product.Community
|
||||
channelId: VisualStudio.18.Release
|
||||
channelId: VisualStudio.17.Release
|
||||
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'
|
||||
configurationVersion: 0.2.0
|
||||
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -40,6 +40,7 @@ body:
|
||||
- Other (please specify in "Steps to Reproduce")
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Area(s) with issue?
|
||||
@@ -57,7 +58,6 @@ body:
|
||||
- Environment Variables
|
||||
- FancyZones
|
||||
- FancyZones Editor
|
||||
- Grab And Move
|
||||
- File Locksmith
|
||||
- "File Explorer: Preview Pane"
|
||||
- "File Explorer: Thumbnail preview"
|
||||
@@ -70,7 +70,6 @@ body:
|
||||
- Mouse Without Borders
|
||||
- New+
|
||||
- Peek
|
||||
- Power Display
|
||||
- PowerRename
|
||||
- PowerToys Run
|
||||
- Quick Accent
|
||||
@@ -107,13 +106,7 @@ body:
|
||||
placeholder: What happened instead?
|
||||
validations:
|
||||
required: false
|
||||
- type: upload
|
||||
id: bugreportfile
|
||||
attributes:
|
||||
label: Upload Bug Report ZIP-file
|
||||
description: Right-clicking the PowerToys tray icon in the taskbar and selecting “Report bug” generates a ZIP file containing diagnostic information about your setup and PowerToys logs, helping us better understand and troubleshoot the issue.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- id: additionalInfo
|
||||
type: textarea
|
||||
attributes:
|
||||
|
||||
85
.github/actions/spell-check/allow/code.txt
vendored
85
.github/actions/spell-check/allow/code.txt
vendored
@@ -1,7 +1,6 @@
|
||||
# COLORS
|
||||
|
||||
argb
|
||||
Bgr
|
||||
bgra
|
||||
BLACKONWHITE
|
||||
BLUEGRAY
|
||||
@@ -19,7 +18,6 @@ OLIVEGREEN
|
||||
PALEBLUE
|
||||
PArgb
|
||||
Pbgra
|
||||
SRGBTo
|
||||
WHITEONBLACK
|
||||
|
||||
|
||||
@@ -30,7 +28,6 @@ RUS
|
||||
|
||||
AYUV
|
||||
bak
|
||||
HDP
|
||||
Bcl
|
||||
bgcode
|
||||
Deflatealgorithm
|
||||
@@ -41,7 +38,6 @@ Gbps
|
||||
gcode
|
||||
Heatshrink
|
||||
Mbits
|
||||
Kbits
|
||||
MBs
|
||||
mkv
|
||||
msix
|
||||
@@ -101,7 +97,6 @@ Yubico
|
||||
Perplexity
|
||||
Groq
|
||||
svgl
|
||||
devhome
|
||||
|
||||
# KEYS
|
||||
|
||||
@@ -127,7 +122,6 @@ HOLDSPACE
|
||||
HOLDBACKSPACE
|
||||
IDIGNORE
|
||||
KBDLLHOOKSTRUCT
|
||||
keydowns
|
||||
keyevent
|
||||
LAlt
|
||||
LBUTTON
|
||||
@@ -186,12 +180,6 @@ xmlutil
|
||||
# Prefix
|
||||
pcs
|
||||
|
||||
# EXPRTK / C++ MATH
|
||||
|
||||
ifunction
|
||||
isinf
|
||||
isnan
|
||||
|
||||
# User32.SYSTEM_METRICS_INDEX.cs
|
||||
|
||||
CLEANBOOT
|
||||
@@ -307,8 +295,6 @@ pwa
|
||||
|
||||
AOT
|
||||
Aot
|
||||
ify
|
||||
TFM
|
||||
|
||||
# YML
|
||||
onefuzz
|
||||
@@ -316,7 +302,6 @@ onefuzz
|
||||
# NameInCode
|
||||
leilzh
|
||||
mengyuanchen
|
||||
contoso
|
||||
|
||||
# DllName
|
||||
testhost
|
||||
@@ -328,7 +313,6 @@ xef
|
||||
xes
|
||||
PACKAGEVERSIONNUMBER
|
||||
APPXMANIFESTVERSION
|
||||
PROGMAN
|
||||
|
||||
# MRU lists
|
||||
CACHEWRITE
|
||||
@@ -337,53 +321,7 @@ MRUINFO
|
||||
REGSTR
|
||||
|
||||
# Misc Win32 APIs and PInvokes
|
||||
DEFAULTTONEAREST
|
||||
INVOKEIDLIST
|
||||
LCMAP
|
||||
MEMORYSTATUSEX
|
||||
ABE
|
||||
Mdt
|
||||
HTCAPTION
|
||||
POSCHANGED
|
||||
QPC
|
||||
QUERYPOS
|
||||
SETAUTOHIDEBAR
|
||||
ULW
|
||||
WINDOWPOS
|
||||
WINEVENTPROC
|
||||
WORKERW
|
||||
FULLSCREENAPP
|
||||
ACLO
|
||||
CACLI
|
||||
DOENVSUBST
|
||||
FILESYSONLY
|
||||
URLIS
|
||||
WAITTIMEOUT
|
||||
DEFAULTTONEAREST
|
||||
|
||||
|
||||
# COM/WinRT interface prefixes and type fragments
|
||||
BAlt
|
||||
BShift
|
||||
Cmanifest
|
||||
Cmodule
|
||||
Cuuid
|
||||
Dng
|
||||
IApplication
|
||||
IDisposable
|
||||
IEnum
|
||||
IFolder
|
||||
IInitialize
|
||||
IMemory
|
||||
IOle
|
||||
ipreview
|
||||
IProperty
|
||||
IShell
|
||||
ithumbnail
|
||||
IVirtual
|
||||
|
||||
# Test frameworks
|
||||
MSTEST
|
||||
|
||||
# PowerRename metadata pattern abbreviations (used in tests and regex patterns)
|
||||
DDDD
|
||||
@@ -392,15 +330,6 @@ HHH
|
||||
riday
|
||||
YYY
|
||||
|
||||
# Unicode
|
||||
precomposed
|
||||
|
||||
# names of characters
|
||||
zwsp
|
||||
|
||||
# mermaid
|
||||
autonumber
|
||||
|
||||
# GitHub issue/PR commands
|
||||
azp
|
||||
feedbackhub
|
||||
@@ -410,17 +339,3 @@ reportbug
|
||||
#ffmpeg
|
||||
crf
|
||||
nostdin
|
||||
|
||||
# Performance counter keys
|
||||
engtype
|
||||
Nonpaged
|
||||
|
||||
# XAML
|
||||
Untargeted
|
||||
|
||||
# Program names
|
||||
SEARCHHOST
|
||||
SHELLEXPERIENCEHOST
|
||||
SHELLHOST
|
||||
STARTMENUEXPERIENCEHOST
|
||||
WIDGETBOARD
|
||||
|
||||
8
.github/actions/spell-check/allow/names.txt
vendored
8
.github/actions/spell-check/allow/names.txt
vendored
@@ -178,9 +178,7 @@ Taras
|
||||
TBM
|
||||
Teutsch
|
||||
tilovell
|
||||
traies
|
||||
Triet
|
||||
udit
|
||||
urnotdfs
|
||||
vednig
|
||||
waaverecords
|
||||
@@ -194,7 +192,6 @@ ycv
|
||||
yeelam
|
||||
Yuniardi
|
||||
yuyoyuppe
|
||||
zadjii
|
||||
Zeol
|
||||
Zhao
|
||||
Zhaopeng
|
||||
@@ -209,8 +206,6 @@ Bilibili
|
||||
BVID
|
||||
capturevideosample
|
||||
cmdow
|
||||
contoso
|
||||
Contoso
|
||||
Controlz
|
||||
cortana
|
||||
devhints
|
||||
@@ -226,7 +221,6 @@ Moq
|
||||
mozilla
|
||||
mspaint
|
||||
Newtonsoft
|
||||
NVIDIA
|
||||
onenote
|
||||
openai
|
||||
Quickime
|
||||
@@ -234,7 +228,6 @@ regedit
|
||||
roslyn
|
||||
Skia
|
||||
Spotify
|
||||
taskmgr
|
||||
tldr
|
||||
Vanara
|
||||
wangyi
|
||||
@@ -250,3 +243,4 @@ xamlstyler
|
||||
Xavalon
|
||||
Xbox
|
||||
Youdao
|
||||
zadjii
|
||||
|
||||
212
.github/actions/spell-check/allow/zoomit.txt
vendored
212
.github/actions/spell-check/allow/zoomit.txt
vendored
@@ -1,212 +0,0 @@
|
||||
accelscroll
|
||||
acq
|
||||
adr
|
||||
Adr
|
||||
APPLYTOSUBMENUS
|
||||
AUDCLNT
|
||||
axisdefer
|
||||
axisflip
|
||||
axisstart
|
||||
BGRX
|
||||
bitmaps
|
||||
blits
|
||||
BREAKSCR
|
||||
BUFFERFLAGS
|
||||
Cands
|
||||
capturepath
|
||||
centiseconds
|
||||
CLASSW
|
||||
coeffs
|
||||
coprime
|
||||
CREATEDIBSECTION
|
||||
CREATESTRUCTW
|
||||
crossfades
|
||||
Ctl
|
||||
CTLCOLOR
|
||||
CTLCOLORBTN
|
||||
CTLCOLORDLG
|
||||
CTLCOLOREDIT
|
||||
CTLCOLORLISTBOX
|
||||
CTrim
|
||||
DBuffer
|
||||
ddx
|
||||
ddy
|
||||
DEVSOURCE
|
||||
DFCS
|
||||
dlg
|
||||
dlu
|
||||
DONTCARE
|
||||
downsample
|
||||
DRAWITEM
|
||||
DRAWITEMSTRUCT
|
||||
droppedband
|
||||
Droppedband
|
||||
dsum
|
||||
dupburst
|
||||
dupsegments
|
||||
DWLP
|
||||
EDITCONTROL
|
||||
ENABLEHOOK
|
||||
ENDOFSTREAM
|
||||
expectedlock
|
||||
fabsf
|
||||
fastscroll
|
||||
FDE
|
||||
GETCHANNELRECT
|
||||
GETCHECK
|
||||
GETCOUNT
|
||||
GETSCREENSAVEACTIVE
|
||||
GETSCREENSAVETIMEOUT
|
||||
GETTHUMBRECT
|
||||
GIFs
|
||||
hcfdark
|
||||
hcfwhitespace
|
||||
HTBOTTOMRIGHT
|
||||
HTHEME
|
||||
htol
|
||||
ICONINFORMATION
|
||||
ICONWARNING
|
||||
igc
|
||||
Inj
|
||||
jumprecover
|
||||
KSDATAFORMAT
|
||||
latestcapture
|
||||
ldx
|
||||
LEFTNOWORDWRAP
|
||||
legitjumps
|
||||
letterbox
|
||||
lld
|
||||
llu
|
||||
llums
|
||||
logfont
|
||||
lookback
|
||||
lround
|
||||
lte
|
||||
luma
|
||||
Luma
|
||||
manualdrop
|
||||
maskcache
|
||||
maxstep
|
||||
MENUINFO
|
||||
MFSTARTUP
|
||||
mfxhw
|
||||
mic
|
||||
middledrop
|
||||
MJPEG
|
||||
MMRESULT
|
||||
momentumreversal
|
||||
mrate
|
||||
mrt
|
||||
narrowstrip
|
||||
ncapture
|
||||
ncm
|
||||
nduplicates
|
||||
niterations
|
||||
nmonitor
|
||||
NONCLIENTMETRICS
|
||||
NONOTIFY
|
||||
nonvle
|
||||
nredraw
|
||||
nstop
|
||||
nsubpixel
|
||||
ntorn
|
||||
nvw
|
||||
osc
|
||||
OWNERDRAW
|
||||
PBGRA
|
||||
periodictrap
|
||||
pillarbox
|
||||
pfdc
|
||||
playhead
|
||||
pointerreuse
|
||||
PSWA
|
||||
pwfx
|
||||
qpc
|
||||
Qpc
|
||||
quantums
|
||||
RCZOOMITSCR
|
||||
readback
|
||||
READERF
|
||||
realcapture
|
||||
REFKNOWNFOLDERID
|
||||
reposted
|
||||
RETURNCMD
|
||||
SCREENSAVE
|
||||
SCRNSAVE
|
||||
SCRNSAVECONFIGURE
|
||||
scrnsavw
|
||||
Scrnsavw
|
||||
scrollramp
|
||||
SCROLLSIZEGRIP
|
||||
selftest
|
||||
SETBARCOLOR
|
||||
SETBKCOLOR
|
||||
SETDEFID
|
||||
SETRECT
|
||||
SETSCREENSAVETIMEOUT
|
||||
SHAREMODE
|
||||
SHAREVIOLATION
|
||||
shortlist
|
||||
slowthenfast
|
||||
smallstart
|
||||
SNIPOCR
|
||||
sqrtf
|
||||
ssi
|
||||
startuprecovery
|
||||
stf
|
||||
stopafter
|
||||
STREAMFLAGS
|
||||
submix
|
||||
sxx
|
||||
sxy
|
||||
synthesising
|
||||
syy
|
||||
tallportal
|
||||
tci
|
||||
tcsicmp
|
||||
TEXTMETRIC
|
||||
tinystep
|
||||
tme
|
||||
toolbars
|
||||
TRACKMOUSEEVENT
|
||||
Unadvise
|
||||
vaddq
|
||||
vaddvq
|
||||
vandq
|
||||
vcgeq
|
||||
vdup
|
||||
VIDCAP
|
||||
vld
|
||||
vle
|
||||
Vle
|
||||
VLE
|
||||
vminq
|
||||
vmlal
|
||||
vmull
|
||||
vqaddq
|
||||
vshrn
|
||||
vsntprintf
|
||||
vsnwprintf
|
||||
vsync
|
||||
WASAPI
|
||||
WAVEFORMATEX
|
||||
WAVEFORMATEXTENSIBLE
|
||||
webcam
|
||||
Webcam
|
||||
webcams
|
||||
wfopen
|
||||
wideportal
|
||||
wil
|
||||
WMU
|
||||
wrapjump
|
||||
wtol
|
||||
WTSSESSION
|
||||
WTSUn
|
||||
XEnd
|
||||
XStart
|
||||
XStep
|
||||
YInternal
|
||||
ZMBS
|
||||
zncc
|
||||
Zncc
|
||||
ZNCC
|
||||
73
.github/actions/spell-check/candidate.patterns
vendored
73
.github/actions/spell-check/candidate.patterns
vendored
@@ -1,3 +1,6 @@
|
||||
# D2D
|
||||
#D?2D
|
||||
|
||||
# Repeated letters
|
||||
\b([a-z])\g{-1}{2,}\b
|
||||
|
||||
@@ -11,7 +14,7 @@
|
||||
^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b
|
||||
|
||||
# copyright
|
||||
Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
|
||||
Copyright (?:\([Cc]\)|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
|
||||
|
||||
# patch hunk comments
|
||||
^@@ -\d+(?:,\d+|) \+\d+(?:,\d+|) @@ .*
|
||||
@@ -19,10 +22,10 @@ Copyright (?:\([Cc]\)|©|)(?:[-\d, ]|and)+(?: [A-Z][a-z]+ [A-Z][a-z]+,?)+
|
||||
index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40}
|
||||
|
||||
# file permissions
|
||||
(?:^|['"`\s])(?!-+\s)[-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
|
||||
['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s]
|
||||
|
||||
# css fonts
|
||||
\bfont(?:-family(?:[-\w+]*)|):[^;}]+
|
||||
\bfont(?:-family|):[^;}]+
|
||||
|
||||
# css url wrappings
|
||||
\burl\([^)]+\)
|
||||
@@ -87,9 +90,6 @@ arn:aws:[-/:\w]+
|
||||
# AWS VPC
|
||||
vpc-\w+
|
||||
|
||||
# Azure AD
|
||||
\baad\.\w{48}\b
|
||||
|
||||
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
|
||||
# YouTube url
|
||||
\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]*
|
||||
@@ -171,7 +171,7 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+.
|
||||
GHSA(?:-[0-9a-z]{4}){3}
|
||||
|
||||
# GitHub actions
|
||||
\buses:\s+(['"]?)[-\w.]+/[-\w./]+@[-\w.]+\g{-1}
|
||||
\buses:\s+[-\w.]+/[-\w./]+@[-\w.]+
|
||||
|
||||
# GitLab commit
|
||||
\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b
|
||||
@@ -240,7 +240,7 @@ accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]*
|
||||
\bmedium\.com/@?[^/\s"]+/[-\w]+
|
||||
|
||||
# microsoft
|
||||
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%?#]*
|
||||
\b(?:https?://|)(?:(?:(?:blogs|download\.visualstudio|docs|msdn2?|research)\.|)microsoft|blogs\.msdn)\.co(?:m|\.\w\w)/[-_a-zA-Z0-9()=./%]*
|
||||
# powerbi
|
||||
\bapp\.powerbi\.com/reportEmbed/[^"' ]*
|
||||
# vs devops
|
||||
@@ -414,7 +414,7 @@ ipfs://[0-9a-zA-Z]{3,}
|
||||
\bgetopts\s+(?:"[^"]+"|'[^']+')
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[(?:\d+(?:;\d+)*|)m
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
|
||||
|
||||
# URL escaped characters
|
||||
%[0-9A-F][A-F](?=[A-Za-z])
|
||||
@@ -431,7 +431,7 @@ sha\d+:[0-9a-f]*?[a-f]{3,}[0-9a-f]*
|
||||
# sha-... -- uses a fancy capture
|
||||
(\\?['"]|")[0-9a-f]{40,}\g{-1}
|
||||
# hex runs
|
||||
\b(?=(?:[a-fA-F]{0,2}\d)*[a-fA-F]{3})[0-9a-fA-F]{16,}\b
|
||||
\b[0-9a-fA-F]{16,}\b
|
||||
# hex in url queries
|
||||
=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?&
|
||||
# ssh
|
||||
@@ -455,11 +455,7 @@ LS0tLS1CRUdJT.*
|
||||
|
||||
# uuid:
|
||||
\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b
|
||||
|
||||
# unicode escaped characters (4)
|
||||
\\u[0-9a-fA-F]{4}
|
||||
|
||||
# hex digits including css/html color classes
|
||||
# hex digits including css/html color classes:
|
||||
(?:[\\0][xX]|\\u\{?|[uU]\+|#x?|%23|&H)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b
|
||||
|
||||
# integrity
|
||||
@@ -482,7 +478,7 @@ Name\[[^\]]+\]=.*
|
||||
(?:(?:\b|_|(?<=[a-z]))I|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
|
||||
# python
|
||||
#\b(?i)py(?!gment|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
|
||||
#\b(?i)py(?!gments|gmy|lon|ramid|ro|th)(?=[a-z]{2,})
|
||||
|
||||
# crypt
|
||||
(['"])\$2[ayb]\$.{56}\g{-1}
|
||||
@@ -502,21 +498,12 @@ Name\[[^\]]+\]=.*
|
||||
# go.sum
|
||||
\bh1:\S+
|
||||
|
||||
# golang print-f-style functions
|
||||
#(?i)(?<=append|comma|debug|equal|err|error|exit|fatal|format|info|log|name|panic|print|skip|scan|string|trace|true|warn|warning|wrap|write)(?:f|ln)(?:[ (]|$)
|
||||
|
||||
# golang regular expression
|
||||
(?<!")\br".+?"
|
||||
|
||||
# imports
|
||||
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+(?:\s+from (['"]).*?\g{-1}|)
|
||||
^import\s+(?:(?:static|type)\s+|)(?:[\w.]|\{\s*\w*?(?:,\s*(?:\w*|\*))+\s*\})+
|
||||
|
||||
# scala modules
|
||||
#("[^"]+"\s*%%?\s*){2,3}"[^"]+"
|
||||
|
||||
# Dataframes / NumPy
|
||||
#\b(?:df|np)\.\w{3,}
|
||||
|
||||
# container images
|
||||
image: [-\w./:@]+
|
||||
|
||||
@@ -546,18 +533,12 @@ content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1}
|
||||
# Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings
|
||||
(?<!['"])\b(?:B|BR|Br|F|FR|Fr|R|RB|RF|Rb|Rf|U|UR|Ur|b|bR|br|f|fR|fr|r|rB|rF|rb|rf|u|uR|ur)['"](?=[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})
|
||||
|
||||
# Regular expression for word breaks
|
||||
#\\b(?=[a-z]{2})
|
||||
|
||||
# Regular expressions for (P|p)assword
|
||||
\([A-Z]\|[a-z]\)[a-z]+
|
||||
|
||||
# Java regular expressions
|
||||
Pattern\.(?:compile|matches)\(".*"
|
||||
|
||||
# JavaScript regular expressions
|
||||
# javascript exec/test regex
|
||||
/.{3,}?/[gim]*\.(?:exec|test)\(
|
||||
# javascript test regex
|
||||
/.{3,}/[gim]*\.test\(
|
||||
# javascript match regex
|
||||
\.match\(/[^/\s"]{3,}/[gim]*\s*
|
||||
# javascript match regex
|
||||
@@ -584,7 +565,7 @@ perl(?:\s+-[a-zA-Z]\w*)+
|
||||
regexp?\.MustCompile\((?:`[^`]*`|".*"|'.*')\)
|
||||
|
||||
# regex choice
|
||||
#\((?:\?:|)[^)|]+(?<! )\|(?!(?:jq|xargs)\b)[^)| ][^)]*\)
|
||||
\(\?:[^)]+\|[^)]+\)
|
||||
|
||||
# proto
|
||||
^\s*(\w+)\s\g{-1} =
|
||||
@@ -607,9 +588,6 @@ urn:shemas-jetbrains-com
|
||||
# Debian changelog severity
|
||||
[-\w]+ \(.*\) (?:\w+|baseline|unstable|experimental); urgency=(?:low|medium|high|emergency|critical)\b
|
||||
|
||||
# Red Hat Package management spec file dependencies
|
||||
^(?:Build|)Requires: [-.\w]+
|
||||
|
||||
# kubernetes pod status lists
|
||||
# https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase
|
||||
\w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+
|
||||
@@ -664,8 +642,6 @@ PrependWithABINamepsace
|
||||
>[-a-zA-Z=;:/0-9+]{3,}=</
|
||||
# base64 encoded content, possibly wrapped in mime
|
||||
#(?:^|[\s=;:?])[-a-zA-Z=;:/0-9+]{50,}(?:[\s=;:?]|$)
|
||||
# jwt
|
||||
(?:\be[wy][-a-zA-Z=;:/0-9+]+\.){2}[-_\w]+
|
||||
# base64 encoded json
|
||||
\beyJ[-a-zA-Z=;:/0-9+]+
|
||||
# base64 encoded pkcs
|
||||
@@ -703,9 +679,9 @@ systemd.*?running in system mode \([-+].*\)$
|
||||
|
||||
# Non-English
|
||||
# Even repositories expecting pure English content can unintentionally have Non-English content... People will occasionally mistakenly enter [homoglyphs](https://en.wikipedia.org/wiki/Homoglyph) which are essentially typos, and using this pattern will mean check-spelling will not complain about them.
|
||||
# .
|
||||
#
|
||||
# If the content to be checked should be written in English and the only Non-English items will be people's names, then you can consider adding this.
|
||||
# .
|
||||
#
|
||||
# Alternatively, if you're using check-spelling v0.0.25+, and you would like to _check_ the Non-English content for spelling errors, you can. For information on how to do so, see:
|
||||
# https://docs.check-spelling.dev/Feature:-Configurable-word-characters.html#unicode
|
||||
[a-zA-Z]*[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3}[a-zA-ZÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]*|[a-zA-Z]{3,}[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź]|[ÀÁÂÃÄÅÆČÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæčçèéêëìíîïðñòóôõöøùúûüýÿĀāŁłŃńŅņŒœŚśŠšŜŝŸŽžź][a-zA-Z]{3,}
|
||||
@@ -717,7 +693,7 @@ systemd.*?running in system mode \([-+].*\)$
|
||||
# This corpus only had capital letters, but you probably want lowercase ones as well.
|
||||
\b[LN]'+[a-z]{2,}\b
|
||||
|
||||
# LaTeX
|
||||
# latex (check-spelling >= 0.0.22)
|
||||
\\\w{2,}\{
|
||||
|
||||
# American Mathematical Society (AMS) / Doxygen
|
||||
@@ -744,6 +720,7 @@ nolint:\s*[\w,]+
|
||||
# cygwin paths
|
||||
/cygdrive/[a-zA-Z]/(?:Program Files(?: \(.*?\)| ?)(?:/[-+.~\\/()\w ]+)*|[-+.~\\/()\w])+
|
||||
|
||||
# in check-spelling@v0.0.22+, printf markers aren't automatically consumed
|
||||
# printf markers
|
||||
#(?<!\\)\\[nrt](?=[a-z]{2,})
|
||||
# alternate printf markers if you run into latex and friends
|
||||
@@ -772,12 +749,12 @@ W/"[^"]+"
|
||||
|
||||
# Compiler flags (Unix, Java/Scala)
|
||||
# Use if you have things like `-Pdocker` and want to treat them as `docker`
|
||||
#(?:^|[\t ,>"'`=\[(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
#(?:^|[\t ,>"'`=(#])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})
|
||||
|
||||
# Compiler flags (Windows / PowerShell)
|
||||
# This is a subset of the more general compiler flags pattern.
|
||||
# It avoids matching `-Path` to prevent it from being treated as `ath`
|
||||
#(?:^|[\t ,"'`=\[(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
|
||||
#(?:^|[\t ,"'`=(#])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}))
|
||||
|
||||
# Compiler flags (linker)
|
||||
,-B
|
||||
@@ -785,7 +762,7 @@ W/"[^"]+"
|
||||
# Library prefix
|
||||
# e.g., `lib`+`archive`, `lib`+`raw`, `lib`+`unwind`
|
||||
# (ignores some words that happen to start with `lib`)
|
||||
(?:\b|_)[Ll]ib(?!era[lt])(?:re(?=office)|era|)(?!ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
|
||||
(?:\b|_)[Ll]ib(?:re(?=office)|)(?!era[lt]|ero|erty|rar(?:i(?:an|es)|y))(?=[a-z])
|
||||
|
||||
# iSCSI iqn (approximate regex)
|
||||
\biqn\.[0-9]{4}-[0-9]{2}(?:[\.-][a-z][a-z0-9]*)*\b
|
||||
@@ -796,9 +773,9 @@ W/"[^"]+"
|
||||
# curl arguments
|
||||
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
|
||||
# set arguments
|
||||
\b(?:bash|(?<!\.)sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
|
||||
\b(?:bash|sh|set)(?:\s+[-+][abefimouxE]{1,2})*\s+[-+][abefimouxE]{3,}(?:\s+[-+][abefimouxE]+)*
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long...
|
||||
\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b
|
||||
# macOS temp folders
|
||||
|
||||
13
.github/actions/spell-check/excludes.txt
vendored
13
.github/actions/spell-check/excludes.txt
vendored
@@ -101,17 +101,11 @@
|
||||
^doc/devdocs/akaLinks\.md$
|
||||
^NOTICE\.md$
|
||||
^src/common/CalculatorEngineCommon/exprtk\.hpp$
|
||||
^src/common/UnitTests-CommonUtils/
|
||||
^src/common/ManagedCommon/ColorFormatHelper\.cs$
|
||||
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
|
||||
^src/common/sysinternals/Eula/
|
||||
^doc/devdocs/modules/cmdpal/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
|
||||
^src/modules/cmdpal/Microsoft\.CmdPal\.UI/Settings/InternalPage\.SampleData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/.*\.TestData\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CmdPal\.Common\.UnitTests/Text/.*\.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherComparisonTests.cs$
|
||||
^src/modules/cmdpal/Tests/Microsoft\.CommandPalette\.Extensions\.Toolkit\.UnitTests/FuzzyMatcherDiacriticsTests.cs$
|
||||
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
||||
@@ -140,7 +134,6 @@
|
||||
^tools/project_template/ModuleTemplate/resource\.h$
|
||||
^tools/Verification scripts/Check preview handler registration\.ps1$
|
||||
ignore$
|
||||
^src/modules/registrypreview/RegistryPreviewUILib/Controls/HexBox/.*$
|
||||
^src/common/CalculatorEngineCommon/exprtk\.hpp$
|
||||
src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage.cs
|
||||
^src/modules/powerrename/unittests/testdata/avif_test\.avif$
|
||||
^src/modules/powerrename/unittests/testdata/heif_test\.heic$
|
||||
^deps/spdlog-msvc-fix/
|
||||
|
||||
388
.github/actions/spell-check/expect.txt
vendored
388
.github/actions/spell-check/expect.txt
vendored
File diff suppressed because it is too large
Load Diff
495
.github/actions/spell-check/line_forbidden.patterns
vendored
495
.github/actions/spell-check/line_forbidden.patterns
vendored
File diff suppressed because one or more lines are too long
62
.github/actions/spell-check/patterns.txt
vendored
62
.github/actions/spell-check/patterns.txt
vendored
@@ -1,18 +1,5 @@
|
||||
# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns
|
||||
|
||||
Inno Setup
|
||||
|
||||
FFmpeg
|
||||
|
||||
# https://github.com/MicrosoftEdge/edge-launcher
|
||||
MIcrosoftEdgeLauncherCsharp
|
||||
|
||||
# x64
|
||||
(?:(?<=[a-df-z])x|(?<=[A-Z]X))64
|
||||
|
||||
# reversed irreversible binomials
|
||||
\b(?:mouse down and up|low and high)\b
|
||||
|
||||
# marker to ignore all code on line
|
||||
^.*/\* #no-spell-check-line \*/.*$
|
||||
# marker for ignoring a comment to the end of the line
|
||||
@@ -84,14 +71,11 @@ StringComparer.OrdinalIgnoreCase\) \{.*\}
|
||||
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
|
||||
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
|
||||
|
||||
# DateTime Formats
|
||||
Get-Date -Format \w+|DateTime\.Now(?::|\.ToString\(")\w+
|
||||
|
||||
# Automatically suggested patterns
|
||||
|
||||
# hit-count: 5402 file-count: 1339
|
||||
# IServiceProvider / isAThing
|
||||
(?:(?:\b|_|(?<=[a-z]))[A-Z]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
(?:(?:\b|_|(?<=[a-z]))[IT]|(?:\b|_)(?:nsI|isA))(?=(?:[A-Z][a-z]{2,})+(?:[A-Z\d]|\b))
|
||||
|
||||
# hit-count: 2073 file-count: 842
|
||||
# #includes
|
||||
@@ -175,10 +159,6 @@ aka\.ms/[a-zA-Z0-9]+
|
||||
# kubernetes crd patterns
|
||||
^\s*pattern: .*$
|
||||
|
||||
# hit-count: 7 file-count: 3
|
||||
# unicode escaped characters (4)
|
||||
\\u[0-9a-fA-F]{4}
|
||||
|
||||
# hit-count: 5 file-count: 3
|
||||
# URL escaped characters
|
||||
%[0-9A-F][A-F](?=[A-Za-z])
|
||||
@@ -191,10 +171,6 @@ aka\.ms/[a-zA-Z0-9]+
|
||||
# medium
|
||||
\bmedium\.com/@?[^/\s"]+/[-\w:/*.]+
|
||||
|
||||
# hit-count: 2 file-count: 2
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:\s-C \S+|(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
|
||||
# hit-count: 2 file-count: 1
|
||||
# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there
|
||||
# YouTube url
|
||||
@@ -208,9 +184,22 @@ aka\.ms/[a-zA-Z0-9]+
|
||||
# curl arguments
|
||||
\b(?:\\n|)curl(?:\.exe|)(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)*
|
||||
|
||||
# hit-count: 1 file-count: 1
|
||||
# tar arguments
|
||||
\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+
|
||||
|
||||
# #pragma lib
|
||||
^\s*#pragma comment\(lib, ".*?"\)
|
||||
|
||||
# UnitTests
|
||||
\[DataRow\(.*\)\]
|
||||
|
||||
# AdditionalDependencies
|
||||
<AdditionalDependencies>.*<
|
||||
|
||||
# the last line of mimetype="application/x-microsoft.net.object.bytearray.base64" things in .resx files
|
||||
^\s*[-a-zA-Z=;:/0-9+]*[-a-zA-Z;:/0-9+][-a-zA-Z=;:/0-9+]*=$
|
||||
|
||||
RegExp\(@?([`'"]).*?\g{-1}\)|(?:escapes|regEx):\s*(?:/.*/|([`'"]).*?\g{-1})|return/.*?/
|
||||
|
||||
# Questionably acceptable forms of `in to`
|
||||
@@ -230,15 +219,13 @@ RegExp\(@?([`'"]).*?\g{-1}\)|(?:escapes|regEx):\s*(?:/.*/|([`'"]).*?\g{-1})|retu
|
||||
# mount
|
||||
\bmount\s+-t\s+(\w+)\s+\g{-1}\b
|
||||
# C types and repeated CSS values
|
||||
\s(auto|await|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
|
||||
\s(auto|buffalo|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?:\s\g{-1})+\s
|
||||
# C enum and struct
|
||||
\b(?:enum|struct)\s+(\w+)\s+\g{-1}\b
|
||||
# go templates
|
||||
\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml):
|
||||
# doxygen / javadoc / .net
|
||||
(?:[\\@](?:brief|defgroup|groupname|link|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+(?:static|override|readonly|required|virtual))*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s
|
||||
# C# getter/setter
|
||||
\s(\w+)\s+\g{-1}\s*\{\s*[gs]et;
|
||||
|
||||
# macOS file path
|
||||
(?:Contents\W+|(?!iOS)/)MacOS\b
|
||||
@@ -286,22 +273,3 @@ St&yle
|
||||
# Usernames with numbers
|
||||
# 0x6f677548 is user name but user folder causes a flag
|
||||
\bx6f677548\b
|
||||
|
||||
# Windows API constants and hardware interface terms
|
||||
\bCOINIT[_A-Z]*\b
|
||||
\bEOAC[_A-Z]*\b
|
||||
\b(?:RPC_C_AUTHN_)?WINNT\b
|
||||
\bUPDATEREGISTRY\b
|
||||
\b(?:CDS_)?UPDATEREGISTRY\b
|
||||
|
||||
# Display interface terms (HDMI, DVI, DisplayPort)
|
||||
\b(?:HDMI|DVI|DisplayPort)(?:-\d+)?\b
|
||||
|
||||
# 2D Region struct names
|
||||
\bDisplayConfig2?D?Region\b
|
||||
|
||||
# Microsoft Store URLs and product IDs
|
||||
ms-windows-store://\S+
|
||||
|
||||
# ANSI color codes
|
||||
(?:\\(?:u00|x)1[Bb]|\\03[1-7]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+)*m
|
||||
|
||||
47
.github/actions/spell-check/reject.txt
vendored
47
.github/actions/spell-check/reject.txt
vendored
@@ -1,30 +1,23 @@
|
||||
attache
|
||||
aroynt.*
|
||||
bellows?
|
||||
^attache$
|
||||
^bellows?$
|
||||
benefitting
|
||||
occurences?
|
||||
.*dnt
|
||||
dependan.*
|
||||
developement
|
||||
developp?e
|
||||
Devers?
|
||||
devex.*
|
||||
devide
|
||||
Devinn?[ae]
|
||||
devisals?
|
||||
devisors?
|
||||
diables?
|
||||
hasta?
|
||||
hastat.*
|
||||
immediatly
|
||||
inisle
|
||||
inital
|
||||
linge
|
||||
oer
|
||||
^dependan.*
|
||||
^develope$
|
||||
^developement$
|
||||
^developpe
|
||||
^Devers?$
|
||||
^devex
|
||||
^devide
|
||||
^Devinn?[ae]
|
||||
^devisal
|
||||
^devisor
|
||||
^diables?$
|
||||
^oer$
|
||||
Sorce
|
||||
[Ss]pae.*
|
||||
Teh
|
||||
untill
|
||||
untilling
|
||||
venders?
|
||||
wether.*
|
||||
^[Ss]pae.*
|
||||
^Teh$
|
||||
^untill$
|
||||
^untilling$
|
||||
^venders?$
|
||||
^wether.*
|
||||
|
||||
92
.github/agents/FixIssue.agent.md
vendored
92
.github/agents/FixIssue.agent.md
vendored
@@ -1,92 +0,0 @@
|
||||
---
|
||||
description: 'Implements fixes for GitHub issues based on implementation plans'
|
||||
name: 'FixIssue'
|
||||
tools: ['read', 'edit', 'search', 'execute', 'agent', 'usages', 'problems', 'changes', 'testFailure', 'github/*', 'github.vscode-pull-request-github/*']
|
||||
argument-hint: 'GitHub issue number (e.g., #12345)'
|
||||
infer: true
|
||||
---
|
||||
|
||||
# FixIssue Agent
|
||||
|
||||
You are an **IMPLEMENTATION AGENT** specialized in executing implementation plans to fix GitHub issues.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at translating plans into working code
|
||||
- Deep knowledge of PowerToys codebase patterns and conventions
|
||||
- Skilled at writing tests, handling edge cases, and validating builds
|
||||
- You follow plans precisely while handling ambiguity gracefully
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **issue_number**, execute the implementation plan and produce:
|
||||
1. Working code changes applied directly to the repository
|
||||
2. `Generated Files/issueFix/{{issue_number}}/pr-description.md` — PR-ready description
|
||||
3. `Generated Files/issueFix/{{issue_number}}/manual-steps.md` — Only if human action needed
|
||||
|
||||
## Core Directive
|
||||
|
||||
**Follow the implementation plan in `Generated Files/issueReview/{{issue_number}}/implementation-plan.md` as the single source of truth.**
|
||||
|
||||
If the plan doesn't exist, invoke PlanIssue agent first via `runSubagent`.
|
||||
|
||||
## Working Principles
|
||||
|
||||
- **Plan First**: Read and understand the entire implementation plan before coding
|
||||
- **Validate Always**: For each change: Edit → Build → Verify → Commit. Never proceed if build fails.
|
||||
- **Atomic Commits**: Each commit must be self-contained, buildable, and meaningful
|
||||
- **Ask, Don't Guess**: When uncertain, insert `// TODO(Human input needed): <question>` and document in manual-steps.md
|
||||
|
||||
## Strategy
|
||||
|
||||
**Core Loop** — For every unit of work:
|
||||
1. **Edit**: Make focused changes to implement one logical piece
|
||||
2. **Build**: Run `tools\build\build.cmd` and check for exit code 0
|
||||
3. **Verify**: Use `problems` tool for lint/compile errors; run relevant tests
|
||||
4. **Commit**: Only after build passes — use `.github/prompts/create-commit-title.prompt.md`
|
||||
|
||||
Never skip steps. Never commit broken code. Never proceed if build fails.
|
||||
|
||||
**Feature-by-Feature E2E**: For big scenarios with multiple features, complete each feature end-to-end before moving to the next:
|
||||
- Settings UI → Functionality → Logging → Tests (for Feature 1)
|
||||
- Then repeat for Feature 2
|
||||
- Benefits: Each feature is self-contained, testable, easier to review, can ship incrementally
|
||||
|
||||
**Large Changes** (3+ files or cross-module):
|
||||
- Use `tools\build\New-WorktreeFromBranch.ps1` for isolated worktrees
|
||||
- Create separate branches per feature (e.g., `issue/{{issue_number}}-export`, `issue/{{issue_number}}-import`)
|
||||
- Merge feature branches back after each is validated
|
||||
|
||||
**Recovery**: If implementation goes wrong:
|
||||
- Create a checkpoint branch before risky changes
|
||||
- On failure: branch from last known-good state, cherry-pick working changes, abandon broken branch
|
||||
- For complex changes, consider multiple smaller PRs
|
||||
|
||||
## Guidelines
|
||||
|
||||
**DO**:
|
||||
- Follow the plan exactly
|
||||
- Validate build before every commit — **NEVER commit broken code**
|
||||
- Use `.github/prompts/create-commit-title.prompt.md` for commit messages
|
||||
- Add comprehensive tests for changed behavior
|
||||
- Use worktrees for large changes (3+ files or cross-module)
|
||||
- Document deviations from plan
|
||||
|
||||
**DON'T**:
|
||||
- Implement everything in a single massive commit
|
||||
- Continue after a failed build without fixing
|
||||
- Make drive-by refactors outside issue scope
|
||||
- Skip tests for behavioral changes
|
||||
- Add noisy logs in hot paths
|
||||
- Break IPC/JSON contracts without updating both sides
|
||||
- Introduce dependencies without documenting in NOTICE.md
|
||||
|
||||
## References
|
||||
|
||||
- [Build Guidelines](../../tools/build/BUILD-GUIDELINES.md) — Build commands and validation
|
||||
- [Coding Style](../../doc/devdocs/development/style.md) — Formatting and conventions
|
||||
- [AGENTS.md](../../AGENTS.md) — Full contributor guide
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_number**: Extract from `#123`, `issue 123`, or plain number. If missing, ask user.
|
||||
65
.github/agents/PlanIssue.agent.md
vendored
65
.github/agents/PlanIssue.agent.md
vendored
@@ -1,65 +0,0 @@
|
||||
---
|
||||
description: 'Analyzes GitHub issues to produce overview and implementation plans'
|
||||
name: 'PlanIssue'
|
||||
tools: ['execute', 'read', 'edit', 'search', 'web', 'github/*', 'agent', 'github-artifacts/*', 'todo']
|
||||
argument-hint: 'GitHub issue number (e.g., #12345)'
|
||||
handoffs:
|
||||
- label: Start Implementation
|
||||
agent: FixIssue
|
||||
prompt: 'Fix issue #{{issue_number}} using the implementation plan'
|
||||
- label: Open Plan in Editor
|
||||
agent: agent
|
||||
prompt: 'Open Generated Files/issueReview/{{issue_number}}/overview.md and implementation-plan.md'
|
||||
showContinueOn: false
|
||||
send: true
|
||||
infer: true
|
||||
---
|
||||
|
||||
# PlanIssue Agent
|
||||
|
||||
You are a **PLANNING AGENT** specialized in analyzing GitHub issues and producing comprehensive planning documentation.
|
||||
|
||||
## Identity & Expertise
|
||||
|
||||
- Expert at issue triage, priority scoring, and technical analysis
|
||||
- Deep knowledge of PowerToys architecture and codebase patterns
|
||||
- Skilled at breaking down problems into actionable implementation steps
|
||||
- You research thoroughly before planning, gathering 80% confidence before drafting
|
||||
|
||||
## Goal
|
||||
|
||||
For the given **issue_number**, produce two deliverables:
|
||||
1. `Generated Files/issueReview/{{issue_number}}/overview.md` — Issue analysis with scoring
|
||||
2. `Generated Files/issueReview/{{issue_number}}/implementation-plan.md` — Technical implementation plan
|
||||
Above is the core interaction with the end user. If you cannot produce the files above, you fail the task. Each time, you must check whether the files exist or have been modified by the end user, without assuming you know their contents.
|
||||
3. `Generated Files/issueReview/{{issue_number}}/logs/**` — logs for your diagnostic of root cause, research steps, and reasoning
|
||||
|
||||
## Core Directive
|
||||
|
||||
**Follow the template in `.github/prompts/review-issue.prompt.md` exactly.** Read it first, then apply every section as specified.
|
||||
|
||||
- Fetch issue details: reactions, comments, linked PRs, images, logs
|
||||
- Search related code and similar past fixes
|
||||
- Ask clarifying questions when ambiguous
|
||||
- Identify subject matter experts via git history
|
||||
|
||||
<stopping_rules>
|
||||
You are a PLANNING agent, NOT an implementation agent.
|
||||
|
||||
STOP if you catch yourself:
|
||||
- Writing code or editing source files outside `Generated Files/issueReview/`
|
||||
- Making assumptions without researching
|
||||
- Skipping the scoring/assessment phase
|
||||
|
||||
Plans describe what the USER or FixIssue agent will execute later.
|
||||
</stopping_rules>
|
||||
|
||||
## References
|
||||
|
||||
- [Review Issue Prompt](../.github/prompts/review-issue.prompt.md) — Template for plan structure
|
||||
- [Architecture Overview](../../doc/devdocs/core/architecture.md) — System design context
|
||||
- [AGENTS.md](../../AGENTS.md) — Full contributor guide
|
||||
|
||||
## Parameter
|
||||
|
||||
- **issue_number**: Extract from `#123`, `issue 123`, or plain number. If missing, ask user.
|
||||
71
.github/copilot-instructions.md
vendored
71
.github/copilot-instructions.md
vendored
@@ -1,36 +1,59 @@
|
||||
---
|
||||
description: 'PowerToys AI contributor guidance'
|
||||
description: PowerToys AI contributor guidance.
|
||||
applyTo: pullRequests
|
||||
---
|
||||
|
||||
# PowerToys – Copilot Instructions
|
||||
# PowerToys - Copilot guide (concise)
|
||||
|
||||
Concise guidance for AI contributions. For complete details, see [AGENTS.md](../AGENTS.md).
|
||||
This is the top-level guide for AI changes. Keep edits small, follow existing patterns, and cite exact paths in PRs.
|
||||
|
||||
## Key Rules
|
||||
# Repo map (1-line per area)
|
||||
- Core apps: `src/runner/**` (tray/loader), `src/settings-ui/**` (Settings app)
|
||||
- Shared libs: `src/common/**`
|
||||
- Modules: `src/modules/*` (one per utility; Command Palette in `src/modules/cmdpal/**`)
|
||||
- Build tools/docs: `tools/**`, `doc/devdocs/**`
|
||||
|
||||
- Atomic PRs: one logical change, no drive-by refactors
|
||||
- Add tests when changing behavior
|
||||
- Keep hot paths quiet (no logging in hooks/tight loops)
|
||||
# Build and test (defaults)
|
||||
- Prerequisites: Visual Studio 2022 17.4+, minimal Windows 10 1803+.
|
||||
- Build discipline:
|
||||
- One terminal per operation (build -> test). Do not switch or open new ones mid-flow.
|
||||
- After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`).
|
||||
- Use scripts to build, synchronously block and wait in foreground for completion: `tools/build/build.ps1|.cmd` (current folder), `build-essentials.*` (once per brand new build for missing nuget packages).
|
||||
- Treat build exit code 0 as success; any non-zero exit code is a failure. Read the errors log in the build folder (such as `build.*.*.errors.log`) and surface problems.
|
||||
- Do not start tests or launch Runner until the previous step succeeded.
|
||||
- Tests (fast and targeted):
|
||||
- Find the test project by product code prefix (for example FancyZones, AdvancedPaste). Look for a sibling folder or one to two levels up named like `<Product>*UnitTests` or `<Product>*UITests`.
|
||||
- Build the test project, wait for exit, then run only those tests via VS Test Explorer or `vstest.console.exe` with filters. Avoid `dotnet test` in this repo.
|
||||
- Add or adjust tests when changing behavior; if skipped, state why (for example comment-only or string rename).
|
||||
|
||||
## Style Enforcement
|
||||
# Pull requests (expectations)
|
||||
- Atomic: one logical change; no drive-by refactors.
|
||||
- Describe: problem, approach, risk, test evidence.
|
||||
- List: touched paths if not obvious.
|
||||
|
||||
- C#: `src/.editorconfig`, StyleCop.Analyzers
|
||||
- C++: `src/.clang-format`
|
||||
- XAML: XamlStyler
|
||||
# When to ask for clarification
|
||||
- Ambiguous spec after scanning relevant docs (see below).
|
||||
- Cross-module impact (shared enum or struct) not clear.
|
||||
- Security, elevation, or installer changes.
|
||||
|
||||
## When to Ask for Clarification
|
||||
# Logging (use existing stacks)
|
||||
- C++ logging lives in `src/common/logger/**` (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`). Keep hot paths quiet (hooks, tight loops).
|
||||
- C# logging goes through `ManagedCommon.Logger` (`LogInfo`, `LogWarning`, `LogError`, `LogDebug`, `LogTrace`). Some UIs use injected `ILogger` via `LoggerInstance.Logger`.
|
||||
|
||||
- Ambiguous spec after scanning docs
|
||||
- Cross-module impact unclear
|
||||
- Security, elevation, or installer changes
|
||||
# Docs to consult
|
||||
- `tools/build/BUILD-GUIDELINES.md`
|
||||
- `doc/devdocs/core/architecture.md`
|
||||
- `doc/devdocs/core/runner.md`
|
||||
- `doc/devdocs/core/settings/readme.md`
|
||||
- `doc/devdocs/modules/readme.md`
|
||||
|
||||
## Component-Specific Instructions
|
||||
# Language style rules
|
||||
- Always enforce repo analyzers: root `.editorconfig` plus any `stylecop.json`.
|
||||
- C# code follows StyleCop.Analyzers and Microsoft.CodeAnalysis.NetAnalyzers.
|
||||
- C++ code honors `.clang-format` plus `.clang-tidy` (modernize/cppcoreguidelines/readability).
|
||||
- Markdown files wrap at 80 characters and use ATX headers with fenced code blocks that include language tags.
|
||||
- YAML files indent two spaces and add comments for complex settings while keeping keys clear.
|
||||
- PowerShell scripts use Verb-Noun names and prefer single-quoted literals while documenting parameters and satisfying PSScriptAnalyzer.
|
||||
|
||||
These are auto-applied based on file location:
|
||||
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md)
|
||||
- [Common Libraries](.github/instructions/common-libraries.instructions.md)
|
||||
|
||||
## Detailed Documentation
|
||||
|
||||
- [Architecture](../doc/devdocs/core/architecture.md)
|
||||
- [Coding Style](../doc/devdocs/development/style.md)
|
||||
# Done checklist (self review before finishing)
|
||||
- Build clean? Tests updated or passed? No unintended formatting? Any new dependency? Documented skips?
|
||||
|
||||
261
.github/instructions/agent-skills.instructions.md
vendored
261
.github/instructions/agent-skills.instructions.md
vendored
@@ -1,261 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating high-quality Agent Skills for GitHub Copilot'
|
||||
applyTo: '**/.github/skills/**/SKILL.md, **/.claude/skills/**/SKILL.md'
|
||||
---
|
||||
|
||||
# Agent Skills File Guidelines
|
||||
|
||||
Instructions for creating effective and portable Agent Skills that enhance GitHub Copilot with specialized capabilities, workflows, and bundled resources.
|
||||
|
||||
## What Are Agent Skills?
|
||||
|
||||
Agent Skills are self-contained folders with instructions and bundled resources that teach AI agents specialized capabilities. Unlike custom instructions (which define coding standards), skills enable task-specific workflows that can include scripts, examples, templates, and reference data.
|
||||
|
||||
Key characteristics:
|
||||
- **Portable**: Works across VS Code, Copilot CLI, and Copilot coding agent
|
||||
- **Progressive loading**: Only loaded when relevant to the user's request
|
||||
- **Resource-bundled**: Can include scripts, templates, examples alongside instructions
|
||||
- **On-demand**: Activated automatically based on prompt relevance
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Skills are stored in specific locations:
|
||||
|
||||
| Location | Scope | Recommendation |
|
||||
|----------|-------|----------------|
|
||||
| `.github/skills/<skill-name>/` | Project/repository | Recommended for project skills |
|
||||
| `.claude/skills/<skill-name>/` | Project/repository | Legacy, for backward compatibility |
|
||||
| `~/.github/skills/<skill-name>/` | Personal (user-wide) | Recommended for personal skills |
|
||||
| `~/.claude/skills/<skill-name>/` | Personal (user-wide) | Legacy, for backward compatibility |
|
||||
|
||||
Each skill **must** have its own subdirectory containing at minimum a `SKILL.md` file.
|
||||
|
||||
## Required SKILL.md Format
|
||||
|
||||
### Frontmatter (Required)
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: webapp-testing
|
||||
description: Toolkit for testing local web applications using Playwright. Use when asked to verify frontend functionality, debug UI behavior, capture browser screenshots, check for visual regressions, or view browser console logs. Supports Chrome, Firefox, and WebKit browsers.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
```
|
||||
|
||||
| Field | Required | Constraints |
|
||||
|-------|----------|-------------|
|
||||
| `name` | Yes | Lowercase, hyphens for spaces, max 64 characters (e.g., `webapp-testing`) |
|
||||
| `description` | Yes | Clear description of capabilities AND use cases, max 1024 characters |
|
||||
| `license` | No | Reference to LICENSE.txt (e.g., `Complete terms in LICENSE.txt`) or SPDX identifier |
|
||||
|
||||
### Description Best Practices
|
||||
|
||||
**CRITICAL**: The `description` field is the PRIMARY mechanism for automatic skill discovery. Copilot reads ONLY the `name` and `description` to decide whether to load a skill. If your description is vague, the skill will never be activated.
|
||||
|
||||
**What to include in description:**
|
||||
1. **WHAT** the skill does (capabilities)
|
||||
2. **WHEN** to use it (specific triggers, scenarios, file types, or user requests)
|
||||
3. **Keywords** that users might mention in their prompts
|
||||
|
||||
**Good description:**
|
||||
```yaml
|
||||
description: Toolkit for testing local web applications using Playwright. Use when asked to verify frontend functionality, debug UI behavior, capture browser screenshots, check for visual regressions, or view browser console logs. Supports Chrome, Firefox, and WebKit browsers.
|
||||
```
|
||||
|
||||
**Poor description:**
|
||||
```yaml
|
||||
description: Web testing helpers
|
||||
```
|
||||
|
||||
The poor description fails because:
|
||||
- No specific triggers (when should Copilot load this?)
|
||||
- No keywords (what user prompts would match?)
|
||||
- No capabilities (what can it actually do?)
|
||||
|
||||
### Body Content
|
||||
|
||||
The body contains detailed instructions that Copilot loads AFTER the skill is activated. Recommended sections:
|
||||
|
||||
| Section | Purpose |
|
||||
|---------|---------|
|
||||
| `# Title` | Brief overview of what this skill enables |
|
||||
| `## When to Use This Skill` | List of scenarios (reinforces description triggers) |
|
||||
| `## Prerequisites` | Required tools, dependencies, environment setup |
|
||||
| `## Step-by-Step Workflows` | Numbered steps for common tasks |
|
||||
| `## Troubleshooting` | Common issues and solutions table |
|
||||
| `## References` | Links to bundled docs or external resources |
|
||||
|
||||
## Bundling Resources
|
||||
|
||||
Skills can include additional files that Copilot accesses on-demand:
|
||||
|
||||
### Supported Resource Types
|
||||
|
||||
| Folder | Purpose | Loaded into Context? | Example Files |
|
||||
|--------|---------|---------------------|---------------|
|
||||
| `scripts/` | Executable automation that performs specific operations | When executed | `helper.py`, `validate.sh`, `build.ts` |
|
||||
| `references/` | Documentation the AI agent reads to inform decisions | Yes, when referenced | `api_reference.md`, `schema.md`, `workflow_guide.md` |
|
||||
| `assets/` | **Static files used AS-IS** in output (not modified by the AI agent) | No | `logo.png`, `brand-template.pptx`, `custom-font.ttf` |
|
||||
| `templates/` | **Starter code/scaffolds that the AI agent MODIFIES** and builds upon | Yes, when referenced | `viewer.html` (insert algorithm), `hello-world/` (extend) |
|
||||
|
||||
### Directory Structure Example
|
||||
|
||||
```
|
||||
.github/skills/my-skill/
|
||||
├── SKILL.md # Required: Main instructions
|
||||
├── LICENSE.txt # Recommended: License terms (Apache 2.0 typical)
|
||||
├── scripts/ # Optional: Executable automation
|
||||
│ ├── helper.py # Python script
|
||||
│ └── helper.ps1 # PowerShell script
|
||||
├── references/ # Optional: Documentation loaded into context
|
||||
│ ├── api_reference.md
|
||||
│ ├── step1-setup.md # Detailed workflow (>3 steps)
|
||||
│ └── step2-deployment.md
|
||||
├── assets/ # Optional: Static files used AS-IS in output
|
||||
│ ├── baseline.png # Reference image for comparison
|
||||
│ └── report-template.html
|
||||
└── templates/ # Optional: Starter code the AI agent modifies
|
||||
├── scaffold.py # Code scaffold the AI agent customizes
|
||||
└── config.template # Config template the AI agent fills in
|
||||
```
|
||||
|
||||
> **LICENSE.txt**: When creating a skill, download the Apache 2.0 license text from https://www.apache.org/licenses/LICENSE-2.0.txt and save as `LICENSE.txt`. Update the copyright year and owner in the appendix section.
|
||||
|
||||
### Assets vs Templates: Key Distinction
|
||||
|
||||
**Assets** are static resources **consumed unchanged** in the output:
|
||||
- A `logo.png` that gets embedded into a generated document
|
||||
- A `report-template.html` copied as output format
|
||||
- A `custom-font.ttf` applied to text rendering
|
||||
|
||||
**Templates** are starter code/scaffolds that **the AI agent actively modifies**:
|
||||
- A `scaffold.py` where the AI agent inserts logic
|
||||
- A `config.template` where the AI agent fills in values based on user requirements
|
||||
- A `hello-world/` project directory that the AI agent extends with new features
|
||||
|
||||
**Rule of thumb**: If the AI agent reads and builds upon the file content → `templates/`. If the file is used as-is in output → `assets/`.
|
||||
|
||||
### Referencing Resources in SKILL.md
|
||||
|
||||
Use relative paths to reference files within the skill directory:
|
||||
|
||||
```markdown
|
||||
## Available Scripts
|
||||
|
||||
Run the [helper script](./scripts/helper.py) to automate common tasks.
|
||||
|
||||
See [API reference](./references/api_reference.md) for detailed documentation.
|
||||
|
||||
Use the [scaffold](./templates/scaffold.py) as a starting point.
|
||||
```
|
||||
|
||||
## Progressive Loading Architecture
|
||||
|
||||
Skills use three-level loading for efficiency:
|
||||
|
||||
| Level | What Loads | When |
|
||||
|-------|------------|------|
|
||||
| 1. Discovery | `name` and `description` only | Always (lightweight metadata) |
|
||||
| 2. Instructions | Full `SKILL.md` body | When request matches description |
|
||||
| 3. Resources | Scripts, examples, docs | Only when Copilot references them |
|
||||
|
||||
This means:
|
||||
- Install many skills without consuming context
|
||||
- Only relevant content loads per task
|
||||
- Resources don't load until explicitly needed
|
||||
|
||||
## Content Guidelines
|
||||
|
||||
### Writing Style
|
||||
|
||||
- Use imperative mood: "Run", "Create", "Configure" (not "You should run")
|
||||
- Be specific and actionable
|
||||
- Include exact commands with parameters
|
||||
- Show expected outputs where helpful
|
||||
- Keep sections focused and scannable
|
||||
|
||||
### Script Requirements
|
||||
|
||||
When including scripts, prefer cross-platform languages:
|
||||
|
||||
| Language | Use Case |
|
||||
|----------|----------|
|
||||
| Python | Complex automation, data processing |
|
||||
| pwsh | PowerShell Core scripting |
|
||||
| Node.js | JavaScript-based tooling |
|
||||
| Bash/Shell | Simple automation tasks |
|
||||
|
||||
Best practices:
|
||||
- Include help/usage documentation (`--help` flag)
|
||||
- Handle errors gracefully with clear messages
|
||||
- Avoid storing credentials or secrets
|
||||
- Use relative paths where possible
|
||||
|
||||
### When to Bundle Scripts
|
||||
|
||||
Include scripts in your skill when:
|
||||
- The same code would be rewritten repeatedly by the agent
|
||||
- Deterministic reliability is critical (e.g., file manipulation, API calls)
|
||||
- Complex logic benefits from being pre-tested rather than generated each time
|
||||
- The operation has a self-contained purpose that can evolve independently
|
||||
- Testability matters — scripts can be unit tested and validated
|
||||
- Predictable behavior is preferred over dynamic generation
|
||||
|
||||
Scripts enable evolution: even simple operations benefit from being implemented as scripts when they may grow in complexity, need consistent behavior across invocations, or require future extensibility.
|
||||
|
||||
### Security Considerations
|
||||
|
||||
- Scripts rely on existing credential helpers (no credential storage)
|
||||
- Include `--force` flags only for destructive operations
|
||||
- Warn users before irreversible actions
|
||||
- Document any network operations or external calls
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Parameter Table Pattern
|
||||
|
||||
Document parameters clearly:
|
||||
|
||||
```markdown
|
||||
| Parameter | Required | Default | Description |
|
||||
|-----------|----------|---------|-------------|
|
||||
| `--input` | Yes | - | Input file or URL to process |
|
||||
| `--action` | Yes | - | Action to perform |
|
||||
| `--verbose` | No | `false` | Enable verbose output |
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before publishing a skill:
|
||||
|
||||
- [ ] `SKILL.md` has valid frontmatter with `name` and `description`
|
||||
- [ ] `name` is lowercase with hyphens, ≤64 characters
|
||||
- [ ] `description` clearly states **WHAT** it does, **WHEN** to use it, and relevant **KEYWORDS**
|
||||
- [ ] Body includes when to use, prerequisites, and step-by-step workflows
|
||||
- [ ] SKILL.md body kept under 500 lines (split large content into `references/` folder)
|
||||
- [ ] Large workflows (>5 steps) split into `references/` folder with clear links from SKILL.md
|
||||
- [ ] Scripts include help documentation and error handling
|
||||
- [ ] Relative paths used for all resource references
|
||||
- [ ] No hardcoded credentials or secrets
|
||||
|
||||
## Workflow Execution Pattern
|
||||
|
||||
When executing multi-step workflows, create a TODO list where each step references the relevant documentation:
|
||||
|
||||
```markdown
|
||||
## TODO
|
||||
- [ ] Step 1: Configure environment - see [workflow-setup.md](./references/workflow-setup.md#environment)
|
||||
- [ ] Step 2: Build project - see [workflow-setup.md](./references/workflow-setup.md#build)
|
||||
- [ ] Step 3: Deploy to staging - see [workflow-deployment.md](./references/workflow-deployment.md#staging)
|
||||
- [ ] Step 4: Run validation - see [workflow-deployment.md](./references/workflow-deployment.md#validation)
|
||||
- [ ] Step 5: Deploy to production - see [workflow-deployment.md](./references/workflow-deployment.md#production)
|
||||
```
|
||||
|
||||
This ensures traceability and allows resuming workflows if interrupted.
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [Agent Skills Specification](https://agentskills.io/)
|
||||
- [VS Code Agent Skills Documentation](https://code.visualstudio.com/docs/copilot/customization/agent-skills)
|
||||
- [Reference Skills Repository](https://github.com/anthropics/skills)
|
||||
- [Awesome Copilot Skills](https://github.com/github/awesome-copilot/blob/main/docs/README.skills.md)
|
||||
791
.github/instructions/agents.instructions.md
vendored
791
.github/instructions/agents.instructions.md
vendored
@@ -1,791 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating custom agent files for GitHub Copilot'
|
||||
applyTo: '**/*.agent.md'
|
||||
---
|
||||
|
||||
# Custom Agent File Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable custom agent files that provide specialized expertise for specific development tasks in GitHub Copilot.
|
||||
|
||||
## Project Context
|
||||
|
||||
- Target audience: Developers creating custom agents for GitHub Copilot
|
||||
- File format: Markdown with YAML frontmatter
|
||||
- File naming convention: lowercase with hyphens (e.g., `test-specialist.agent.md`)
|
||||
- Location: `.github/agents/` directory (repository-level) or `agents/` directory (organization/enterprise-level)
|
||||
- Purpose: Define specialized agents with tailored expertise, tools, and instructions for specific tasks
|
||||
- Official documentation: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents
|
||||
|
||||
## Required Frontmatter
|
||||
|
||||
Every agent file must include YAML frontmatter with the following fields:
|
||||
|
||||
```yaml
|
||||
---
|
||||
description: 'Brief description of the agent purpose and capabilities'
|
||||
name: 'Agent Display Name'
|
||||
tools: ['read', 'edit', 'search']
|
||||
model: 'Claude Sonnet 4.5'
|
||||
target: 'vscode'
|
||||
infer: true
|
||||
---
|
||||
```
|
||||
|
||||
### Core Frontmatter Properties
|
||||
|
||||
#### **description** (REQUIRED)
|
||||
- Single-quoted string, clearly stating the agent's purpose and domain expertise
|
||||
- Should be concise (50-150 characters) and actionable
|
||||
- Example: `'Focuses on test coverage, quality, and testing best practices'`
|
||||
|
||||
#### **name** (OPTIONAL)
|
||||
- Display name for the agent in the UI
|
||||
- If omitted, defaults to filename (without `.md` or `.agent.md`)
|
||||
- Use title case and be descriptive
|
||||
- Example: `'Testing Specialist'`
|
||||
|
||||
#### **tools** (OPTIONAL)
|
||||
- List of tool names or aliases the agent can use
|
||||
- Supports comma-separated string or YAML array format
|
||||
- If omitted, agent has access to all available tools
|
||||
- See "Tool Configuration" section below for details
|
||||
|
||||
#### **model** (STRONGLY RECOMMENDED)
|
||||
- Specifies which AI model the agent should use
|
||||
- Supported in VS Code, JetBrains IDEs, Eclipse, and Xcode
|
||||
- Example: `'Claude Sonnet 4.5'`, `'gpt-4'`, `'gpt-4o'`
|
||||
- Choose based on agent complexity and required capabilities
|
||||
|
||||
#### **target** (OPTIONAL)
|
||||
- Specifies target environment: `'vscode'` or `'github-copilot'`
|
||||
- If omitted, agent is available in both environments
|
||||
- Use when agent has environment-specific features
|
||||
|
||||
#### **infer** (OPTIONAL)
|
||||
- Boolean controlling whether Copilot can automatically use this agent based on context
|
||||
- Default: `true` if omitted
|
||||
- Set to `false` to require manual agent selection
|
||||
|
||||
#### **metadata** (OPTIONAL, GitHub.com only)
|
||||
- Object with name-value pairs for agent annotation
|
||||
- Example: `metadata: { category: 'testing', version: '1.0' }`
|
||||
- Not supported in VS Code
|
||||
|
||||
#### **mcp-servers** (OPTIONAL, Organization/Enterprise only)
|
||||
- Configure MCP servers available only to this agent
|
||||
- Only supported for organization/enterprise level agents
|
||||
- See "MCP Server Configuration" section below
|
||||
|
||||
## Tool Configuration
|
||||
|
||||
### Tool Specification Strategies
|
||||
|
||||
**Enable all tools** (default):
|
||||
```yaml
|
||||
# Omit tools property entirely, or use:
|
||||
tools: ['*']
|
||||
```
|
||||
|
||||
**Enable specific tools**:
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'search', 'execute']
|
||||
```
|
||||
|
||||
**Enable MCP server tools**:
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'github/*', 'playwright/navigate']
|
||||
```
|
||||
|
||||
**Disable all tools**:
|
||||
```yaml
|
||||
tools: []
|
||||
```
|
||||
|
||||
### Standard Tool Aliases
|
||||
|
||||
All aliases are case-insensitive:
|
||||
|
||||
| Alias | Alternative Names | Category | Description |
|
||||
|-------|------------------|----------|-------------|
|
||||
| `execute` | shell, Bash, powershell | Shell execution | Execute commands in appropriate shell |
|
||||
| `read` | Read, NotebookRead, view | File reading | Read file contents |
|
||||
| `edit` | Edit, MultiEdit, Write, NotebookEdit | File editing | Edit and modify files |
|
||||
| `search` | Grep, Glob, search | Code search | Search for files or text in files |
|
||||
| `agent` | custom-agent, Task | Agent invocation | Invoke other custom agents |
|
||||
| `web` | WebSearch, WebFetch | Web access | Fetch web content and search |
|
||||
| `todo` | TodoWrite | Task management | Create and manage task lists (VS Code only) |
|
||||
|
||||
### Built-in MCP Server Tools
|
||||
|
||||
**GitHub MCP Server**:
|
||||
```yaml
|
||||
tools: ['github/*'] # All GitHub tools
|
||||
tools: ['github/get_file_contents', 'github/search_repositories'] # Specific tools
|
||||
```
|
||||
- All read-only tools available by default
|
||||
- Token scoped to source repository
|
||||
|
||||
**Playwright MCP Server**:
|
||||
```yaml
|
||||
tools: ['playwright/*'] # All Playwright tools
|
||||
tools: ['playwright/navigate', 'playwright/screenshot'] # Specific tools
|
||||
```
|
||||
- Configured to access localhost only
|
||||
- Useful for browser automation and testing
|
||||
|
||||
### Tool Selection Best Practices
|
||||
|
||||
- **Principle of Least Privilege**: Only enable tools necessary for the agent's purpose
|
||||
- **Security**: Limit `execute` access unless explicitly required
|
||||
- **Focus**: Fewer tools = clearer agent purpose and better performance
|
||||
- **Documentation**: Comment why specific tools are required for complex configurations
|
||||
|
||||
## Sub-Agent Invocation (Agent Orchestration)
|
||||
|
||||
Agents can invoke other agents using `runSubagent` to orchestrate multi-step workflows.
|
||||
|
||||
### How It Works
|
||||
|
||||
Include `agent` in tools list to enable sub-agent invocation:
|
||||
|
||||
```yaml
|
||||
tools: ['read', 'edit', 'search', 'agent']
|
||||
```
|
||||
|
||||
Then invoke other agents with `runSubagent`:
|
||||
|
||||
```javascript
|
||||
const result = await runSubagent({
|
||||
description: 'What this step does',
|
||||
prompt: `You are the [Specialist] specialist.
|
||||
|
||||
Context:
|
||||
- Parameter: ${parameterValue}
|
||||
- Input: ${inputPath}
|
||||
- Output: ${outputPath}
|
||||
|
||||
Task:
|
||||
1. Do the specific work
|
||||
2. Write results to output location
|
||||
3. Return summary of completion`
|
||||
});
|
||||
```
|
||||
|
||||
### Basic Pattern
|
||||
|
||||
Structure each sub-agent call with:
|
||||
|
||||
1. **description**: Clear one-line purpose of the sub-agent invocation
|
||||
2. **prompt**: Detailed instructions with substituted variables
|
||||
|
||||
The prompt should include:
|
||||
- Who the sub-agent is (specialist role)
|
||||
- What context it needs (parameters, paths)
|
||||
- What to do (concrete tasks)
|
||||
- Where to write output
|
||||
- What to return (summary)
|
||||
|
||||
### Example: Multi-Step Processing
|
||||
|
||||
```javascript
|
||||
// Step 1: Process data
|
||||
const processing = await runSubagent({
|
||||
description: 'Transform raw input data',
|
||||
prompt: `You are the Data Processor specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${basePath}/raw/
|
||||
Output: ${basePath}/processed/
|
||||
|
||||
Task:
|
||||
1. Read all files from input directory
|
||||
2. Apply transformations
|
||||
3. Write processed files to output
|
||||
4. Create summary: ${basePath}/processed/summary.md
|
||||
|
||||
Return: Number of files processed and any issues found`
|
||||
});
|
||||
|
||||
// Step 2: Analyze (depends on Step 1)
|
||||
const analysis = await runSubagent({
|
||||
description: 'Analyze processed data',
|
||||
prompt: `You are the Data Analyst specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${basePath}/processed/
|
||||
Output: ${basePath}/analysis/
|
||||
|
||||
Task:
|
||||
1. Read processed files from input
|
||||
2. Generate analysis report
|
||||
3. Write to: ${basePath}/analysis/report.md
|
||||
|
||||
Return: Key findings and identified patterns`
|
||||
});
|
||||
```
|
||||
|
||||
### Key Points
|
||||
|
||||
- **Pass variables in prompts**: Use `${variableName}` for all dynamic values
|
||||
- **Keep prompts focused**: Clear, specific tasks for each sub-agent
|
||||
- **Return summaries**: Each sub-agent should report what it accomplished
|
||||
- **Sequential execution**: Use `await` to maintain order when steps depend on each other
|
||||
- **Error handling**: Check results before proceeding to dependent steps
|
||||
|
||||
### ⚠️ Tool Availability Requirement
|
||||
|
||||
**Critical**: If a sub-agent requires specific tools (e.g., `edit`, `execute`, `search`), the orchestrator must include those tools in its own `tools` list. Sub-agents cannot access tools that aren't available to their parent orchestrator.
|
||||
|
||||
**Example**:
|
||||
```yaml
|
||||
# If your sub-agents need to edit files, execute commands, or search code
|
||||
tools: ['read', 'edit', 'search', 'execute', 'agent']
|
||||
```
|
||||
|
||||
The orchestrator's tool permissions act as a ceiling for all invoked sub-agents. Plan your tool list carefully to ensure all sub-agents have the tools they need.
|
||||
|
||||
### ⚠️ Important Limitation
|
||||
|
||||
**Sub-agent orchestration is NOT suitable for large-scale data processing.** Avoid using `runSubagent` when:
|
||||
- Processing hundreds or thousands of files
|
||||
- Handling large datasets
|
||||
- Performing bulk transformations on big codebases
|
||||
- Orchestrating more than 5-10 sequential steps
|
||||
|
||||
Each sub-agent call adds latency and context overhead. For high-volume processing, implement logic directly in a single agent instead. Use orchestration only for coordinating specialized tasks on focused, manageable datasets.
|
||||
|
||||
## Agent Prompt Structure
|
||||
|
||||
The markdown content below the frontmatter defines the agent's behavior, expertise, and instructions. Well-structured prompts typically include:
|
||||
|
||||
1. **Agent Identity and Role**: Who the agent is and its primary role
|
||||
2. **Core Responsibilities**: What specific tasks the agent performs
|
||||
3. **Approach and Methodology**: How the agent works to accomplish tasks
|
||||
4. **Guidelines and Constraints**: What to do/avoid and quality standards
|
||||
5. **Output Expectations**: Expected output format and quality
|
||||
|
||||
### Prompt Writing Best Practices
|
||||
|
||||
- **Be Specific and Direct**: Use imperative mood ("Analyze", "Generate"); avoid vague terms
|
||||
- **Define Boundaries**: Clearly state scope limits and constraints
|
||||
- **Include Context**: Explain domain expertise and reference relevant frameworks
|
||||
- **Focus on Behavior**: Describe how the agent should think and work
|
||||
- **Use Structured Format**: Headers, bullets, and lists make prompts scannable
|
||||
|
||||
## Variable Definition and Extraction
|
||||
|
||||
Agents can define dynamic parameters to extract values from user input and use them throughout the agent's behavior and sub-agent communications. This enables flexible, context-aware agents that adapt to user-provided data.
|
||||
|
||||
### When to Use Variables
|
||||
|
||||
**Use variables when**:
|
||||
- Agent behavior depends on user input
|
||||
- Need to pass dynamic values to sub-agents
|
||||
- Want to make agents reusable across different contexts
|
||||
- Require parameterized workflows
|
||||
- Need to track or reference user-provided context
|
||||
|
||||
**Examples**:
|
||||
- Extract project name from user prompt
|
||||
- Capture certification name for pipeline processing
|
||||
- Identify file paths or directories
|
||||
- Extract configuration options
|
||||
- Parse feature names or module identifiers
|
||||
|
||||
### Variable Declaration Pattern
|
||||
|
||||
Define variables section early in the agent prompt to document expected parameters:
|
||||
|
||||
```markdown
|
||||
# Agent Name
|
||||
|
||||
## Dynamic Parameters
|
||||
|
||||
- **Parameter Name**: Description and usage
|
||||
- **Another Parameter**: How it's extracted and used
|
||||
|
||||
## Your Mission
|
||||
|
||||
Process [PARAMETER_NAME] to accomplish [task].
|
||||
```
|
||||
|
||||
### Variable Extraction Methods
|
||||
|
||||
#### 1. **Explicit User Input**
|
||||
Ask the user to provide the variable if not detected in the prompt:
|
||||
|
||||
```markdown
|
||||
## Your Mission
|
||||
|
||||
Process the project by analyzing your codebase.
|
||||
|
||||
### Step 1: Identify Project
|
||||
If no project name is provided, **ASK THE USER** for:
|
||||
- Project name or identifier
|
||||
- Base path or directory location
|
||||
- Configuration type (if applicable)
|
||||
|
||||
Use this information to contextualize all subsequent tasks.
|
||||
```
|
||||
|
||||
#### 2. **Implicit Extraction from Prompt**
|
||||
Automatically extract variables from the user's natural language input:
|
||||
|
||||
```javascript
|
||||
// Example: Extract certification name from user input
|
||||
const userInput = "Process My Certification";
|
||||
|
||||
// Extract key information
|
||||
const certificationName = extractCertificationName(userInput);
|
||||
// Result: "My Certification"
|
||||
|
||||
const basePath = `certifications/${certificationName}`;
|
||||
// Result: "certifications/My Certification"
|
||||
```
|
||||
|
||||
#### 3. **Contextual Variable Resolution**
|
||||
Use file context or workspace information to derive variables:
|
||||
|
||||
```markdown
|
||||
## Variable Resolution Strategy
|
||||
|
||||
1. **From User Prompt**: First, look for explicit mentions in user input
|
||||
2. **From File Context**: Check current file name or path
|
||||
3. **From Workspace**: Use workspace folder or active project
|
||||
4. **From Settings**: Reference configuration files
|
||||
5. **Ask User**: If all else fails, request missing information
|
||||
```
|
||||
|
||||
### Using Variables in Agent Prompts
|
||||
|
||||
#### Variable Substitution in Instructions
|
||||
|
||||
Use template variables in agent prompts to make them dynamic:
|
||||
|
||||
```markdown
|
||||
# Agent Name
|
||||
|
||||
## Dynamic Parameters
|
||||
- **Project Name**: ${projectName}
|
||||
- **Base Path**: ${basePath}
|
||||
- **Output Directory**: ${outputDir}
|
||||
|
||||
## Your Mission
|
||||
|
||||
Process the **${projectName}** project located at `${basePath}`.
|
||||
|
||||
## Process Steps
|
||||
|
||||
1. Read input from: `${basePath}/input/`
|
||||
2. Process files according to project configuration
|
||||
3. Write results to: `${outputDir}/`
|
||||
4. Generate summary report
|
||||
|
||||
## Quality Standards
|
||||
|
||||
- Maintain project-specific coding standards for **${projectName}**
|
||||
- Follow directory structure: `${basePath}/[structure]`
|
||||
```
|
||||
|
||||
#### Passing Variables to Sub-Agents
|
||||
|
||||
When invoking a sub-agent, pass all context through template variables in the prompt:
|
||||
|
||||
```javascript
|
||||
// Extract and prepare variables
|
||||
const basePath = `projects/${projectName}`;
|
||||
const inputPath = `${basePath}/src/`;
|
||||
const outputPath = `${basePath}/docs/`;
|
||||
|
||||
// Pass to sub-agent with all variables substituted
|
||||
const result = await runSubagent({
|
||||
description: 'Generate project documentation',
|
||||
prompt: `You are the Documentation specialist.
|
||||
|
||||
Project: ${projectName}
|
||||
Input: ${inputPath}
|
||||
Output: ${outputPath}
|
||||
|
||||
Task:
|
||||
1. Read source files from ${inputPath}
|
||||
2. Generate comprehensive documentation
|
||||
3. Write to ${outputPath}/index.md
|
||||
4. Include code examples and usage guides
|
||||
|
||||
Return: Summary of documentation generated (file count, word count)`
|
||||
});
|
||||
```
|
||||
|
||||
The sub-agent receives all necessary context embedded in the prompt. Variables are resolved before sending the prompt, so the sub-agent works with concrete paths and values, not variable placeholders.
|
||||
|
||||
### Real-World Example: Code Review Orchestrator
|
||||
|
||||
Example of a simple orchestrator that validates code through multiple specialized agents:
|
||||
|
||||
```javascript
|
||||
async function reviewCodePipeline(repositoryName, prNumber) {
|
||||
const basePath = `projects/${repositoryName}/pr-${prNumber}`;
|
||||
|
||||
// Step 1: Security Review
|
||||
const security = await runSubagent({
|
||||
description: 'Scan for security vulnerabilities',
|
||||
prompt: `You are the Security Reviewer specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
PR: ${prNumber}
|
||||
Code: ${basePath}/changes/
|
||||
|
||||
Task:
|
||||
1. Scan code for OWASP Top 10 vulnerabilities
|
||||
2. Check for injection attacks, auth flaws
|
||||
3. Write findings to ${basePath}/security-review.md
|
||||
|
||||
Return: List of critical, high, and medium issues found`
|
||||
});
|
||||
|
||||
// Step 2: Test Coverage Check
|
||||
const coverage = await runSubagent({
|
||||
description: 'Verify test coverage for changes',
|
||||
prompt: `You are the Test Coverage specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
PR: ${prNumber}
|
||||
Changes: ${basePath}/changes/
|
||||
|
||||
Task:
|
||||
1. Analyze code coverage for modified files
|
||||
2. Identify untested critical paths
|
||||
3. Write report to ${basePath}/coverage-report.md
|
||||
|
||||
Return: Current coverage percentage and gaps`
|
||||
});
|
||||
|
||||
// Step 3: Aggregate Results
|
||||
const finalReport = await runSubagent({
|
||||
description: 'Compile all review findings',
|
||||
prompt: `You are the Review Aggregator specialist.
|
||||
|
||||
Repository: ${repositoryName}
|
||||
Reports: ${basePath}/*.md
|
||||
|
||||
Task:
|
||||
1. Read all review reports from ${basePath}/
|
||||
2. Synthesize findings into single report
|
||||
3. Determine overall verdict (APPROVE/NEEDS_FIXES/BLOCK)
|
||||
4. Write to ${basePath}/final-review.md
|
||||
|
||||
Return: Final verdict and executive summary`
|
||||
});
|
||||
|
||||
return finalReport;
|
||||
}
|
||||
```
|
||||
|
||||
This pattern applies to any orchestration scenario: extract variables, call sub-agents with clear context, await results.
|
||||
|
||||
|
||||
### Variable Best Practices
|
||||
|
||||
#### 1. **Clear Documentation**
|
||||
Always document what variables are expected:
|
||||
|
||||
```markdown
|
||||
## Required Variables
|
||||
- **projectName**: The name of the project (string, required)
|
||||
- **basePath**: Root directory for project files (path, required)
|
||||
|
||||
## Optional Variables
|
||||
- **mode**: Processing mode - quick/standard/detailed (enum, default: standard)
|
||||
- **outputFormat**: Output format - markdown/json/html (enum, default: markdown)
|
||||
|
||||
## Derived Variables
|
||||
- **outputDir**: Automatically set to ${basePath}/output
|
||||
- **logFile**: Automatically set to ${basePath}/.log.md
|
||||
```
|
||||
|
||||
#### 2. **Consistent Naming**
|
||||
Use consistent variable naming conventions:
|
||||
|
||||
```javascript
|
||||
// Good: Clear, descriptive naming
|
||||
const variables = {
|
||||
projectName, // What project to work on
|
||||
basePath, // Where project files are located
|
||||
outputDirectory, // Where to save results
|
||||
processingMode, // How to process (detail level)
|
||||
configurationPath // Where config files are
|
||||
};
|
||||
|
||||
// Avoid: Ambiguous or inconsistent
|
||||
const bad_variables = {
|
||||
name, // Too generic
|
||||
path, // Unclear which path
|
||||
mode, // Too short
|
||||
config // Too vague
|
||||
};
|
||||
```
|
||||
|
||||
#### 3. **Validation and Constraints**
|
||||
Document valid values and constraints:
|
||||
|
||||
```markdown
|
||||
## Variable Constraints
|
||||
|
||||
**projectName**:
|
||||
- Type: string (alphanumeric, hyphens, underscores allowed)
|
||||
- Length: 1-100 characters
|
||||
- Required: yes
|
||||
- Pattern: `/^[a-zA-Z0-9_-]+$/`
|
||||
|
||||
**processingMode**:
|
||||
- Type: enum
|
||||
- Valid values: "quick" (< 5min), "standard" (5-15min), "detailed" (15+ min)
|
||||
- Default: "standard"
|
||||
- Required: no
|
||||
```
|
||||
|
||||
## MCP Server Configuration (Organization/Enterprise Only)
|
||||
|
||||
MCP servers extend agent capabilities with additional tools. Only supported for organization and enterprise-level agents.
|
||||
|
||||
### Configuration Format
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-custom-agent
|
||||
description: 'Agent with MCP integration'
|
||||
tools: ['read', 'edit', 'custom-mcp/tool-1']
|
||||
mcp-servers:
|
||||
custom-mcp:
|
||||
type: 'local'
|
||||
command: 'some-command'
|
||||
args: ['--arg1', '--arg2']
|
||||
tools: ["*"]
|
||||
env:
|
||||
ENV_VAR_NAME: ${{ secrets.API_KEY }}
|
||||
---
|
||||
```
|
||||
|
||||
### MCP Server Properties
|
||||
|
||||
- **type**: Server type (`'local'` or `'stdio'`)
|
||||
- **command**: Command to start the MCP server
|
||||
- **args**: Array of command arguments
|
||||
- **tools**: Tools to enable from this server (`["*"]` for all)
|
||||
- **env**: Environment variables (supports secrets)
|
||||
|
||||
### Environment Variables and Secrets
|
||||
|
||||
Secrets must be configured in repository settings under "copilot" environment.
|
||||
|
||||
**Supported syntax**:
|
||||
```yaml
|
||||
env:
|
||||
# Environment variable only
|
||||
VAR_NAME: COPILOT_MCP_ENV_VAR_VALUE
|
||||
|
||||
# Variable with header
|
||||
VAR_NAME: $COPILOT_MCP_ENV_VAR_VALUE
|
||||
VAR_NAME: ${COPILOT_MCP_ENV_VAR_VALUE}
|
||||
|
||||
# GitHub Actions-style (YAML only)
|
||||
VAR_NAME: ${{ secrets.COPILOT_MCP_ENV_VAR_VALUE }}
|
||||
VAR_NAME: ${{ var.COPILOT_MCP_ENV_VAR_VALUE }}
|
||||
```
|
||||
|
||||
## File Organization and Naming
|
||||
|
||||
### Repository-Level Agents
|
||||
- Location: `.github/agents/`
|
||||
- Scope: Available only in the specific repository
|
||||
- Access: Uses repository-configured MCP servers
|
||||
|
||||
### Organization/Enterprise-Level Agents
|
||||
- Location: `.github-private/agents/` (then move to `agents/` root)
|
||||
- Scope: Available across all repositories in org/enterprise
|
||||
- Access: Can configure dedicated MCP servers
|
||||
|
||||
### Naming Conventions
|
||||
- Use lowercase with hyphens: `test-specialist.agent.md`
|
||||
- Name should reflect agent purpose
|
||||
- Filename becomes default agent name (if `name` not specified)
|
||||
- Allowed characters: `.`, `-`, `_`, `a-z`, `A-Z`, `0-9`
|
||||
|
||||
## Agent Processing and Behavior
|
||||
|
||||
### Versioning
|
||||
- Based on Git commit SHAs for the agent file
|
||||
- Create branches/tags for different agent versions
|
||||
- Instantiated using latest version for repository/branch
|
||||
- PR interactions use same agent version for consistency
|
||||
|
||||
### Name Conflicts
|
||||
Priority (highest to lowest):
|
||||
1. Repository-level agent
|
||||
2. Organization-level agent
|
||||
3. Enterprise-level agent
|
||||
|
||||
Lower-level configurations override higher-level ones with the same name.
|
||||
|
||||
### Tool Processing
|
||||
- `tools` list filters available tools (built-in and MCP)
|
||||
- No tools specified = all tools enabled
|
||||
- Empty list (`[]`) = all tools disabled
|
||||
- Specific list = only those tools enabled
|
||||
- Unrecognized tool names are ignored (allows environment-specific tools)
|
||||
|
||||
### MCP Server Processing Order
|
||||
1. Out-of-the-box MCP servers (e.g., GitHub MCP)
|
||||
2. Custom agent MCP configuration (org/enterprise only)
|
||||
3. Repository-level MCP configurations
|
||||
|
||||
Each level can override settings from previous levels.
|
||||
|
||||
## Agent Creation Checklist
|
||||
|
||||
### Frontmatter
|
||||
- [ ] `description` field present and descriptive (50-150 chars)
|
||||
- [ ] `description` wrapped in single quotes
|
||||
- [ ] `name` specified (optional but recommended)
|
||||
- [ ] `tools` configured appropriately (or intentionally omitted)
|
||||
- [ ] `model` specified for optimal performance
|
||||
- [ ] `target` set if environment-specific
|
||||
- [ ] `infer` set to `false` if manual selection required
|
||||
|
||||
### Prompt Content
|
||||
- [ ] Clear agent identity and role defined
|
||||
- [ ] Core responsibilities listed explicitly
|
||||
- [ ] Approach and methodology explained
|
||||
- [ ] Guidelines and constraints specified
|
||||
- [ ] Output expectations documented
|
||||
- [ ] Examples provided where helpful
|
||||
- [ ] Instructions are specific and actionable
|
||||
- [ ] Scope and boundaries clearly defined
|
||||
- [ ] Total content under 30,000 characters
|
||||
|
||||
### File Structure
|
||||
- [ ] Filename follows lowercase-with-hyphens convention
|
||||
- [ ] File placed in correct directory (`.github/agents/` or `agents/`)
|
||||
- [ ] Filename uses only allowed characters
|
||||
- [ ] File extension is `.agent.md`
|
||||
|
||||
### Quality Assurance
|
||||
- [ ] Agent purpose is unique and not duplicative
|
||||
- [ ] Tools are minimal and necessary
|
||||
- [ ] Instructions are clear and unambiguous
|
||||
- [ ] Agent has been tested with representative tasks
|
||||
- [ ] Documentation references are current
|
||||
- [ ] Security considerations addressed (if applicable)
|
||||
|
||||
## Common Agent Patterns
|
||||
|
||||
### Testing Specialist
|
||||
**Purpose**: Focus on test coverage and quality
|
||||
**Tools**: All tools (for comprehensive test creation)
|
||||
**Approach**: Analyze, identify gaps, write tests, avoid production code changes
|
||||
|
||||
### Implementation Planner
|
||||
**Purpose**: Create detailed technical plans and specifications
|
||||
**Tools**: Limited to `['read', 'search', 'edit']`
|
||||
**Approach**: Analyze requirements, create documentation, avoid implementation
|
||||
|
||||
### Code Reviewer
|
||||
**Purpose**: Review code quality and provide feedback
|
||||
**Tools**: `['read', 'search']` only
|
||||
**Approach**: Analyze, suggest improvements, no direct modifications
|
||||
|
||||
### Refactoring Specialist
|
||||
**Purpose**: Improve code structure and maintainability
|
||||
**Tools**: `['read', 'search', 'edit']`
|
||||
**Approach**: Analyze patterns, propose refactorings, implement safely
|
||||
|
||||
### Security Auditor
|
||||
**Purpose**: Identify security issues and vulnerabilities
|
||||
**Tools**: `['read', 'search', 'web']`
|
||||
**Approach**: Scan code, check against OWASP, report findings
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
### Frontmatter Errors
|
||||
- ❌ Missing `description` field
|
||||
- ❌ Description not wrapped in quotes
|
||||
- ❌ Invalid tool names without checking documentation
|
||||
- ❌ Incorrect YAML syntax (indentation, quotes)
|
||||
|
||||
### Tool Configuration Issues
|
||||
- ❌ Granting excessive tool access unnecessarily
|
||||
- ❌ Missing required tools for agent's purpose
|
||||
- ❌ Not using tool aliases consistently
|
||||
- ❌ Forgetting MCP server namespace (`server-name/tool`)
|
||||
|
||||
### Prompt Content Problems
|
||||
- ❌ Vague, ambiguous instructions
|
||||
- ❌ Conflicting or contradictory guidelines
|
||||
- ❌ Lack of clear scope definition
|
||||
- ❌ Missing output expectations
|
||||
- ❌ Overly verbose instructions (exceeding character limits)
|
||||
- ❌ No examples or context for complex tasks
|
||||
|
||||
### Organizational Issues
|
||||
- ❌ Filename doesn't reflect agent purpose
|
||||
- ❌ Wrong directory (confusing repo vs org level)
|
||||
- ❌ Using spaces or special characters in filename
|
||||
- ❌ Duplicate agent names causing conflicts
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
### Manual Testing
|
||||
1. Create the agent file with proper frontmatter
|
||||
2. Reload VS Code or refresh GitHub.com
|
||||
3. Select the agent from the dropdown in Copilot Chat
|
||||
4. Test with representative user queries
|
||||
5. Verify tool access works as expected
|
||||
6. Confirm output meets expectations
|
||||
|
||||
### Integration Testing
|
||||
- Test agent with different file types in scope
|
||||
- Verify MCP server connectivity (if configured)
|
||||
- Check agent behavior with missing context
|
||||
- Test error handling and edge cases
|
||||
- Validate agent switching and handoffs
|
||||
|
||||
### Quality Checks
|
||||
- Run through agent creation checklist
|
||||
- Review against common mistakes list
|
||||
- Compare with example agents in repository
|
||||
- Get peer review for complex agents
|
||||
- Document any special configuration needs
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Official Documentation
|
||||
- [Creating Custom Agents](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-custom-agents)
|
||||
- [Custom Agents Configuration](https://docs.github.com/en/copilot/reference/custom-agents-configuration)
|
||||
- [Custom Agents in VS Code](https://code.visualstudio.com/docs/copilot/customization/custom-agents)
|
||||
- [MCP Integration](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp)
|
||||
|
||||
### Community Resources
|
||||
- [Awesome Copilot Agents Collection](https://github.com/github/awesome-copilot/tree/main/agents)
|
||||
- [Customization Library Examples](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents)
|
||||
- [Your First Custom Agent Tutorial](https://docs.github.com/en/copilot/tutorials/customization-library/custom-agents/your-first-custom-agent)
|
||||
|
||||
### Related Files
|
||||
- [Prompt Files Guidelines](./prompt.instructions.md) - For creating prompt files
|
||||
- [Instructions Guidelines](./instructions.instructions.md) - For creating instruction files
|
||||
|
||||
## Version Compatibility Notes
|
||||
|
||||
### GitHub.com (Coding Agent)
|
||||
- ✅ Fully supports all standard frontmatter properties
|
||||
- ✅ Repository and org/enterprise level agents
|
||||
- ✅ MCP server configuration (org/enterprise)
|
||||
- ❌ Does not support `model`, `argument-hint`, `handoffs` properties
|
||||
|
||||
### VS Code / JetBrains / Eclipse / Xcode
|
||||
- ✅ Supports `model` property for AI model selection
|
||||
- ✅ Supports `argument-hint` and `handoffs` properties
|
||||
- ✅ User profile and workspace-level agents
|
||||
- ❌ Cannot configure MCP servers at repository level
|
||||
- ⚠️ Some properties may behave differently
|
||||
|
||||
When creating agents for multiple environments, focus on common properties and test in all target environments. Use `target` property to create environment-specific agents when necessary.
|
||||
@@ -1,187 +0,0 @@
|
||||
---
|
||||
description: 'Best practices for Azure DevOps Pipeline YAML files'
|
||||
applyTo: '**/azure-pipelines.yml, **/azure-pipelines*.yml, **/*.pipeline.yml'
|
||||
---
|
||||
|
||||
# Azure DevOps Pipeline YAML Best Practices
|
||||
|
||||
Guidelines for creating maintainable, secure, and efficient Azure DevOps pipelines in PowerToys.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
- Use YAML syntax consistently with proper indentation (2 spaces)
|
||||
- Always include meaningful names and display names for pipelines, stages, jobs, and steps
|
||||
- Implement proper error handling and conditional execution
|
||||
- Use variables and parameters to make pipelines reusable and maintainable
|
||||
- Follow the principle of least privilege for service connections and permissions
|
||||
- Include comprehensive logging and diagnostics for troubleshooting
|
||||
|
||||
## Pipeline Structure
|
||||
|
||||
- Organize complex pipelines using stages for better visualization and control
|
||||
- Use jobs to group related steps and enable parallel execution when possible
|
||||
- Implement proper dependencies between stages and jobs
|
||||
- Use templates for reusable pipeline components
|
||||
- Keep pipeline files focused and modular - split large pipelines into multiple files
|
||||
|
||||
## Build Best Practices
|
||||
|
||||
- Use specific agent pool versions and VM images for consistency
|
||||
- Cache dependencies (npm, NuGet, Maven, etc.) to improve build performance
|
||||
- Implement proper artifact management with meaningful names and retention policies
|
||||
- Use build variables for version numbers and build metadata
|
||||
- Include code quality gates (lint checks, testing, security scans)
|
||||
- Ensure builds are reproducible and environment-independent
|
||||
|
||||
## Testing Integration
|
||||
|
||||
- Run unit tests as part of the build process
|
||||
- Publish test results in standard formats (JUnit, VSTest, etc.)
|
||||
- Include code coverage reporting and quality gates
|
||||
- Implement integration and end-to-end tests in appropriate stages
|
||||
- Use test impact analysis when available to optimize test execution
|
||||
- Fail fast on test failures to provide quick feedback
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use Azure Key Vault for sensitive configuration and secrets
|
||||
- Implement proper secret management with variable groups
|
||||
- Use service connections with minimal required permissions
|
||||
- Enable security scans (dependency vulnerabilities, static analysis)
|
||||
- Implement approval gates for production deployments
|
||||
- Use managed identities when possible instead of service principals
|
||||
|
||||
## Deployment Strategies
|
||||
|
||||
- Implement proper environment promotion (dev → staging → production)
|
||||
- Use deployment jobs with proper environment targeting
|
||||
- Implement blue-green or canary deployment strategies when appropriate
|
||||
- Include rollback mechanisms and health checks
|
||||
- Use infrastructure as code (ARM, Bicep, Terraform) for consistent deployments
|
||||
- Implement proper configuration management per environment
|
||||
|
||||
## Variable and Parameter Management
|
||||
|
||||
- Use variable groups for shared configuration across pipelines
|
||||
- Implement runtime parameters for flexible pipeline execution
|
||||
- Use conditional variables based on branches or environments
|
||||
- Secure sensitive variables and mark them as secrets
|
||||
- Document variable purposes and expected values
|
||||
- Use variable templates for complex variable logic
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
- Use parallel jobs and matrix strategies when appropriate
|
||||
- Implement proper caching strategies for dependencies and build outputs
|
||||
- Use shallow clone for Git operations when full history isn't needed
|
||||
- Optimize Docker image builds with multi-stage builds and layer caching
|
||||
- Monitor pipeline performance and optimize bottlenecks
|
||||
- Use pipeline resource triggers efficiently
|
||||
|
||||
## Monitoring and Observability
|
||||
|
||||
- Include comprehensive logging throughout the pipeline
|
||||
- Use Azure Monitor and Application Insights for deployment tracking
|
||||
- Implement proper notification strategies for failures and successes
|
||||
- Include deployment health checks and automated rollback triggers
|
||||
- Use pipeline analytics to identify improvement opportunities
|
||||
- Document pipeline behavior and troubleshooting steps
|
||||
|
||||
## Template and Reusability
|
||||
|
||||
- Create pipeline templates for common patterns
|
||||
- Use extends templates for complete pipeline inheritance
|
||||
- Implement step templates for reusable task sequences
|
||||
- Use variable templates for complex variable logic
|
||||
- Version templates appropriately for stability
|
||||
- Document template parameters and usage examples
|
||||
|
||||
## Branch and Trigger Strategy
|
||||
|
||||
- Implement appropriate triggers for different branch types
|
||||
- Use path filters to trigger builds only when relevant files change
|
||||
- Configure proper CI/CD triggers for main/master branches
|
||||
- Use pull request triggers for code validation
|
||||
- Implement scheduled triggers for maintenance tasks
|
||||
- Consider resource triggers for multi-repository scenarios
|
||||
|
||||
## Example Structure
|
||||
|
||||
```yaml
|
||||
# azure-pipelines.yml
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
exclude:
|
||||
- docs/*
|
||||
- README.md
|
||||
|
||||
variables:
|
||||
- group: shared-variables
|
||||
- name: buildConfiguration
|
||||
value: 'Release'
|
||||
|
||||
stages:
|
||||
- stage: Build
|
||||
displayName: 'Build and Test'
|
||||
jobs:
|
||||
- job: Build
|
||||
displayName: 'Build Application'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .NET SDK'
|
||||
inputs:
|
||||
version: '8.x'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Restore dependencies'
|
||||
inputs:
|
||||
command: 'restore'
|
||||
projects: '**/*.csproj'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build application'
|
||||
inputs:
|
||||
command: 'build'
|
||||
projects: '**/*.csproj'
|
||||
arguments: '--configuration $(buildConfiguration) --no-restore'
|
||||
|
||||
- stage: Deploy
|
||||
displayName: 'Deploy to Staging'
|
||||
dependsOn: Build
|
||||
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
|
||||
jobs:
|
||||
- deployment: DeployToStaging
|
||||
displayName: 'Deploy to Staging Environment'
|
||||
environment: 'staging'
|
||||
strategy:
|
||||
runOnce:
|
||||
deploy:
|
||||
steps:
|
||||
- download: current
|
||||
displayName: 'Download drop artifact'
|
||||
artifact: drop
|
||||
- task: AzureWebApp@1
|
||||
displayName: 'Deploy to Azure Web App'
|
||||
inputs:
|
||||
azureSubscription: 'staging-service-connection'
|
||||
appType: 'webApp'
|
||||
appName: 'myapp-staging'
|
||||
package: '$(Pipeline.Workspace)/drop/**/*.zip'
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
|
||||
- Hardcoding sensitive values directly in YAML files
|
||||
- Using overly broad triggers that cause unnecessary builds
|
||||
- Mixing build and deployment logic in a single stage
|
||||
- Not implementing proper error handling and cleanup
|
||||
- Using deprecated task versions without upgrade plans
|
||||
- Creating monolithic pipelines that are difficult to maintain
|
||||
- Not using proper naming conventions for clarity
|
||||
- Ignoring pipeline security best practices
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for shared libraries including logging, IPC, settings, DPI, telemetry, and utilities consumed by multiple modules'
|
||||
applyTo: 'src/common/**'
|
||||
---
|
||||
|
||||
# Common Libraries – Shared Code Guidance
|
||||
|
||||
Guidelines for modifying shared code in `src/common/`. Changes here can have wide-reaching impact across the entire PowerToys codebase.
|
||||
|
||||
## Scope
|
||||
|
||||
- Logging infrastructure (`src/common/logger/`)
|
||||
- IPC primitives and named pipe utilities
|
||||
- Settings serialization and management
|
||||
- DPI awareness and scaling utilities
|
||||
- Telemetry helpers
|
||||
- General utilities (JSON parsing, string helpers, etc.)
|
||||
|
||||
## Guidelines
|
||||
|
||||
### API Stability
|
||||
|
||||
- Avoid breaking public headers/APIs; if changed, search & update all callers
|
||||
- Coordinate ABI-impacting struct/class layout changes; keep binary compatibility
|
||||
- When modifying public interfaces, grep the entire codebase for usages
|
||||
|
||||
### Performance
|
||||
|
||||
- Watch perf in hot paths (hooks, timers, serialization)
|
||||
- Avoid avoidable allocations in frequently called code
|
||||
- Profile changes that touch performance-sensitive areas
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Ask before adding third-party deps or changing serialization formats
|
||||
- New dependencies must be MIT-licensed or approved by PM team
|
||||
- Add any new external packages to `NOTICE.md`
|
||||
|
||||
### Logging
|
||||
|
||||
- C++ logging uses spdlog (`Logger::info`, `Logger::warn`, `Logger::error`, `Logger::debug`)
|
||||
- Initialize with `init_logger()` early in startup
|
||||
- Keep hot paths quiet – no logging in tight loops or hooks
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- No unintended ABI breaks
|
||||
- No noisy logs in hot paths
|
||||
- New non-obvious symbols briefly commented
|
||||
- All callers updated when interfaces change
|
||||
|
||||
## Code Style
|
||||
|
||||
- **C++**: Follow `.clang-format` in `src/`; use Modern C++ patterns per C++ Core Guidelines
|
||||
- **C#**: Follow `src/.editorconfig`; enforce StyleCop.Analyzers
|
||||
|
||||
## Validation
|
||||
|
||||
- Build: `tools\build\build.cmd` from `src/common/` folder
|
||||
- Verify no ABI breaks: grep for changed function/struct names across codebase
|
||||
- Check logs: ensure no new logging in performance-critical paths
|
||||
256
.github/instructions/instructions.instructions.md
vendored
256
.github/instructions/instructions.instructions.md
vendored
@@ -1,256 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating high-quality custom instruction files for GitHub Copilot'
|
||||
applyTo: '**/*.instructions.md'
|
||||
---
|
||||
|
||||
# Custom Instructions File Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable custom instruction files that guide GitHub Copilot in generating domain-specific code and following project conventions.
|
||||
|
||||
## Project Context
|
||||
|
||||
- Target audience: Developers and GitHub Copilot working with domain-specific code
|
||||
- File format: Markdown with YAML frontmatter
|
||||
- File naming convention: lowercase with hyphens (e.g., `react-best-practices.instructions.md`)
|
||||
- Location: `.github/instructions/` directory
|
||||
- Purpose: Provide context-aware guidance for code generation, review, and documentation
|
||||
|
||||
## Required Frontmatter
|
||||
|
||||
Every instruction file must include YAML frontmatter with the following fields:
|
||||
|
||||
```yaml
|
||||
---
|
||||
description: 'Brief description of the instruction purpose and scope'
|
||||
applyTo: 'glob pattern for target files (e.g., **/*.ts, **/*.py)'
|
||||
---
|
||||
```
|
||||
|
||||
### Frontmatter Guidelines
|
||||
|
||||
- **description**: Single-quoted string, 1-500 characters, clearly stating the purpose
|
||||
- **applyTo**: Glob pattern(s) specifying which files these instructions apply to
|
||||
- Single pattern: `'**/*.ts'`
|
||||
- Multiple patterns: `'**/*.ts, **/*.tsx, **/*.js'`
|
||||
- Specific files: `'src/**/*.py'`
|
||||
- All files: `'**'`
|
||||
|
||||
## File Structure
|
||||
|
||||
A well-structured instruction file should include the following sections:
|
||||
|
||||
### 1. Title and Overview
|
||||
|
||||
- Clear, descriptive title using `#` heading
|
||||
- Brief introduction explaining the purpose and scope
|
||||
- Optional: Project context section with key technologies and versions
|
||||
|
||||
### 2. Core Sections
|
||||
|
||||
Organize content into logical sections based on the domain:
|
||||
|
||||
- **General Instructions**: High-level guidelines and principles
|
||||
- **Best Practices**: Recommended patterns and approaches
|
||||
- **Code Standards**: Naming conventions, formatting, style rules
|
||||
- **Architecture/Structure**: Project organization and design patterns
|
||||
- **Common Patterns**: Frequently used implementations
|
||||
- **Security**: Security considerations (if applicable)
|
||||
- **Performance**: Optimization guidelines (if applicable)
|
||||
- **Testing**: Testing standards and approaches (if applicable)
|
||||
|
||||
### 3. Examples and Code Snippets
|
||||
|
||||
Provide concrete examples with clear labels:
|
||||
|
||||
```markdown
|
||||
### Good Example
|
||||
\`\`\`language
|
||||
// Recommended approach
|
||||
code example here
|
||||
\`\`\`
|
||||
|
||||
### Bad Example
|
||||
\`\`\`language
|
||||
// Avoid this pattern
|
||||
code example here
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 4. Validation and Verification (Optional but Recommended)
|
||||
|
||||
- Build commands to verify code
|
||||
- Lint checks and formatting tools
|
||||
- Testing requirements
|
||||
- Verification steps
|
||||
|
||||
## Content Guidelines
|
||||
|
||||
### Writing Style
|
||||
|
||||
- Use clear, concise language
|
||||
- Write in imperative mood ("Use", "Implement", "Avoid")
|
||||
- Be specific and actionable
|
||||
- Avoid ambiguous terms like "should", "might", "possibly"
|
||||
- Use bullet points and lists for readability
|
||||
- Keep sections focused and scannable
|
||||
|
||||
### Best Practices
|
||||
|
||||
- **Be Specific**: Provide concrete examples rather than abstract concepts
|
||||
- **Show Why**: Explain the reasoning behind recommendations when it adds value
|
||||
- **Use Tables**: For comparing options, listing rules, or showing patterns
|
||||
- **Include Examples**: Real code snippets are more effective than descriptions
|
||||
- **Stay Current**: Reference current versions and best practices
|
||||
- **Link Resources**: Include official documentation and authoritative sources
|
||||
|
||||
### Common Patterns to Include
|
||||
|
||||
1. **Naming Conventions**: How to name variables, functions, classes, files
|
||||
2. **Code Organization**: File structure, module organization, import order
|
||||
3. **Error Handling**: Preferred error handling patterns
|
||||
4. **Dependencies**: How to manage and document dependencies
|
||||
5. **Comments and Documentation**: When and how to document code
|
||||
6. **Version Information**: Target language/framework versions
|
||||
|
||||
## Patterns to Follow
|
||||
|
||||
### Bullet Points and Lists
|
||||
|
||||
```markdown
|
||||
## Security Best Practices
|
||||
|
||||
- Always validate user input before processing
|
||||
- Use parameterized queries to prevent SQL injection
|
||||
- Store secrets in environment variables, never in code
|
||||
- Implement proper authentication and authorization
|
||||
- Enable HTTPS for all production endpoints
|
||||
```
|
||||
|
||||
### Tables for Structured Information
|
||||
|
||||
```markdown
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution | Example |
|
||||
| ---------------- | ------------------- | ----------------------------- |
|
||||
| Magic numbers | Use named constants | `const MAX_RETRIES = 3` |
|
||||
| Deep nesting | Extract functions | Refactor nested if statements |
|
||||
| Hardcoded values | Use configuration | Store API URLs in config |
|
||||
```
|
||||
|
||||
### Code Comparison
|
||||
|
||||
```markdown
|
||||
### Good Example - Using TypeScript interfaces
|
||||
\`\`\`typescript
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
function getUser(id: string): User {
|
||||
// Implementation
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
### Bad Example - Using any type
|
||||
\`\`\`typescript
|
||||
function getUser(id: any): any {
|
||||
// Loses type safety
|
||||
}
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### Conditional Guidance
|
||||
|
||||
```markdown
|
||||
## Framework Selection
|
||||
|
||||
- **For small projects**: Use Minimal API approach
|
||||
- **For large projects**: Use controller-based architecture with clear separation
|
||||
- **For microservices**: Consider domain-driven design patterns
|
||||
```
|
||||
|
||||
## Patterns to Avoid
|
||||
|
||||
- **Overly verbose explanations**: Keep it concise and scannable
|
||||
- **Outdated information**: Always reference current versions and practices
|
||||
- **Ambiguous guidelines**: Be specific about what to do or avoid
|
||||
- **Missing examples**: Abstract rules without concrete code examples
|
||||
- **Contradictory advice**: Ensure consistency throughout the file
|
||||
- **Copy-paste from documentation**: Add value by distilling and providing context
|
||||
|
||||
## Testing Your Instructions
|
||||
|
||||
Before finalizing instruction files:
|
||||
|
||||
1. **Test with Copilot**: Try the instructions with actual prompts in VS Code
|
||||
2. **Verify Examples**: Ensure code examples are correct and run without errors
|
||||
3. **Check Glob Patterns**: Confirm `applyTo` patterns match intended files
|
||||
|
||||
## Example Structure
|
||||
|
||||
Here's a minimal example structure for a new instruction file:
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: 'Brief description of purpose'
|
||||
applyTo: '**/*.ext'
|
||||
---
|
||||
|
||||
# Technology Name Development
|
||||
|
||||
Brief introduction and context.
|
||||
|
||||
## General Instructions
|
||||
|
||||
- High-level guideline 1
|
||||
- High-level guideline 2
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Specific practice 1
|
||||
- Specific practice 2
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Naming Conventions
|
||||
- Rule 1
|
||||
- Rule 2
|
||||
|
||||
### File Organization
|
||||
- Structure 1
|
||||
- Structure 2
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1
|
||||
Description and example
|
||||
|
||||
\`\`\`language
|
||||
code example
|
||||
\`\`\`
|
||||
|
||||
### Pattern 2
|
||||
Description and example
|
||||
|
||||
## Validation
|
||||
|
||||
- Build command: `command to verify`
|
||||
- Lint checks: `command to lint`
|
||||
- Testing: `command to test`
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
- Review instructions when dependencies or frameworks are updated
|
||||
- Update examples to reflect current best practices
|
||||
- Remove outdated patterns or deprecated features
|
||||
- Add new patterns as they emerge in the community
|
||||
- Keep glob patterns accurate as project structure evolves
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Custom Instructions Documentation](https://code.visualstudio.com/docs/copilot/customization/custom-instructions)
|
||||
- [Awesome Copilot Instructions](https://github.com/github/awesome-copilot/tree/main/instructions)
|
||||
88
.github/instructions/prompt.instructions.md
vendored
88
.github/instructions/prompt.instructions.md
vendored
@@ -1,88 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for creating high-quality prompt files for GitHub Copilot'
|
||||
applyTo: '**/*.prompt.md'
|
||||
---
|
||||
|
||||
# Copilot Prompt Files Guidelines
|
||||
|
||||
Instructions for creating effective and maintainable prompt files that guide GitHub Copilot in delivering consistent, high-quality outcomes across any repository.
|
||||
|
||||
## Scope and Principles
|
||||
- Target audience: maintainers and contributors authoring reusable prompts for Copilot Chat.
|
||||
- Goals: predictable behaviour, clear expectations, minimal permissions, and portability across repositories.
|
||||
- Primary references: VS Code documentation on prompt files and organization-specific conventions.
|
||||
|
||||
## Frontmatter Requirements
|
||||
|
||||
Every prompt file should include YAML frontmatter with the following fields:
|
||||
|
||||
### Required/Recommended Fields
|
||||
|
||||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `description` | Recommended | A short description of the prompt (single sentence, actionable outcome) |
|
||||
| `name` | Optional | The name shown after typing `/` in chat. Defaults to filename if not specified |
|
||||
| `agent` | Recommended | The agent to use: `ask`, `edit`, `agent`, or a custom agent name. Defaults to current agent |
|
||||
| `model` | Optional | The language model to use. Defaults to currently selected model |
|
||||
| `tools` | Optional | List of tool/tool set names available for this prompt |
|
||||
| `argument-hint` | Optional | Hint text shown in chat input to guide user interaction |
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Use consistent quoting (single quotes recommended) and keep one field per line for readability and version control clarity
|
||||
- If `tools` are specified and current agent is `ask` or `edit`, the default agent becomes `agent`
|
||||
- Preserve any additional metadata (`language`, `tags`, `visibility`, etc.) required by your organization
|
||||
|
||||
## File Naming and Placement
|
||||
- Use kebab-case filenames ending with `.prompt.md` and store them under `.github/prompts/` unless your workspace standard specifies another directory.
|
||||
- Provide a short filename that communicates the action (for example, `generate-readme.prompt.md` rather than `prompt1.prompt.md`).
|
||||
|
||||
## Body Structure
|
||||
- Start with an `#` level heading that matches the prompt intent so it surfaces well in Quick Pick search.
|
||||
- Organize content with predictable sections. Recommended baseline: `Mission` or `Primary Directive`, `Scope & Preconditions`, `Inputs`, `Workflow` (step-by-step), `Output Expectations`, and `Quality Assurance`.
|
||||
- Adjust section names to fit the domain, but retain the logical flow: why → context → inputs → actions → outputs → validation.
|
||||
- Reference related prompts or instruction files using relative links to aid discoverability.
|
||||
|
||||
## Input and Context Handling
|
||||
- Use `${input:variableName[:placeholder]}` for required values and explain when the user must supply them. Provide defaults or alternatives where possible.
|
||||
- Call out contextual variables such as `${selection}`, `${file}`, `${workspaceFolder}` only when they are essential, and describe how Copilot should interpret them.
|
||||
- Document how to proceed when mandatory context is missing (for example, “Request the file path and stop if it remains undefined”).
|
||||
|
||||
## Tool and Permission Guidance
|
||||
- Limit `tools` to the smallest set that enables the task. List them in the preferred execution order when the sequence matters.
|
||||
- If the prompt inherits tools from a chat mode, mention that relationship and state any critical tool behaviours or side effects.
|
||||
- Warn about destructive operations (file creation, edits, terminal commands) and include guard rails or confirmation steps in the workflow.
|
||||
|
||||
## Instruction Tone and Style
|
||||
- Write in direct, imperative sentences targeted at Copilot (for example, “Analyze”, “Generate”, “Summarize”).
|
||||
- Keep sentences short and unambiguous, following Google Developer Documentation translation best practices to support localization.
|
||||
- Avoid idioms, humor, or culturally specific references; favor neutral, inclusive language.
|
||||
|
||||
## Output Definition
|
||||
- Specify the format, structure, and location of expected results (for example, “Create an architecture decision record file using the template below, such as `docs/architecture-decisions/record-XXXX.md`).
|
||||
- Include success criteria and failure triggers so Copilot knows when to halt or retry.
|
||||
- Provide validation steps—manual checks, automated commands, or acceptance criteria lists—that reviewers can execute after running the prompt.
|
||||
|
||||
## Examples and Reusable Assets
|
||||
- Embed Good/Bad examples or scaffolds (Markdown templates, JSON stubs) that the prompt should produce or follow.
|
||||
- Maintain reference tables (capabilities, status codes, role descriptions) inline to keep the prompt self-contained. Update these tables when upstream resources change.
|
||||
- Link to authoritative documentation instead of duplicating lengthy guidance.
|
||||
|
||||
## Quality Assurance Checklist
|
||||
- [ ] Frontmatter fields are complete, accurate, and least-privilege.
|
||||
- [ ] Inputs include placeholders, default behaviours, and fallbacks.
|
||||
- [ ] Workflow covers preparation, execution, and post-processing without gaps.
|
||||
- [ ] Output expectations include formatting and storage details.
|
||||
- [ ] Validation steps are actionable (commands, diff checks, review prompts).
|
||||
- [ ] Security, compliance, and privacy policies referenced by the prompt are current.
|
||||
- [ ] Prompt executes successfully in VS Code (`Chat: Run Prompt`) using representative scenarios.
|
||||
|
||||
## Maintenance Guidance
|
||||
- Version-control prompts alongside the code they affect; update them when dependencies, tooling, or review processes change.
|
||||
- Review prompts periodically to ensure tool lists, model requirements, and linked documents remain valid.
|
||||
- Coordinate with other repositories: when a prompt proves broadly useful, extract common guidance into instruction files or shared prompt packs.
|
||||
|
||||
## Additional Resources
|
||||
- [Prompt Files Documentation](https://code.visualstudio.com/docs/copilot/customization/prompt-files#_prompt-file-format)
|
||||
- [Awesome Copilot Prompt Files](https://github.com/github/awesome-copilot/tree/main/prompts)
|
||||
- [Tool Configuration](https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode#_agent-mode-tools)
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
description: 'Guidelines for Runner and Settings UI components that communicate via named pipes and manage module lifecycle'
|
||||
applyTo: 'src/runner/**,src/settings-ui/**'
|
||||
---
|
||||
|
||||
# Runner & Settings UI – Core Components Guidance
|
||||
|
||||
Guidelines for modifying the Runner (tray/module loader) and Settings UI (configuration app). These components communicate via Windows Named Pipes using JSON messages.
|
||||
|
||||
## Runner (`src/runner/`)
|
||||
|
||||
### Scope
|
||||
|
||||
- Module bootstrap, hotkey management, settings bridge, update/elevation handling
|
||||
|
||||
### Guidelines
|
||||
|
||||
- If IPC/JSON contracts change, mirror updates in `src/settings-ui/**`
|
||||
- Keep module discovery in `src/runner/main.cpp` in sync when adding/removing modules
|
||||
- Keep startup lean: avoid blocking/network calls in early init path
|
||||
- Preserve GPO & elevation behaviors; confirm no regression in policy handling
|
||||
- Ask before modifying update workflow or elevation logic
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Stable startup, consistent contracts, no unnecessary logging noise
|
||||
|
||||
## Settings UI (`src/settings-ui/`)
|
||||
|
||||
### Scope
|
||||
|
||||
- WinUI/WPF UI, communicates with Runner over named pipes; manages persisted settings schema
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Don't break settings schema silently; add migration when shape changes
|
||||
- If IPC/JSON contracts change, align with `src/runner/**` implementation
|
||||
- Keep UI responsive: marshal to UI thread for UI-bound operations
|
||||
- Reuse existing styles/resources; avoid duplicate theme keys
|
||||
- Add/adjust migration or serialization tests when changing persisted settings
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Schema integrity preserved, responsive UI, consistent contracts, no style duplication
|
||||
|
||||
## Shared Concerns
|
||||
|
||||
### IPC Contract Changes
|
||||
|
||||
When modifying the JSON message format between Runner and Settings UI:
|
||||
|
||||
1. Update both `src/runner/` and `src/settings-ui/` in the same PR
|
||||
2. Preserve backward compatibility where possible
|
||||
3. Add migration logic for settings schema changes
|
||||
4. Test both directions of communication
|
||||
|
||||
### Code Style
|
||||
|
||||
- **C++ (Runner)**: Follow `.clang-format` in `src/`
|
||||
- **C# (Settings UI)**: Follow `src/.editorconfig`, use StyleCop.Analyzers
|
||||
- **XAML**: Use XamlStyler or run `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
|
||||
## Validation
|
||||
|
||||
- Build Runner: `tools\build\build.cmd` from `src/runner/`
|
||||
- Build Settings UI: `tools\build\build.cmd` from `src/settings-ui/`
|
||||
- Test IPC: Launch both Runner and Settings UI, verify communication works
|
||||
- Schema changes: Run serialization tests if settings shape changed
|
||||
@@ -1,228 +0,0 @@
|
||||
---
|
||||
description: 'Instructions for building Model Context Protocol (MCP) servers using the TypeScript SDK'
|
||||
applyTo: '**/*.ts, **/*.js, **/package.json'
|
||||
---
|
||||
|
||||
# TypeScript MCP Server Development
|
||||
|
||||
## Instructions
|
||||
|
||||
- Use the **@modelcontextprotocol/sdk** npm package: `npm install @modelcontextprotocol/sdk`
|
||||
- Import from specific paths: `@modelcontextprotocol/sdk/server/mcp.js`, `@modelcontextprotocol/sdk/server/stdio.js`, etc.
|
||||
- Use `McpServer` class for high-level server implementation with automatic protocol handling
|
||||
- Use `Server` class for low-level control with manual request handlers
|
||||
- Use **zod** for input/output schema validation: `npm install zod@3`
|
||||
- Always provide `title` field for tools, resources, and prompts for better UI display
|
||||
- Use `registerTool()`, `registerResource()`, and `registerPrompt()` methods (recommended over older APIs)
|
||||
- Define schemas using zod: `{ inputSchema: { param: z.string() }, outputSchema: { result: z.string() } }`
|
||||
- Return both `content` (for display) and `structuredContent` (for structured data) from tools
|
||||
- For HTTP servers, use `StreamableHTTPServerTransport` with Express or similar frameworks
|
||||
- For local integrations, use `StdioServerTransport` for stdio-based communication
|
||||
- Create new transport instances per request to prevent request ID collisions (stateless mode)
|
||||
- Use session management with `sessionIdGenerator` for stateful servers
|
||||
- Enable DNS rebinding protection for local servers: `enableDnsRebindingProtection: true`
|
||||
- Configure CORS headers and expose `Mcp-Session-Id` for browser-based clients
|
||||
- Use `ResourceTemplate` for dynamic resources with URI parameters: `new ResourceTemplate('resource://{param}', { list: undefined })`
|
||||
- Support completions for better UX using `completable()` wrapper from `@modelcontextprotocol/sdk/server/completable.js`
|
||||
- Implement sampling with `server.server.createMessage()` to request LLM completions from clients
|
||||
- Use `server.server.elicitInput()` to request additional user input during tool execution
|
||||
- Enable notification debouncing for bulk updates: `debouncedNotificationMethods: ['notifications/tools/list_changed']`
|
||||
- Dynamic updates: call `.enable()`, `.disable()`, `.update()`, or `.remove()` on registered items to emit `listChanged` notifications
|
||||
- Use `getDisplayName()` from `@modelcontextprotocol/sdk/shared/metadataUtils.js` for UI display names
|
||||
- Test servers with MCP Inspector: `npx @modelcontextprotocol/inspector`
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Keep tool implementations focused on single responsibilities
|
||||
- Provide clear, descriptive titles and descriptions for LLM understanding
|
||||
- Use proper TypeScript types for all parameters and return values
|
||||
- Implement comprehensive error handling with try-catch blocks
|
||||
- Return `isError: true` in tool results for error conditions
|
||||
- Use async/await for all asynchronous operations
|
||||
- Close database connections and clean up resources properly
|
||||
- Validate input parameters before processing
|
||||
- Use structured logging for debugging without polluting stdout/stderr
|
||||
- Consider security implications when exposing file system or network access
|
||||
- Implement proper resource cleanup on transport close events
|
||||
- Use environment variables for configuration (ports, API keys, etc.)
|
||||
- Document tool capabilities and limitations clearly
|
||||
- Test with multiple clients to ensure compatibility
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Basic Server Setup (HTTP)
|
||||
```typescript
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import express from 'express';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'my-server',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/mcp', async (req, res) => {
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: undefined,
|
||||
enableJsonResponse: true
|
||||
});
|
||||
|
||||
res.on('close', () => transport.close());
|
||||
|
||||
await server.connect(transport);
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
```
|
||||
|
||||
### Basic Server Setup (stdio)
|
||||
```typescript
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
|
||||
const server = new McpServer({
|
||||
name: 'my-server',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
// ... register tools, resources, prompts ...
|
||||
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
```
|
||||
|
||||
### Simple Tool
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
server.registerTool(
|
||||
'calculate',
|
||||
{
|
||||
title: 'Calculator',
|
||||
description: 'Perform basic calculations',
|
||||
inputSchema: { a: z.number(), b: z.number(), op: z.enum(['+', '-', '*', '/']) },
|
||||
outputSchema: { result: z.number() }
|
||||
},
|
||||
async ({ a, b, op }) => {
|
||||
const result = op === '+' ? a + b : op === '-' ? a - b :
|
||||
op === '*' ? a * b : a / b;
|
||||
const output = { result };
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(output) }],
|
||||
structuredContent: output
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Dynamic Resource
|
||||
```typescript
|
||||
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
|
||||
server.registerResource(
|
||||
'user',
|
||||
new ResourceTemplate('users://{userId}', { list: undefined }),
|
||||
{
|
||||
title: 'User Profile',
|
||||
description: 'Fetch user profile data'
|
||||
},
|
||||
async (uri, { userId }) => ({
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: `User ${userId} data here`
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Tool with Sampling
|
||||
```typescript
|
||||
server.registerTool(
|
||||
'summarize',
|
||||
{
|
||||
title: 'Text Summarizer',
|
||||
description: 'Summarize text using LLM',
|
||||
inputSchema: { text: z.string() },
|
||||
outputSchema: { summary: z.string() }
|
||||
},
|
||||
async ({ text }) => {
|
||||
const response = await server.server.createMessage({
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: { type: 'text', text: `Summarize: ${text}` }
|
||||
}],
|
||||
maxTokens: 500
|
||||
});
|
||||
|
||||
const summary = response.content.type === 'text' ?
|
||||
response.content.text : 'Unable to summarize';
|
||||
const output = { summary };
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(output) }],
|
||||
structuredContent: output
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Prompt with Completion
|
||||
```typescript
|
||||
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
||||
|
||||
server.registerPrompt(
|
||||
'review',
|
||||
{
|
||||
title: 'Code Review',
|
||||
description: 'Review code with specific focus',
|
||||
argsSchema: {
|
||||
language: completable(z.string(), value =>
|
||||
['typescript', 'python', 'javascript', 'java']
|
||||
.filter(l => l.startsWith(value))
|
||||
),
|
||||
code: z.string()
|
||||
}
|
||||
},
|
||||
({ language, code }) => ({
|
||||
messages: [{
|
||||
role: 'user',
|
||||
content: {
|
||||
type: 'text',
|
||||
text: `Review this ${language} code:\n\n${code}`
|
||||
}
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
server.registerTool(
|
||||
'risky-operation',
|
||||
{
|
||||
title: 'Risky Operation',
|
||||
description: 'An operation that might fail',
|
||||
inputSchema: { input: z.string() },
|
||||
outputSchema: { result: z.string() }
|
||||
},
|
||||
async ({ input }) => {
|
||||
try {
|
||||
const result = await performRiskyOperation(input);
|
||||
const output = { result };
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(output) }],
|
||||
structuredContent: output
|
||||
};
|
||||
} catch (err: unknown) {
|
||||
const error = err as Error;
|
||||
return {
|
||||
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
35
.github/policies/resourceManagement.yml
vendored
35
.github/policies/resourceManagement.yml
vendored
@@ -233,30 +233,6 @@ configuration:
|
||||
- addReply:
|
||||
reply: Hi! Thanks for making us aware of the problem. We raised the issue with our internal localization team. This issue should be fixed hopefully in the next version of PowerToys.
|
||||
description:
|
||||
- if:
|
||||
- payloadType: Issue_Comment
|
||||
- commentContains:
|
||||
pattern: '\/need-monitor-info'
|
||||
isRegex: True
|
||||
- hasLabel:
|
||||
label: Product-Cursor Wrap
|
||||
- or:
|
||||
- activitySenderHasAssociation:
|
||||
association: Owner
|
||||
- activitySenderHasAssociation:
|
||||
association: Member
|
||||
- activitySenderHasAssociation:
|
||||
association: Collaborator
|
||||
then:
|
||||
- removeLabel:
|
||||
label: Needs-Triage
|
||||
- removeLabel:
|
||||
label: Needs-Team-Response
|
||||
- addLabel:
|
||||
label: Needs-Author-Feedback
|
||||
- addReply:
|
||||
reply: "To help debug your layout, please run [this script](https://github.com/microsoft/PowerToys/blob/main/src/modules/MouseUtils/CursorWrap/CursorWrapTests/Capture-MonitorLayout.ps1) and attach the generated JSON output to this thread.\n\nThis allows us to better understand the issue and investigate potential fixes."
|
||||
description:
|
||||
- if:
|
||||
- payloadType: Issue_Comment
|
||||
- commentContains:
|
||||
@@ -266,5 +242,16 @@ configuration:
|
||||
- addReply:
|
||||
reply: Hi! Your last comment indicates to our system, that you might want to contribute to this feature/fix this bug. Thank you! Please make us aware on our ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769), as we don't see all the comments. <br /><br />_I'm a bot (beep!) so please excuse any mistakes I may make_
|
||||
description:
|
||||
- if:
|
||||
- payloadType: Issues
|
||||
- isAction:
|
||||
action: Opened
|
||||
- bodyContains:
|
||||
pattern: 'Area\(s\) with issue\?\s*\nWorkspaces'
|
||||
isRegex: True
|
||||
then:
|
||||
- addLabel:
|
||||
label: Product-Workspaces
|
||||
description:
|
||||
onFailure:
|
||||
onSuccess:
|
||||
|
||||
57
.github/prompts/create-commit-title.prompt.md
vendored
57
.github/prompts/create-commit-title.prompt.md
vendored
@@ -1,49 +1,16 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Generate an 80-character git commit title for the local diff'
|
||||
mode: 'agent'
|
||||
model: Claude Sonnet 4.5
|
||||
description: 'Generate an 80-character git commit title for the local diff.'
|
||||
---
|
||||
|
||||
# Generate Commit Title
|
||||
**Goal:** Provide a ready-to-paste git commit title (<= 80 characters) that captures the most important local changes since `HEAD`.
|
||||
|
||||
## Purpose
|
||||
Provide a single-line, ready-to-paste git commit title (<= 80 characters) that reflects the most important local changes since `HEAD`.
|
||||
|
||||
## Input to collect
|
||||
- Run exactly one command to view the local diff:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
|
||||
## How to decide the title
|
||||
1. From the diff, find the dominant area (e.g., `src/modules/*`, `doc/devdocs/**`) and the change type (bug fix, docs update, config tweak).
|
||||
2. Draft an imperative, plain-ASCII title that:
|
||||
- Mentions the primary component when obvious (e.g., `FancyZones:` or `Docs:`)
|
||||
- Stays within 80 characters and has no trailing punctuation
|
||||
|
||||
## Final output
|
||||
- Reply with only the commit title on a single line—no extra text.
|
||||
|
||||
## PR title convention (when asked)
|
||||
Use Conventional Commits style:
|
||||
|
||||
`<type>(<scope>): <summary>`
|
||||
|
||||
**Allowed types**
|
||||
- feat, fix, docs, refactor, perf, test, build, ci, chore
|
||||
|
||||
**Scope rules**
|
||||
- Use a short, PowerToys-focused scope (one word preferred). Common scopes:
|
||||
- Core: `runner`, `settings-ui`, `common`, `docs`, `build`, `ci`, `installer`, `gpo`, `dsc`
|
||||
- Modules: `fancyzones`, `powerrename`, `awake`, `colorpicker`, `imageresizer`, `keyboardmanager`, `mouseutils`, `peek`, `hosts`, `file-locksmith`, `screen-ruler`, `text-extractor`, `cropandlock`, `paste`, `powerlauncher`
|
||||
- If unclear, pick the closest module or subsystem; omit only if unavoidable
|
||||
|
||||
**Summary rules**
|
||||
- Imperative, present tense (“add”, “update”, “remove”, “fix”)
|
||||
- Keep it <= 72 characters when possible; be specific, avoid “misc changes”
|
||||
|
||||
**Examples**
|
||||
- `feat(fancyzones): add canvas template duplication`
|
||||
- `fix(mouseutils): guard crosshair toggle when dpi info missing`
|
||||
- `docs(runner): document tray icon states`
|
||||
- `build(installer): align wix v5 suffix flag`
|
||||
- `ci(ci): cache pipeline artifacts for x64`
|
||||
**Workflow:**
|
||||
1. Run a single command to view the local diff since the last commit:
|
||||
```@terminal
|
||||
git diff HEAD
|
||||
```
|
||||
2. From that diff, identify the dominant area (reference key paths like `src/modules/*`, `doc/devdocs/**`, etc.), the type of change (bug fix, docs update, config tweak), and any notable impact.
|
||||
3. Draft a concise, imperative commit title summarizing the dominant change. Keep it plain ASCII, <= 80 characters, and avoid trailing punctuation. Mention the primary component when obvious (for example `FancyZones:` or `Docs:`).
|
||||
4. Respond with only the final commit title on a single line so it can be pasted directly into `git commit`.
|
||||
|
||||
8
.github/prompts/create-pr-summary.prompt.md
vendored
8
.github/prompts/create-pr-summary.prompt.md
vendored
@@ -1,10 +1,9 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff'
|
||||
mode: 'agent'
|
||||
model: Claude Sonnet 4.5
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff.'
|
||||
---
|
||||
|
||||
# Generate PR Summary
|
||||
|
||||
**Goal:** Produce a ready-to-paste PR title and description that follows PowerToys conventions by comparing the current branch against a user-selected target branch.
|
||||
|
||||
**Repo guardrails:**
|
||||
@@ -21,4 +20,3 @@ description: 'Generate a PowerToys-ready pull request description from the local
|
||||
5. Confirm validation: list tests executed with results or state why tests were skipped in line with repo guidance.
|
||||
6. Load `.github/pull_request_template.md`, mirror its section order, and populate it with the gathered facts. Include only relevant checklist entries, marking them `[x]/[ ]` and noting any intentional omissions as "N/A".
|
||||
7. Present the filled template inside a fenced ```markdown code block with no extra commentary so it is ready to paste into a PR, clearly flagging any placeholders that still need user input.
|
||||
8. Prepend the PR title above the filled template, applying the Conventional Commit type/scope rules from `.github/prompts/create-commit-title.prompt.md`; pick the dominant component from the diff and keep the title concise and imperative.
|
||||
|
||||
9
.github/prompts/fix-issue.prompt.md
vendored
9
.github/prompts/fix-issue.prompt.md
vendored
@@ -1,11 +1,10 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: " Execute the fix for a GitHub issue using the previously generated implementation plan. Apply code & tests directly in the repo. Output only a PR description (and optional manual steps)."
|
||||
---
|
||||
|
||||
# Fix GitHub Issue
|
||||
|
||||
## Dependencies
|
||||
# DEPENDENCY
|
||||
Source review prompt (for generating the implementation plan if missing):
|
||||
- .github/prompts/review-issue.prompt.md
|
||||
|
||||
|
||||
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
@@ -1,70 +0,0 @@
|
||||
---
|
||||
description: 'Fix active pull request comments with scoped changes'
|
||||
name: 'fix-pr-active-comments'
|
||||
agent: 'agent'
|
||||
argument-hint: 'PR number or active PR URL'
|
||||
---
|
||||
|
||||
# Fix Active PR Comments
|
||||
|
||||
## Mission
|
||||
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
|
||||
|
||||
## Scope & Preconditions
|
||||
- You must have an active pull request context or a provided PR number.
|
||||
- Only implement simple changes. Do not implement large refactors.
|
||||
- If required context is missing, request it and stop.
|
||||
|
||||
## Inputs
|
||||
- Required: ${input:pr_number:PR number or URL}
|
||||
- Optional: ${input:comment_scope:files or areas to focus on}
|
||||
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
|
||||
|
||||
## Workflow
|
||||
1. Locate all active (unresolved) PR review comments for the given PR.
|
||||
2. For each comment, classify the change scope:
|
||||
- Simple change: limited edits, localized fix, low risk, no broad redesign.
|
||||
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
|
||||
3. For each large refactor request:
|
||||
- Do not modify code.
|
||||
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
4. For each simple change request:
|
||||
- Implement the fix with minimal edits.
|
||||
- Run quick checks if needed.
|
||||
- Commit and push the change.
|
||||
5. For comments that seem invalid, unclear, or not applicable (even if simple):
|
||||
- Do not change code.
|
||||
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Consult back to the end user in a friendly, polite tone.
|
||||
6. Respond to each comment that you fixed:
|
||||
- Reply in the active conversation.
|
||||
- Use a polite or friendly tone.
|
||||
- Keep the response under 200 words.
|
||||
- Resolve the comment after replying.
|
||||
|
||||
## Output Expectations
|
||||
- Simple fixes: code changes committed and pushed.
|
||||
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Each fixed comment has a reply under 200 words and is resolved.
|
||||
|
||||
## Plan File Template
|
||||
Use this template for each large refactor item:
|
||||
|
||||
# Fix Plan: <short title>
|
||||
|
||||
## Context
|
||||
- Comment link:
|
||||
- Impacted areas:
|
||||
|
||||
## Overview Table Template
|
||||
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
|
||||
|
||||
| Comment link | Summary | Reason not applied | Suggested follow-up |
|
||||
| --- | --- | --- | --- |
|
||||
| | | | |
|
||||
|
||||
## Quality Assurance
|
||||
- Verify plan file path exists.
|
||||
- Ensure no code changes were made for large refactor items.
|
||||
- Confirm replies are under 200 words and comments are resolved.
|
||||
16
.github/prompts/fix-spelling.prompt.md
vendored
16
.github/prompts/fix-spelling.prompt.md
vendored
@@ -1,16 +1,15 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
mode: 'agent'
|
||||
model: GPT-5-Codex (Preview)
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR.'
|
||||
---
|
||||
|
||||
# Fix Spelling Comments
|
||||
|
||||
**Goal:** Clear every outstanding GitHub pull request comment created by the `Code scanning / check-spelling` workflow by explicitly allowing intentional terms.
|
||||
|
||||
**Guardrails:**
|
||||
- Update only discussion threads authored by `github-actions` or `github-actions[bot]` that mention `Code scanning results / check-spelling`.
|
||||
- Prefer improving the wording in the originally flagged file when it clarifies intent without changing meaning; if the wording is already clear/standard for the context, handle it via `.github/actions/spell-check/expect.txt` and reuse existing entries.
|
||||
- Limit edits to the flagged text and `.github/actions/spell-check/expect.txt`; leave all other files and topics untouched.
|
||||
- Resolve findings solely by editing `.github/actions/spell-check/expect.txt`; reuse existing entries.
|
||||
- Leave all other files and topics untouched.
|
||||
|
||||
**Prerequisites:**
|
||||
- Install GitHub CLI if it is not present: `winget install GitHub.cli`.
|
||||
@@ -19,6 +18,5 @@ description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
**Workflow:**
|
||||
1. Determine the active pull request with a single `gh pr view --json number` call (default to the current branch).
|
||||
2. Fetch all PR discussion data once via `gh pr view --json comments,reviews` and filter to check-spelling comments authored by `github-actions` or `github-actions[bot]` that are not minimized; when several remain, process only the most recent comment body.
|
||||
3. For each flagged token, first consider tightening or rephrasing the original text to avoid the false positive while keeping the meaning intact; if the existing wording is already normal and professional for the context, proceed to allowlisting instead of changing it.
|
||||
4. When allowlisting, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
5. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
3. For each flagged token, review `.github/actions/spell-check/expect.txt` for an equivalent term (for example an existing lowercase variant); when found, reuse that normalized term rather than adding a new entry, even if the flagged token differs only by casing. Only add a new entry after confirming no equivalent already exists.
|
||||
4. Add any remaining missing token to `.github/actions/spell-check/expect.txt`, keeping surrounding formatting intact.
|
||||
21
.github/prompts/review-issue.prompt.md
vendored
21
.github/prompts/review-issue.prompt.md
vendored
@@ -1,27 +1,20 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
|
||||
mode: 'agent'
|
||||
model: Claude Sonnet 4.5
|
||||
description: "You are github issue review and planning expertise, Score (0–100) and write one Implementation Plan. Outputs: overview.md, implementation-plan.md."
|
||||
---
|
||||
|
||||
# Review GitHub Issue
|
||||
|
||||
## Goal
|
||||
# GOAL
|
||||
For **#{{issue_number}}** produce:
|
||||
1) `Generated Files/issueReview/{{issue_number}}/overview.md`
|
||||
2) `Generated Files/issueReview/{{issue_number}}/implementation-plan.md`
|
||||
|
||||
## Inputs
|
||||
Figure out required inputs {{issue_number}} from the invocation context; if anything is missing, ask for the value or note it as a gap.
|
||||
figure out from the prompt on the
|
||||
|
||||
# CONTEXT (brief)
|
||||
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, download images via MCP `github_issue_images` to better understand the issue context. Finally, use MCP `github_issue_attachments` to download logs with parameter `extractFolder` as `Generated Files/issueReview/{{issue_number}}/logs`, and analyze the downloaded logs if available to identify relevant issues. Locate the source code in the current workspace (use `rg`/`git grep` as needed). Link related issues and PRs.
|
||||
|
||||
## When to call MCP tools
|
||||
If the following MCP "github-artifacts" tools are available in the environment, use them:
|
||||
- `github_issue_images`: use when the issue/PR likely contains screenshots or other visual evidence (UI bugs, glitches, design problems).
|
||||
- `github_issue_attachments`: use when the issue/PR mentions attached ZIPs (PowerToysReport_*.zip, logs.zip, debug.zip) or asks to analyze logs/diagnostics. Always provide `extractFolder` as `Generated Files/issueReview/{{issue_number}}/logs`
|
||||
|
||||
If these tools are not available (not listed by the runtime), start the MCP server "github-artifacts" first.
|
||||
Ground evidence using `gh issue view {{issue_number}} --json number,title,body,author,createdAt,updatedAt,state,labels,milestone,reactions,comments,linkedPullRequests`, and download the image for understand the context of the issue more.
|
||||
Locate source code in current workspace, but also free feel to use via `rg`/`git grep`. Link related issues/PRs.
|
||||
|
||||
# OVERVIEW.MD
|
||||
## Summary
|
||||
|
||||
7
.github/prompts/review-pr.prompt.md
vendored
7
.github/prompts/review-pr.prompt.md
vendored
@@ -1,9 +1,10 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
|
||||
mode: 'agent'
|
||||
model: Claude Sonnet 4.5
|
||||
description: "gh-driven PR review; per-step Markdown + machine-readable outputs"
|
||||
---
|
||||
|
||||
# Review Pull Request
|
||||
# PR Review — gh + stepwise
|
||||
|
||||
**Goal**: Given `{{pr_number}}`, run a *one-topic-per-step* review. Write files to `Generated Files/prReview/{{pr_number}}/` (replace `{{pr_number}}` with the integer). Emit machine‑readable blocks for a GitHub MCP to post review comments.
|
||||
|
||||
|
||||
201
.github/skills/release-note-generation/LICENSE.txt
vendored
201
.github/skills/release-note-generation/LICENSE.txt
vendored
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2026 Microsoft Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
140
.github/skills/release-note-generation/SKILL.md
vendored
140
.github/skills/release-note-generation/SKILL.md
vendored
@@ -1,140 +0,0 @@
|
||||
---
|
||||
name: release-note-generation
|
||||
description: Toolkit for generating PowerToys release notes from GitHub milestone PRs or commit ranges. Use when asked to create release notes, summarize milestone PRs, generate changelog, prepare release documentation, generate PR review summaries locally for release notes, update README for a new release, manage PR milestones, collect PRs between commits/tags, or prepare release assets (download installers and compute installer hashes).
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# Release Note Generation Skill
|
||||
|
||||
Generate professional release notes for PowerToys milestones by collecting merged PRs, summarizing each PR with the local CLI agent, grouping by label, and producing user-facing summaries.
|
||||
|
||||
## Output Directory
|
||||
|
||||
All generated artifacts are placed under `Generated Files/ReleaseNotes/` at the repository root (gitignored).
|
||||
|
||||
```
|
||||
Generated Files/ReleaseNotes/
|
||||
├── milestone_prs.json # Raw PR data from GitHub
|
||||
├── sorted_prs.csv # Sorted PR list with Copilot summaries
|
||||
├── prs_with_milestone.csv # Milestone assignment tracking
|
||||
├── grouped_csv/ # PRs grouped by label (one CSV per label)
|
||||
├── grouped_md/ # Generated markdown summaries per label
|
||||
└── v{VERSION}-release-notes.md # Final consolidated release notes
|
||||
```
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Generate release notes for a milestone
|
||||
- Summarize PRs merged in a release
|
||||
- Generate per-PR review summaries locally for release-notes copy
|
||||
- Assign milestones to PRs missing them
|
||||
- Collect PRs between two commits/tags
|
||||
- Update README.md for a new version
|
||||
- Prepare GitHub release assets (download installers/symbols + compute hashes)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **GitHub CLI (`gh`) installed and authenticated** — The collection script uses `gh pr view` and `gh api graphql` to fetch PR metadata and co-author information. Run `gh auth status` to verify; if not logged in, run `gh auth login` first. See [Step 1.0.0](./references/step1-collection.md) for details.
|
||||
- MCP Server: github-mcp-server installed (used to fetch PR diffs/files for the local-agent review step)
|
||||
- For [prepare-release-assets.ps1](./scripts/prepare-release-assets.ps1) only: **Azure CLI** authenticated against the Microsoft tenant (`az login`) with the `azure-devops` extension; access to the `microsoft/Dart` ADO project
|
||||
|
||||
## Required Variables
|
||||
|
||||
⚠️ **Before starting**, confirm `{{ReleaseVersion}}` with the user. If not provided, **ASK**: "What release version are we generating notes for? (e.g., 0.98)"
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{ReleaseVersion}}` | Target release version | `0.98` |
|
||||
|
||||
## Workflow Overview
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 1.0 Verify gh auth + MemberList │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 1.1 Collect PRs (stable range) │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 1.2 Assign Milestones │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 2.1–2.4 Label PRs (auto+human) │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 3.1 Local-agent PR summaries │
|
||||
│ (writes CopilotSummary) │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 3.2 (Optional) Refresh PR data │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 3.3 Group by label │
|
||||
│ (grouped_csv) │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 4.1 Summarize (grouped_md) │
|
||||
└────────────────────────────────┘
|
||||
↓
|
||||
┌────────────────────────────────┐
|
||||
│ 4.2 Final notes (v{VERSION}.md) │
|
||||
└────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Step | Action | Details |
|
||||
|------|--------|---------|
|
||||
| 1.0 | Verify prerequisites | `gh auth status` must pass; generate MemberList.md |
|
||||
| 1.1 | Collect PRs | From previous release tag on `stable` branch → `sorted_prs.csv` |
|
||||
| 1.2 | Assign Milestones | Ensure all PRs have correct milestone |
|
||||
| 2.1–2.4 | Label PRs | Auto-suggest + human label low-confidence |
|
||||
| 3.1–3.3 | Reviews & Grouping | Local agent summarizes each PR diff into `CopilotSummary` → (optional refresh) → group by label |
|
||||
| 4.1–4.2 | Summaries & Final | Generate grouped summaries, then consolidate |
|
||||
|
||||
## Detailed workflow docs
|
||||
|
||||
Do not read all steps at once—only read the step you are executing.
|
||||
|
||||
- [Step 1: Collection & Milestones](./references/step1-collection.md)
|
||||
- [Step 2: Labeling PRs](./references/step2-labeling.md)
|
||||
- [Step 3: Reviews & Grouping](./references/step3-review-grouping.md)
|
||||
- [Step 4: Summarization](./references/step4-summarization.md)
|
||||
|
||||
|
||||
## Available Scripts
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| [dump-prs-since-commit.ps1](./scripts/dump-prs-since-commit.ps1) | Fetch PRs between commits/tags |
|
||||
| [group-prs-by-label.ps1](./scripts/group-prs-by-label.ps1) | Group PRs into CSVs |
|
||||
| [collect-or-apply-milestones.ps1](./scripts/collect-or-apply-milestones.ps1) | Assign milestones |
|
||||
| [diff_prs.ps1](./scripts/diff_prs.ps1) | Incremental PR diff |
|
||||
| [prepare-release-assets.ps1](./scripts/prepare-release-assets.ps1) | Download installers + symbols from an ADO build, compute SHA256, emit the "Installer Hashes" markdown table for the GitHub release page |
|
||||
|
||||
## References
|
||||
|
||||
- [Sample Output](./references/SampleOutput.md) - Example summary formatting
|
||||
- [Detailed Instructions](./references/Instruction.md) - Legacy full documentation
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Terminal usage**: Disabled by default; only run scripts when user explicitly requests
|
||||
- **Batch generation**: Generate ALL grouped_md files in one pass, then human reviews
|
||||
- **PR order**: Preserve order from `sorted_prs.csv` in all outputs
|
||||
- **Label filtering**: Keeps `Product-*`, `Area-*`, `GitHub*`, `*Plugin`, `Issue-*`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| `gh` command not found | Install GitHub CLI and add to PATH |
|
||||
| No PRs returned | Verify milestone title matches exactly |
|
||||
| Empty `CopilotSummary` for many PRs | Run Step 3.1 (local-agent summaries). Do **not** use `mcp_github_request_copilot_review` from a CLI/coding agent — the GitHub API rejects bot-initiated review requests, so the column will stay empty. |
|
||||
| Many unlabeled PRs | Return to labeling step before grouping |
|
||||
| `prepare-release-assets.ps1` fails with "Failed to acquire ADO access token" | Run `az login` and ensure you have access to the `microsoft/Dart` ADO project |
|
||||
@@ -1,9 +0,0 @@
|
||||
- Added mouse button actions so you can choose what left, right, or middle click does in [#1234](https://github.com/microsoft/PowerToys/pull/1234) by [@PesBandi](https://github.com/PesBandi)
|
||||
|
||||
- Aligned window styling with current Windows theme for a cleaner look in [#1235](https://github.com/microsoft/PowerToys/pull/1235) by [@sadirano](https://github.com/sadirano)
|
||||
|
||||
- Ensured screen readers are notified when the selected item in the list changes for better accessibility in [#1236](https://github.com/microsoft/PowerToys/pull/1236)
|
||||
|
||||
- Implemented configurable UI test pipeline that can use pre-built official releases instead of building everything from scratch, reducing test execution time from 2+ hours in [#1237](https://github.com/microsoft/PowerToys/pull/1237)
|
||||
|
||||
- Fixed Alt+Left Arrow navigation not working when search box contains text in [#1238](https://github.com/microsoft/PowerToys/pull/1238) by [@jiripolasek](https://github.com/jiripolasek)
|
||||
@@ -1,174 +0,0 @@
|
||||
# Step 1: Collection and Milestones
|
||||
|
||||
## 1.0 To-do
|
||||
- 1.0.0 Verify GitHub CLI authentication (REQUIRED)
|
||||
- 1.0.1 Generate MemberList.md (REQUIRED)
|
||||
- 1.1 Collect PRs
|
||||
- 1.2 Assign Milestones (REQUIRED)
|
||||
|
||||
## Required Variables
|
||||
|
||||
⚠️ **Before starting**, confirm these values with the user:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{ReleaseVersion}}` | Target release version | `0.97` |
|
||||
| `{{PreviousReleaseTag}}` | Previous release tag from releases page | `v0.96.1` |
|
||||
|
||||
**If user hasn't specified `{{ReleaseVersion}}`, ASK:** "What release version are we generating notes for? (e.g., 0.97)"
|
||||
|
||||
**`{{PreviousReleaseTag}}` is derived from the releases page, not user input.** Use the latest published release tag (top of the page). You will use its tag name and tag commit SHA in Step 1.
|
||||
|
||||
---
|
||||
|
||||
## 1.0.0 Verify GitHub CLI Authentication (REQUIRED)
|
||||
|
||||
⚠️ **BLOCKING:** The collection script requires an authenticated `gh` CLI to fetch PR metadata and co-author information via GitHub's GraphQL API. Without authentication, PR data and `NeedThanks` attribution will be incomplete.
|
||||
|
||||
### Check authentication status
|
||||
|
||||
```powershell
|
||||
gh auth status
|
||||
```
|
||||
|
||||
**If authenticated:** You'll see `Logged in to github.com account <username>`. Proceed to 1.0.1.
|
||||
|
||||
**If NOT authenticated:** Run the login flow before continuing:
|
||||
|
||||
```powershell
|
||||
# Interactive login (opens browser for OAuth)
|
||||
gh auth login --hostname github.com --web
|
||||
|
||||
# Or use a personal access token
|
||||
gh auth login --with-token <<< "YOUR_GITHUB_TOKEN"
|
||||
```
|
||||
|
||||
**Required scopes:** `repo` (for reading PR data and assigning milestones)
|
||||
|
||||
After login, verify again with `gh auth status` and confirm exit code 0.
|
||||
|
||||
---
|
||||
|
||||
## 1.0.1 Generate MemberList.md (REQUIRED)
|
||||
|
||||
Create `Generated Files/ReleaseNotes/MemberList.md` from the **PowerToys core team** section in [COMMUNITY.md](../../../COMMUNITY.md).
|
||||
|
||||
Rules:
|
||||
- One GitHub username per line, **no** `@` prefix.
|
||||
- Use the usernames exactly as listed in the core team section.
|
||||
- Do not include former team members or other sections.
|
||||
|
||||
Example (format only):
|
||||
```
|
||||
example-user
|
||||
another-user
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1.1 Collect PRs
|
||||
|
||||
### 1.1.1 Get the previous release commit
|
||||
|
||||
1. Open the [PowerToys releases page](https://github.com/microsoft/PowerToys/releases/)
|
||||
2. Find the latest release (e.g., v0.96.1, which should be at the top)
|
||||
3. Set `{{PreviousReleaseTag}}` to that tag name (e.g., `v0.96.1`)
|
||||
4. Copy the full tag commit SHA as `{{SHALastRelease}}`
|
||||
|
||||
|
||||
**If the release SHA is not in your branch history:** Use the helper script to find an equivalent commit on the target branch by matching the commit title:
|
||||
|
||||
```powershell
|
||||
pwsh ./.github/skills/release-note-generation/scripts/find-commit-by-title.ps1 `
|
||||
-Commit '{{SHALastRelease}}' `
|
||||
-Branch 'stable'
|
||||
```
|
||||
|
||||
### 1.1.2 Run collection script against stable branch
|
||||
|
||||
```powershell
|
||||
# Collect PRs from previous release to current HEAD of stable branch
|
||||
pwsh ./.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1 `
|
||||
-StartCommit '{{SHALastRelease}}' `
|
||||
-Branch 'stable' `
|
||||
-OutputDir 'Generated Files/ReleaseNotes'
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `-StartCommit` - Previous release tag or commit SHA (exclusive)
|
||||
- `-Branch` - Always use `stable` branch, not `main` (script uses `origin/stable` as the end ref)
|
||||
- `-EndCommit` - Optional override if you need a custom end ref
|
||||
- `-OutputDir` - Output directory for generated files
|
||||
|
||||
**Reliability check:** If the script reports “No commits found”, the stable branch has not moved since the last release. In that case, either:
|
||||
- Confirm this is expected and stop (no new release notes), or
|
||||
- Re-run against `main` to gather pending changes for the next release cycle.
|
||||
|
||||
The script detects both merge commits (`Merge pull request #12345`) and squash commits (`Feature (#12345)`).
|
||||
|
||||
**Output** (in `Generated Files/ReleaseNotes/`):
|
||||
- `milestone_prs.json` - raw PR data
|
||||
- `sorted_prs.csv` - sorted PR list with columns: Id, Title, Labels, Author, Url, Body, CopilotSummary, NeedThanks
|
||||
- `Author`: Comma-separated list of all contributors (PR opener + co-authors from commit trailers)
|
||||
- `NeedThanks`: Comma-separated list of external contributors to thank (excludes core team members from MemberList.md). Empty string means no thanks needed.
|
||||
|
||||
---
|
||||
|
||||
## 1.2 Assign Milestones (REQUIRED)
|
||||
|
||||
**Before generating release notes**, ensure all collected PRs have the correct milestone assigned.
|
||||
|
||||
⚠️ **CRITICAL:** Do NOT proceed to labeling until all PRs have milestones assigned.
|
||||
|
||||
### 1.2.1 Check current milestone status (dry run)
|
||||
|
||||
```powershell
|
||||
# Dry run first to see what would be changed:
|
||||
pwsh ./.github/skills/release-note-generation/scripts/collect-or-apply-milestones.ps1 `
|
||||
-InputCsv 'Generated Files/ReleaseNotes/sorted_prs.csv' `
|
||||
-OutputCsv 'Generated Files/ReleaseNotes/prs_with_milestone.csv' `
|
||||
-DefaultMilestone 'PowerToys {{ReleaseVersion}}' `
|
||||
-ApplyMissing -WhatIf
|
||||
```
|
||||
|
||||
This queries GitHub for each PR's current milestone and shows which PRs would be updated.
|
||||
|
||||
### 1.2.2 Apply milestones to PRs missing them
|
||||
|
||||
```powershell
|
||||
# Apply for real:
|
||||
pwsh ./.github/skills/release-note-generation/scripts/collect-or-apply-milestones.ps1 `
|
||||
-InputCsv 'Generated Files/ReleaseNotes/sorted_prs.csv' `
|
||||
-OutputCsv 'Generated Files/ReleaseNotes/prs_with_milestone.csv' `
|
||||
-DefaultMilestone 'PowerToys {{ReleaseVersion}}' `
|
||||
-ApplyMissing
|
||||
```
|
||||
|
||||
**Script Behavior:**
|
||||
- Queries each PR's current milestone from GitHub
|
||||
- PRs that already have a milestone are **skipped** (not overwritten)
|
||||
- PRs missing a milestone get the default milestone applied
|
||||
- Outputs `prs_with_milestone.csv` with (Id, Milestone) columns
|
||||
- Produces summary: `Updated=X Skipped=Y Failed=Z`
|
||||
|
||||
**Validation:** After assignment, all PRs in `prs_with_milestone.csv` should have the target milestone.
|
||||
|
||||
---
|
||||
|
||||
## Additional Commands
|
||||
|
||||
### Collect milestones only (no changes to GitHub)
|
||||
```powershell
|
||||
pwsh ./.github/skills/release-note-generation/scripts/collect-or-apply-milestones.ps1 `
|
||||
-InputCsv 'Generated Files/ReleaseNotes/sorted_prs.csv' `
|
||||
-OutputCsv 'Generated Files/ReleaseNotes/prs_with_milestone.csv'
|
||||
```
|
||||
|
||||
### Local assignment only (fill blanks in CSV, no GitHub changes)
|
||||
```powershell
|
||||
pwsh ./.github/skills/release-note-generation/scripts/collect-or-apply-milestones.ps1 `
|
||||
-InputCsv 'Generated Files/ReleaseNotes/sorted_prs.csv' `
|
||||
-OutputCsv 'Generated Files/ReleaseNotes/prs_with_milestone.csv' `
|
||||
-DefaultMilestone 'PowerToys {{ReleaseVersion}}' `
|
||||
-LocalAssign
|
||||
```
|
||||
@@ -1,131 +0,0 @@
|
||||
# Step 2: Label Unlabeled PRs
|
||||
|
||||
## 2.0 To-do
|
||||
- 2.1 Identify unlabeled PRs (Agent Mode)
|
||||
- 2.2 Suggest labels (Agent Mode)
|
||||
- 2.3 Human label low-confidence PRs
|
||||
- 2.4 Recheck labels, delete Unlabeled.csv, and re-collect
|
||||
|
||||
**Before grouping**, ensure all PRs have appropriate labels for categorization.
|
||||
|
||||
⚠️ **CRITICAL:** Do NOT proceed to grouping until all PRs have labels assigned. PRs without labels will end up in `Unlabeled.csv` and won't appear in the correct release note sections.
|
||||
|
||||
## 2.1 Identify unlabeled PRs (Agent Mode)
|
||||
|
||||
Read `sorted_prs.csv` and identify PRs with empty or missing `Labels` column.
|
||||
|
||||
For each unlabeled PR, analyze:
|
||||
- **Title** - Often contains module name or feature
|
||||
- **Body** - PR description with context
|
||||
- **CopilotSummary** - AI-generated summary of changes
|
||||
|
||||
## 2.2 Suggest labels (Agent Mode)
|
||||
|
||||
For each unlabeled PR, suggest an appropriate label based on the content analysis.
|
||||
|
||||
**Output:** Create `Generated Files/ReleaseNotes/prs_label_review.md` with the following format:
|
||||
|
||||
```markdown
|
||||
# PR Label Review
|
||||
|
||||
Generated: YYYY-MM-DD HH:mm:ss
|
||||
|
||||
## Summary
|
||||
- Total unlabeled PRs: X
|
||||
- High confidence: X
|
||||
- Medium confidence: X
|
||||
- Low confidence: X
|
||||
|
||||
---
|
||||
|
||||
## PRs Needing Review (sorted by confidence, low first)
|
||||
|
||||
| PR | Title | Suggested Label | Confidence | Reason |
|
||||
|----|-------|-----------------|------------|--------|
|
||||
| [#12347](url) | Some generic fix | ??? | Low | Unclear from content |
|
||||
| [#12346](url) | Update dependencies | `Area-Build` | Medium | Body mentions NuGet packages |
|
||||
```
|
||||
|
||||
Sort by confidence (low first) so human reviews uncertain ones first.
|
||||
|
||||
After writing `prs_label_review.md`, **generate `prs_to_label.csv`, apply labels, and re-run collection** so the CSV/labels stay in sync:
|
||||
|
||||
```powershell
|
||||
# Generate CSV from suggestions (agent)
|
||||
# Apply labels
|
||||
pwsh ./.github/skills/release-note-generation/scripts/apply-labels.ps1 `
|
||||
-InputCsv 'Generated Files/ReleaseNotes/prs_to_label.csv'
|
||||
|
||||
# Refresh collection
|
||||
pwsh ./.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1 `
|
||||
-StartCommit '{{PreviousReleaseTag}}' -Branch 'stable' `
|
||||
-OutputDir 'Generated Files/ReleaseNotes'
|
||||
```
|
||||
|
||||
## 2.3 Human label low-confidence PRs
|
||||
|
||||
Ask the human to label **low-confidence** PRs directly (in GitHub). Skip any they decide not to label.
|
||||
|
||||
## 2.4 Recheck labels, delete Unlabeled.csv, and re-collect
|
||||
|
||||
Recheck that all PRs now have labels. Delete `Unlabeled.csv` (if present), then re-run the collection script to update `sorted_prs.csv`:
|
||||
|
||||
```powershell
|
||||
# Remove stale unlabeled output if it exists
|
||||
Remove-Item 'Generated Files/ReleaseNotes/Unlabeled.csv' -ErrorAction SilentlyContinue
|
||||
```
|
||||
|
||||
```powershell
|
||||
pwsh ./.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1 `
|
||||
-StartCommit '{{PreviousReleaseTag}}' -Branch 'stable' `
|
||||
-OutputDir 'Generated Files/ReleaseNotes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Label Mappings
|
||||
|
||||
| Keywords/Patterns | Suggested Label |
|
||||
| ----------------- | --------------- |
|
||||
| Advanced Paste, AP, clipboard, paste | `Product-Advanced Paste` |
|
||||
| CmdPal, Command Palette, cmdpal | `Product-Command Palette` |
|
||||
| FancyZones, zones, layout | `Product-FancyZones` |
|
||||
| ZoomIt, zoom, screen annotation | `Product-ZoomIt` |
|
||||
| Settings, settings-ui, Quick Access, flyout | `Product-Settings` |
|
||||
| Installer, setup, MSI, MSIX, WiX | `Area-Setup/Install` |
|
||||
| Build, pipeline, CI/CD, msbuild | `Area-Build` |
|
||||
| Test, unit test, UI test, fuzz | `Area-Tests` |
|
||||
| Localization, loc, translation, resw | `Area-Localization` |
|
||||
| Foundry, AI, LLM | `Product-Advanced Paste` (AI features) |
|
||||
| Mouse Without Borders, MWB | `Product-Mouse Without Borders` |
|
||||
| PowerRename, rename, regex | `Product-PowerRename` |
|
||||
| Peek, preview, file preview | `Product-Peek` |
|
||||
| Image Resizer, resize | `Product-Image Resizer` |
|
||||
| LightSwitch, theme, dark mode | `Product-LightSwitch` |
|
||||
| Quick Accent, accent, diacritics | `Product-Quick Accent` |
|
||||
| Awake, keep awake, caffeine | `Product-Awake` |
|
||||
| ColorPicker, color picker, eyedropper | `Product-ColorPicker` |
|
||||
| Hosts, hosts file | `Product-Hosts` |
|
||||
| Keyboard Manager, remap | `Product-Keyboard Manager` |
|
||||
| Mouse Highlighter | `Product-Mouse Highlighter` |
|
||||
| Mouse Jump | `Product-Mouse Jump` |
|
||||
| Find My Mouse | `Product-Find My Mouse` |
|
||||
| Mouse Pointer Crosshairs | `Product-Mouse Pointer Crosshairs` |
|
||||
| Shortcut Guide | `Product-Shortcut Guide` |
|
||||
| Text Extractor, OCR, PowerOCR | `Product-Text Extractor` |
|
||||
| Workspaces | `Product-Workspaces` |
|
||||
| File Locksmith | `Product-File Locksmith` |
|
||||
| Crop And Lock | `Product-CropAndLock` |
|
||||
| Environment Variables | `Product-Environment Variables` |
|
||||
| New+ | `Product-New+` |
|
||||
|
||||
## Label Filtering Rules
|
||||
|
||||
The grouping script keeps labels matching these patterns:
|
||||
- `Product-*`
|
||||
- `Area-*`
|
||||
- `GitHub*`
|
||||
- `*Plugin`
|
||||
- `Issue-*`
|
||||
|
||||
Other labels are ignored for grouping purposes.
|
||||
@@ -1,58 +0,0 @@
|
||||
# Step 3: Local Agent Reviews and Grouping
|
||||
|
||||
## 3.0 To-do
|
||||
- 3.1 Generate PR Summaries with the Local Agent
|
||||
- 3.2 (Optional) Refresh PR Data
|
||||
- 3.3 Group PRs by Label
|
||||
|
||||
## 3.1 Generate PR Summaries with the Local Agent
|
||||
|
||||
> ⚠️ **Do not use `mcp_github_request_copilot_review` (or any "request Copilot review" tool that calls the GitHub API).**
|
||||
> When this skill is driven from a CLI / coding agent, the request is made from a bot identity and the GitHub API rejects it ("Bot reviewers cannot be requested"). The PR ends up with no Copilot review and `CopilotSummary` stays empty.
|
||||
>
|
||||
> Instead, **the local agent that is running this skill performs the review itself** and writes the summary directly into `sorted_prs.csv`.
|
||||
|
||||
For every PR listed in `Generated Files/ReleaseNotes/sorted_prs.csv` whose `CopilotSummary` is empty:
|
||||
|
||||
1. Fetch the PR diff using a tool that does **not** post anything back to GitHub. Any of these works:
|
||||
- `mcp_github_pull_request_read` with `method: get_diff`
|
||||
- `mcp_github_pull_request_read` with `method: get_files` (when the diff is large)
|
||||
- `gh pr diff <PR_NUMBER> --repo microsoft/PowerToys`
|
||||
2. Read the PR title, body, and diff. Produce a 1–3 sentence, user-facing summary in the same style as a Copilot PR review (focus on observable behavior change, not implementation details).
|
||||
3. Write the summary into the `CopilotSummary` column for that PR row in `Generated Files/ReleaseNotes/sorted_prs.csv`. Preserve all other columns and the existing row order.
|
||||
|
||||
**Batching guidance**
|
||||
|
||||
- Process PRs in the order they appear in `sorted_prs.csv`.
|
||||
- Generate summaries for **all** PRs in one pass before continuing to Step 3.3, so the human reviewer can validate them together.
|
||||
- For very large diffs, summarize from `get_files` (filenames + per-file patches) rather than the full diff.
|
||||
- Skip PRs that already have a non-empty `CopilotSummary` (e.g. PRs where a human reviewer already pasted one). Do not overwrite existing summaries.
|
||||
|
||||
**Why not post the summary back to the PR?** Posting a comment from the agent's identity would not be picked up by `dump-prs-since-commit.ps1` (which only matches Copilot bot authors), and it adds noise to the PR. Writing straight into the CSV keeps the artifact self-contained.
|
||||
|
||||
---
|
||||
|
||||
## 3.2 (Optional) Refresh PR Data
|
||||
|
||||
Only re-run the collection script if PR metadata on GitHub has changed (new labels, retitled PRs, etc.) since Step 1.1. **Skip this step if you only want to preserve the locally generated `CopilotSummary` values from Step 3.1**, because re-running the dump will overwrite the CSV.
|
||||
|
||||
```powershell
|
||||
pwsh ./.github/skills/release-note-generation/scripts/dump-prs-since-commit.ps1 `
|
||||
-StartCommit '{{PreviousReleaseTag}}' -Branch 'stable' `
|
||||
-OutputDir 'Generated Files/ReleaseNotes'
|
||||
```
|
||||
|
||||
If you do refresh, redo Step 3.1 afterwards to repopulate `CopilotSummary`.
|
||||
|
||||
---
|
||||
|
||||
## 3.3 Group PRs by Label
|
||||
|
||||
```powershell
|
||||
pwsh ./.github/skills/release-note-generation/scripts/group-prs-by-label.ps1 -CsvPath 'Generated Files/ReleaseNotes/sorted_prs.csv' -OutDir 'Generated Files/ReleaseNotes/grouped_csv'
|
||||
```
|
||||
|
||||
Creates `Generated Files/ReleaseNotes/grouped_csv/` with one CSV per label combination.
|
||||
|
||||
**Validation:** The `Unlabeled.csv` file should be minimal (ideally empty). If many PRs remain unlabeled, return to Step 2 (see [step2-labeling.md](./step2-labeling.md)).
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# Step 4: Summaries and Final Release Notes
|
||||
|
||||
## 4.0 To-do
|
||||
- 4.1 Generate Summary Markdown (Agent Mode)
|
||||
- 4.2 Produce Final Release Notes File
|
||||
|
||||
## 4.1 Generate Summary Markdown (Agent Mode)
|
||||
|
||||
For each CSV in `Generated Files/ReleaseNotes/grouped_csv/`, create a markdown file in `Generated Files/ReleaseNotes/grouped_md/`.
|
||||
|
||||
⚠️ **IMPORTANT:** Generate **ALL** markdown files first. Do NOT pause between files or ask for feedback during generation. Complete the entire batch, then human reviews afterwards.
|
||||
|
||||
### Structure per file
|
||||
|
||||
**1. Bullet list** - one concise, user-facing line per PR:
|
||||
- Use the “Verb-ed + Scenario + Impact” sentence structure—make readers think, “That’s exactly what I need” or “Yes, that’s an awesome fix.”; The "impact" can be end-user focused (written to convey user excitement) or technical (performance/stability) when user-facing impact is minimal.
|
||||
- If nothing special on impact or unclear impact, mark as needing human summary
|
||||
- Source from Title, Body, and CopilotSummary (prefer CopilotSummary when available)
|
||||
- The `NeedThanks` column contains a comma-separated list of external contributor usernames who should be credited (empty = no attribution needed, all authors are core team). For each non-empty `NeedThanks` value, append a `by` attribution that lists **every** contributor, matching GitHub's standard contributor-attribution style: `by [@user1](https://github.com/user1)` for a single contributor, `by [@user1](https://github.com/user1) and [@user2](https://github.com/user2)` for two, or `by [@user1](https://github.com/user1), [@user2](https://github.com/user2), and [@user3](https://github.com/user3)` for three or more. In the final consolidated release notes (Step 4.2), the attribution follows the PR link, e.g. `…in [#1234](url) by [@user](url)`. Do not use "Thanks @user!" phrasing.
|
||||
- Do NOT include PR numbers in bullet lines
|
||||
- Do NOT mention “security” or “privacy” issues, since these are not known and could be leveraged by attackers in earlier versions. Instead, describe the user-facing scenario, usage, or impact.
|
||||
- If confidence < 70%, write: `Human Summary Needed: <PR full link>`
|
||||
|
||||
**See [SampleOutput.md](./SampleOutput.md) for examples of well-written bullet summaries.**
|
||||
|
||||
**2. Three-column table** (same PR order):
|
||||
- Column 1: Concise summary (same as bullet)
|
||||
- Column 2: PR link `[#ID](URL)`
|
||||
- Column 3: Confidence level (High/Medium/Low)
|
||||
|
||||
### Review Process (AFTER all files generated)
|
||||
|
||||
- Human reviews each `grouped_md/*.md` file and requests rewrites as needed
|
||||
- Human may say "rewrite Product-X" or "combine these bullets"—apply changes to that specific file
|
||||
- Do NOT interrupt generation to ask for feedback
|
||||
|
||||
---
|
||||
|
||||
## 4.2 Produce Final Release Notes File
|
||||
|
||||
Once all `grouped_md/*.md` files are reviewed and approved, consolidate into a single release notes file.
|
||||
|
||||
**Output:** `Generated Files/ReleaseNotes/v{{ReleaseVersion}}-release-notes.md`
|
||||
|
||||
### Structure
|
||||
|
||||
**1. Highlights section** (top):
|
||||
- 8-12 bullets covering the most user-visible features and impactful fixes
|
||||
- Pattern: `**Module**: brief description`
|
||||
- Avoid internal refactors; focus on what users will notice
|
||||
|
||||
**2. Module sections** (alphabetical order):
|
||||
- One section per product (Advanced Paste, Awake, Command Palette, etc.)
|
||||
- Migrate bullet summaries from the approved `grouped_md/Product-*.md` files
|
||||
- One section 'Development' for all the rest summaries from the approved `grouped_md/Area-*.md` files
|
||||
- Re-review E2E, group release improvements by section, and move the most important items to the top of each section.
|
||||
Some items in the Development section may overlap and should be moved to the Module section where more applicable.
|
||||
|
||||
### Example Final Structure
|
||||
|
||||
```markdown
|
||||
# PowerToys v{{ReleaseVersion}} Release Notes
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Command Palette**: Added theme customization and drag-and-drop support
|
||||
- **Advanced Paste**: Image input for AI, color detection in clipboard history
|
||||
- **FancyZones**: New CLI tool for command-line layout management
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
## Advanced Paste
|
||||
|
||||
- Wrapped paste option lists in a single ScrollViewer in [#5678](https://github.com/microsoft/PowerToys/pull/5678)
|
||||
- Added image input handling for AI-powered transformations in [#5679](https://github.com/microsoft/PowerToys/pull/5679)
|
||||
...
|
||||
|
||||
## Awake
|
||||
|
||||
- Fixed timed mode expiration in [#5680](https://github.com/microsoft/PowerToys/pull/5680) by [@daverayment](https://github.com/daverayment)
|
||||
...
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
...
|
||||
```
|
||||
@@ -1,90 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Apply labels to PRs from a CSV file.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads a CSV with Id and Label columns and applies the specified label to each PR via GitHub CLI.
|
||||
Supports dry-run mode to preview changes before applying.
|
||||
|
||||
.PARAMETER InputCsv
|
||||
CSV file with Id and Label columns. Default: prs_to_label.csv
|
||||
|
||||
.PARAMETER Repo
|
||||
GitHub repository (owner/name). Default: microsoft/PowerToys
|
||||
|
||||
.PARAMETER WhatIf
|
||||
Dry run - show what would be applied without making changes.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./apply-labels.ps1 -InputCsv 'Generated Files/ReleaseNotes/prs_to_label.csv'
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./apply-labels.ps1 -InputCsv 'Generated Files/ReleaseNotes/prs_to_label.csv' -WhatIf
|
||||
|
||||
.NOTES
|
||||
Requires: gh CLI authenticated with repo write access.
|
||||
|
||||
Input CSV format:
|
||||
Id,Label
|
||||
12345,Product-Advanced Paste
|
||||
12346,Product-Settings
|
||||
#>
|
||||
[CmdletBinding()] param(
|
||||
[Parameter(Mandatory=$false)][string]$InputCsv = 'prs_to_label.csv',
|
||||
[Parameter(Mandatory=$false)][string]$Repo = 'microsoft/PowerToys',
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Info($m){ Write-Host "[info] $m" -ForegroundColor Cyan }
|
||||
function Write-Warn($m){ Write-Host "[warn] $m" -ForegroundColor Yellow }
|
||||
function Write-Err($m){ Write-Host "[error] $m" -ForegroundColor Red }
|
||||
function Write-OK($m){ Write-Host "[ok] $m" -ForegroundColor Green }
|
||||
|
||||
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) { Write-Err "GitHub CLI 'gh' not found in PATH"; exit 1 }
|
||||
if (-not (Test-Path -LiteralPath $InputCsv)) { Write-Err "Input CSV not found: $InputCsv"; exit 1 }
|
||||
|
||||
$rows = Import-Csv -LiteralPath $InputCsv
|
||||
if (-not $rows) { Write-Info "No rows in CSV."; exit 0 }
|
||||
|
||||
$firstCols = $rows[0].PSObject.Properties.Name
|
||||
if (-not ($firstCols -contains 'Id' -and $firstCols -contains 'Label')) {
|
||||
Write-Err "CSV must contain 'Id' and 'Label' columns"; exit 1
|
||||
}
|
||||
|
||||
Write-Info "Processing $($rows.Count) label assignments..."
|
||||
if ($WhatIf) { Write-Warn "DRY RUN - no changes will be made" }
|
||||
|
||||
$applied = 0
|
||||
$skipped = 0
|
||||
$failed = 0
|
||||
|
||||
foreach ($row in $rows) {
|
||||
$id = $row.Id
|
||||
$label = $row.Label
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($id) -or [string]::IsNullOrWhiteSpace($label)) {
|
||||
Write-Warn "Skipping row with empty Id or Label"
|
||||
$skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
if ($WhatIf) {
|
||||
Write-Info "Would apply label '$label' to PR #$id"
|
||||
$applied++
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
gh pr edit $id --repo $Repo --add-label $label 2>&1 | Out-Null
|
||||
Write-OK "Applied '$label' to PR #$id"
|
||||
$applied++
|
||||
} catch {
|
||||
Write-Warn "Failed to apply label to PR #${id}: $_"
|
||||
$failed++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Info ""
|
||||
Write-Info "Summary: Applied=$applied Skipped=$skipped Failed=$failed"
|
||||
@@ -1,172 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Collect existing PR milestones or (optionally) assign/apply a milestone to missing PRs in one script.
|
||||
|
||||
.DESCRIPTION
|
||||
This unified script merges the behaviors of the previous add-milestone-column (collector) and
|
||||
set-milestones-missing (remote updater) scripts.
|
||||
|
||||
Modes (controlled by switches):
|
||||
1. Collect (default) – For each PR Id in the input CSV, queries GitHub for the current milestone and
|
||||
outputs a two-column CSV (Id,Milestone) leaving blanks where none are set.
|
||||
2. LocalAssign – Same as Collect, but for rows that end up blank assigns the value of -DefaultMilestone
|
||||
in memory (does NOT touch GitHub). Useful for quickly preparing a fully populated CSV.
|
||||
3. ApplyMissing – After determining which PRs have no milestone, call GitHub API to set their milestone
|
||||
to -DefaultMilestone. Requires milestone to already exist (open). Network + write.
|
||||
|
||||
You can combine LocalAssign and ApplyMissing: the remote update uses the existing live state; LocalAssign only
|
||||
affects the output CSV/pipeline objects.
|
||||
|
||||
.PARAMETER InputCsv
|
||||
Source CSV with at least an Id column. Default: sorted_prs.csv
|
||||
|
||||
.PARAMETER OutputCsv
|
||||
Destination CSV for collected (and optionally locally assigned) milestones. Default: prs_with_milestone.csv
|
||||
|
||||
.PARAMETER Repo
|
||||
GitHub repository (owner/name). Default: microsoft/PowerToys
|
||||
|
||||
.PARAMETER DefaultMilestone
|
||||
Milestone title used when -LocalAssign or -ApplyMissing is specified. Default: 'PowerToys 0.97'
|
||||
|
||||
.PARAMETER Offline
|
||||
Skip ALL GitHub lookups / updates. Implies Collect-only with all Milestone cells blank (unless LocalAssign).
|
||||
|
||||
.PARAMETER LocalAssign
|
||||
Populate empty Milestone cells in the output with -DefaultMilestone (does not modify GitHub).
|
||||
|
||||
.PARAMETER ApplyMissing
|
||||
For PRs which currently have no milestone (live on GitHub), set them to -DefaultMilestone via Issues API.
|
||||
|
||||
.PARAMETER WhatIf
|
||||
Dry run for ApplyMissing: show intended remote changes without performing PATCH requests.
|
||||
|
||||
.EXAMPLE
|
||||
# Collect only
|
||||
pwsh ./collect-or-apply-milestones.ps1
|
||||
|
||||
.EXAMPLE
|
||||
# Collect and fill blanks locally in the output only
|
||||
pwsh ./collect-or-apply-milestones.ps1 -LocalAssign
|
||||
|
||||
.EXAMPLE
|
||||
# Collect and remotely apply milestone to missing PRs
|
||||
pwsh ./collect-or-apply-milestones.ps1 -ApplyMissing
|
||||
|
||||
.EXAMPLE
|
||||
# Dry run remote application
|
||||
pwsh ./collect-or-apply-milestones.ps1 -ApplyMissing -WhatIf
|
||||
|
||||
.EXAMPLE
|
||||
# Offline local assignment
|
||||
pwsh ./collect-or-apply-milestones.ps1 -Offline -LocalAssign -DefaultMilestone 'PowerToys 0.96'
|
||||
|
||||
.NOTES
|
||||
Requires gh CLI unless -Offline AND -ApplyMissing not specified.
|
||||
Remote apply path queries milestones to resolve numeric ID.
|
||||
#>
|
||||
[CmdletBinding()] param(
|
||||
[Parameter(Mandatory=$false)][string]$InputCsv = 'sorted_prs.csv',
|
||||
[Parameter(Mandatory=$false)][string]$OutputCsv = 'prs_with_milestone.csv',
|
||||
[Parameter(Mandatory=$false)][string]$Repo = 'microsoft/PowerToys',
|
||||
[Parameter(Mandatory=$false)][string]$DefaultMilestone = 'PowerToys 0.97',
|
||||
[switch]$Offline,
|
||||
[switch]$LocalAssign,
|
||||
[switch]$ApplyMissing,
|
||||
[switch]$WhatIf
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
function Write-Info($m){ Write-Host "[info] $m" -ForegroundColor Cyan }
|
||||
function Write-Warn($m){ Write-Host "[warn] $m" -ForegroundColor Yellow }
|
||||
function Write-Err($m){ Write-Host "[error] $m" -ForegroundColor Red }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $InputCsv)) { Write-Err "Input CSV not found: $InputCsv"; exit 1 }
|
||||
$rows = Import-Csv -LiteralPath $InputCsv
|
||||
if (-not $rows) { Write-Warn "Input CSV has no rows."; @() | Export-Csv -NoTypeInformation -LiteralPath $OutputCsv; exit 0 }
|
||||
if (-not ($rows[0].PSObject.Properties.Name -contains 'Id')) { Write-Err "Input CSV missing 'Id' column."; exit 1 }
|
||||
|
||||
$needGh = (-not $Offline) -and ($ApplyMissing -or -not $Offline)
|
||||
if ($needGh -and -not (Get-Command gh -ErrorAction SilentlyContinue)) { Write-Err "GitHub CLI 'gh' not found. Use -Offline or install gh."; exit 1 }
|
||||
|
||||
# Step 1: Collect current milestone titles
|
||||
$milestoneCache = @{}
|
||||
$collected = New-Object System.Collections.Generic.List[object]
|
||||
$idx = 0
|
||||
foreach ($row in $rows) {
|
||||
$idx++
|
||||
$id = $row.Id
|
||||
if (-not $id) { Write-Warn "Row $idx missing Id; skipping"; continue }
|
||||
$ms = ''
|
||||
if (-not $Offline) {
|
||||
if ($milestoneCache.ContainsKey($id)) { $ms = $milestoneCache[$id] }
|
||||
else {
|
||||
try {
|
||||
$json = gh pr view $id --repo $Repo --json milestone 2>$null | ConvertFrom-Json
|
||||
if ($json -and $json.milestone -and $json.milestone.title) { $ms = $json.milestone.title }
|
||||
} catch {
|
||||
Write-Warn "Failed to fetch PR #$id milestone: $_"
|
||||
}
|
||||
$milestoneCache[$id] = $ms
|
||||
}
|
||||
}
|
||||
$collected.Add([PSCustomObject]@{ Id = $id; Milestone = $ms }) | Out-Null
|
||||
}
|
||||
|
||||
# Step 2: Remote apply (if requested)
|
||||
$applySummary = @()
|
||||
if ($ApplyMissing) {
|
||||
if ($Offline) { Write-Err "Cannot use -ApplyMissing with -Offline."; exit 1 }
|
||||
Write-Info "Resolving milestone id for '$DefaultMilestone' ..."
|
||||
$milestonesRaw = gh api repos/$Repo/milestones --paginate --jq '.[] | {number,title,state}'
|
||||
$msObj = $milestonesRaw | ConvertFrom-Json | Where-Object { $_.title -eq $DefaultMilestone -and $_.state -eq 'open' } | Select-Object -First 1
|
||||
if (-not $msObj) { Write-Err "Milestone '$DefaultMilestone' not found/open."; exit 1 }
|
||||
$msNumber = $msObj.number
|
||||
$targets = $collected | Where-Object { [string]::IsNullOrWhiteSpace($_.Milestone) }
|
||||
Write-Info ("ApplyMissing: {0} PR(s) without milestone." -f $targets.Count)
|
||||
foreach ($t in $targets) {
|
||||
$id = $t.Id
|
||||
try {
|
||||
# Verify still missing live
|
||||
$current = gh pr view $id --repo $Repo --json milestone --jq '.milestone.title // ""'
|
||||
if ($current) {
|
||||
$applySummary += [PSCustomObject]@{ Id=$id; Action='Skip (already has)'; Milestone=$current; Status='OK' }
|
||||
continue
|
||||
}
|
||||
if ($WhatIf) {
|
||||
$applySummary += [PSCustomObject]@{ Id=$id; Action='Would set'; Milestone=$DefaultMilestone; Status='DRY RUN' }
|
||||
continue
|
||||
}
|
||||
gh api -X PATCH -H 'Accept: application/vnd.github+json' repos/$Repo/issues/$id -f milestone=$msNumber | Out-Null
|
||||
$applySummary += [PSCustomObject]@{ Id=$id; Action='Set'; Milestone=$DefaultMilestone; Status='OK' }
|
||||
# Reflect in collected object for CSV output if not LocalAssign already doing so
|
||||
$t.Milestone = $DefaultMilestone
|
||||
} catch {
|
||||
$errText = $_ | Out-String
|
||||
$applySummary += [PSCustomObject]@{ Id=$id; Action='Failed'; Milestone=$DefaultMilestone; Status=$errText.Trim() }
|
||||
Write-Warn ("Failed to set milestone for PR #{0}: {1}" -f $id, ($errText.Trim()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3: Local assignment (purely for output) AFTER remote so remote actual result not overwritten accidentally
|
||||
if ($LocalAssign) {
|
||||
foreach ($item in $collected) {
|
||||
if ([string]::IsNullOrWhiteSpace($item.Milestone)) { $item.Milestone = $DefaultMilestone }
|
||||
}
|
||||
}
|
||||
|
||||
# Step 4: Export CSV
|
||||
$collected | Export-Csv -LiteralPath $OutputCsv -NoTypeInformation -Encoding UTF8
|
||||
Write-Info ("Wrote collected CSV -> {0}" -f (Resolve-Path -LiteralPath $OutputCsv))
|
||||
|
||||
# Step 5: Summaries
|
||||
if ($ApplyMissing) {
|
||||
$updated = ($applySummary | Where-Object { $_.Action -eq 'Set' }).Count
|
||||
$skipped = ($applySummary | Where-Object { $_.Action -like 'Skip*' }).Count
|
||||
$failed = ($applySummary | Where-Object { $_.Action -eq 'Failed' }).Count
|
||||
Write-Info ("ApplyMissing summary: Updated={0} Skipped={1} Failed={2}" -f $updated, $skipped, $failed)
|
||||
}
|
||||
|
||||
# Emit objects (final collected set)
|
||||
return $collected
|
||||
@@ -1,100 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Produce an incremental PR CSV containing rows present in a newer full export but absent from a baseline export.
|
||||
|
||||
.DESCRIPTION
|
||||
Compares two previously generated sorted PR CSV files (same schema). Any row whose key column value
|
||||
(defaults to 'Number') does not exist in the baseline file is emitted to a new incremental CSV, preserving
|
||||
the original column order. If no new rows are found, an empty CSV (with headers when determinable) is written.
|
||||
|
||||
.PARAMETER BaseCsv
|
||||
Path to the baseline (earlier) PR CSV.
|
||||
|
||||
.PARAMETER AllCsv
|
||||
Path to the newer full PR CSV containing superset (or equal set) of rows.
|
||||
|
||||
.PARAMETER OutCsv
|
||||
Path to write the incremental CSV containing only new rows.
|
||||
|
||||
.PARAMETER Key
|
||||
Column name used as unique identifier (defaults to 'Number'). Must exist in both CSVs.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./diff_prs.ps1 -BaseCsv sorted_prs_prev.csv -AllCsv sorted_prs.csv -OutCsv sorted_prs_incremental.csv
|
||||
|
||||
.NOTES
|
||||
Requires: PowerShell 7+, both CSVs with identical column schemas.
|
||||
Exit code 0 on success (even if zero incremental rows). Throws on missing files.
|
||||
#>
|
||||
|
||||
[CmdletBinding()] param(
|
||||
[Parameter(Mandatory=$false)][string]$BaseCsv = "./sorted_prs_93_round1.csv",
|
||||
[Parameter(Mandatory=$false)][string]$AllCsv = "./sorted_prs.csv",
|
||||
[Parameter(Mandatory=$false)][string]$OutCsv = "./sorted_prs_93_incremental.csv",
|
||||
[Parameter(Mandatory=$false)][string]$Key = "Number"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Info($m) { Write-Host "[info] $m" -ForegroundColor Cyan }
|
||||
function Write-Warn($m) { Write-Host "[warn] $m" -ForegroundColor Yellow }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $BaseCsv)) { throw "Base CSV not found: $BaseCsv" }
|
||||
if (-not (Test-Path -LiteralPath $AllCsv)) { throw "All CSV not found: $AllCsv" }
|
||||
|
||||
# Load CSVs
|
||||
$baseRows = Import-Csv -LiteralPath $BaseCsv
|
||||
$allRows = Import-Csv -LiteralPath $AllCsv
|
||||
|
||||
if (-not $baseRows) { Write-Warn "Base CSV has no rows." }
|
||||
if (-not $allRows) { Write-Warn "All CSV has no rows." }
|
||||
|
||||
# Validate key presence
|
||||
if ($baseRows -and -not ($baseRows[0].PSObject.Properties.Name -contains $Key)) { throw "Key column '$Key' not found in base CSV." }
|
||||
if ($allRows -and -not ($allRows[0].PSObject.Properties.Name -contains $Key)) { throw "Key column '$Key' not found in all CSV." }
|
||||
|
||||
# Build a set of existing keys from base
|
||||
$set = New-Object 'System.Collections.Generic.HashSet[string]'
|
||||
foreach ($row in $baseRows) {
|
||||
$val = [string]($row.$Key)
|
||||
if ($null -ne $val) { [void]$set.Add($val) }
|
||||
}
|
||||
|
||||
# Filter rows in AllCsv whose key is not in base (these are the new / incremental rows)
|
||||
$incremental = @()
|
||||
foreach ($row in $allRows) {
|
||||
$val = [string]($row.$Key)
|
||||
if (-not $set.Contains($val)) { $incremental += $row }
|
||||
}
|
||||
|
||||
# Preserve column order from the All CSV
|
||||
$columns = @()
|
||||
if ($allRows.Count -gt 0) {
|
||||
$columns = $allRows[0].PSObject.Properties.Name
|
||||
}
|
||||
|
||||
try {
|
||||
if ($incremental.Count -gt 0) {
|
||||
if ($columns.Count -gt 0) {
|
||||
$incremental | Select-Object -Property $columns | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
|
||||
} else {
|
||||
$incremental | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
|
||||
}
|
||||
} else {
|
||||
# Write an empty CSV with headers if we know them (facilitates downstream tooling expecting header row)
|
||||
if ($columns.Count -gt 0) {
|
||||
$obj = [PSCustomObject]@{}
|
||||
foreach ($c in $columns) { $obj | Add-Member -NotePropertyName $c -NotePropertyValue $null }
|
||||
$obj | Select-Object -Property $columns | Export-Csv -LiteralPath $OutCsv -NoTypeInformation -Encoding UTF8
|
||||
} else {
|
||||
'' | Out-File -LiteralPath $OutCsv -Encoding UTF8
|
||||
}
|
||||
}
|
||||
Write-Info ("Incremental rows: {0}" -f $incremental.Count)
|
||||
Write-Info ("Output: {0}" -f (Resolve-Path -LiteralPath $OutCsv))
|
||||
}
|
||||
catch {
|
||||
Write-Host "[error] Failed writing output CSV: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Export merged PR metadata between two commits (exclusive start, inclusive end) to JSON and CSV.
|
||||
|
||||
.DESCRIPTION
|
||||
Identifies merge/squash commits reachable from EndCommit but not StartCommit, extracts PR numbers,
|
||||
queries GitHub for metadata plus (optionally) Copilot review/comment summaries, filters labels, then
|
||||
emits a JSON artifact and a sorted CSV (first label alphabetical).
|
||||
|
||||
.PARAMETER StartCommit
|
||||
Exclusive starting commit (SHA, tag, or ref). Commits AFTER this one are considered.
|
||||
|
||||
.PARAMETER EndCommit
|
||||
Inclusive ending commit (SHA, tag, or ref). If not provided, uses origin/<Branch> when Branch is set; otherwise uses HEAD.
|
||||
|
||||
.PARAMETER Repo
|
||||
GitHub repository (owner/name). Default: microsoft/PowerToys.
|
||||
|
||||
.PARAMETER OutputCsv
|
||||
Destination CSV path. Default: sorted_prs.csv.
|
||||
|
||||
.PARAMETER OutputJson
|
||||
Destination JSON path containing raw PR objects. Default: milestone_prs.json.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -Branch stable
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./dump-prs-since-commit.ps1 -StartCommit 0123abcd -EndCommit 89ef7654 -OutputCsv delta.csv
|
||||
|
||||
.NOTES
|
||||
Requires: git, gh (authenticated). No Set-StrictMode to keep parity with existing release scripts.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$StartCommit, # exclusive start (commits AFTER this one)
|
||||
[string]$EndCommit,
|
||||
[string]$Branch,
|
||||
[string]$Repo = "microsoft/PowerToys",
|
||||
[string]$OutputDir,
|
||||
[string]$OutputCsv = "sorted_prs.csv",
|
||||
[string]$OutputJson = "milestone_prs.json"
|
||||
)
|
||||
|
||||
# (See top-level synopsis above for full documentation)
|
||||
|
||||
function Write-Info($msg) { Write-Host $msg -ForegroundColor Cyan }
|
||||
function Write-Warn($msg) { Write-Host $msg -ForegroundColor Yellow }
|
||||
function Write-Err($msg) { Write-Host $msg -ForegroundColor Red }
|
||||
function Write-DebugMsg($msg) { if ($PSBoundParameters.ContainsKey('Verbose') -or $VerbosePreference -eq 'Continue') { Write-Host "[VERBOSE] $msg" -ForegroundColor DarkGray } }
|
||||
|
||||
# Load member list from Generated Files/ReleaseNotes/MemberList.md (internal team - no thanks needed)
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Resolve-Path (Join-Path $scriptDir "..\..\..\..")
|
||||
$defaultMemberListPath = Join-Path $repoRoot "Generated Files\ReleaseNotes\MemberList.md"
|
||||
$memberListPath = $defaultMemberListPath
|
||||
if ($OutputDir) {
|
||||
$memberListFromOutputDir = Join-Path $OutputDir "MemberList.md"
|
||||
if (Test-Path $memberListFromOutputDir) {
|
||||
$memberListPath = $memberListFromOutputDir
|
||||
}
|
||||
}
|
||||
$memberList = @()
|
||||
if (Test-Path $memberListPath) {
|
||||
$memberListContent = Get-Content $memberListPath -Raw
|
||||
# Extract usernames - skip markdown code fence lines, get all non-empty lines
|
||||
$memberList = ($memberListContent -split "`n") | Where-Object { $_ -notmatch '^\s*```' -and $_.Trim() -ne '' } | ForEach-Object { $_.Trim() }
|
||||
if (-not $memberList -or $memberList.Count -eq 0) {
|
||||
Write-Err "MemberList.md is empty at $memberListPath"
|
||||
exit 1
|
||||
}
|
||||
Write-DebugMsg "Loaded $($memberList.Count) members from MemberList.md"
|
||||
} else {
|
||||
Write-Err "MemberList.md not found at $memberListPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Validate we are in a git repo
|
||||
#if (-not (Test-Path .git)) {
|
||||
# Write-Err "Current directory does not appear to be the root of a git repository."
|
||||
# exit 1
|
||||
#}
|
||||
|
||||
# Resolve output directory (if specified)
|
||||
if ($OutputDir) {
|
||||
if (-not (Test-Path $OutputDir)) {
|
||||
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
|
||||
}
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutputCsv)) {
|
||||
$OutputCsv = Join-Path $OutputDir $OutputCsv
|
||||
}
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutputJson)) {
|
||||
$OutputJson = Join-Path $OutputDir $OutputJson
|
||||
}
|
||||
}
|
||||
|
||||
# Resolve commits
|
||||
try {
|
||||
if ($Branch) {
|
||||
Write-Info "Fetching latest '$Branch' from origin (with tags)..."
|
||||
git fetch origin $Branch --tags | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "git fetch origin $Branch --tags failed" }
|
||||
}
|
||||
|
||||
$startSha = (git rev-parse --verify $StartCommit) 2>$null
|
||||
if (-not $startSha) { throw "StartCommit '$StartCommit' not found" }
|
||||
if ($Branch) {
|
||||
$branchRef = $Branch
|
||||
$branchSha = (git rev-parse --verify $branchRef) 2>$null
|
||||
if (-not $branchSha) {
|
||||
$branchRef = "origin/$Branch"
|
||||
$branchSha = (git rev-parse --verify $branchRef) 2>$null
|
||||
}
|
||||
if (-not $branchSha) { throw "Branch '$Branch' not found" }
|
||||
if (-not $PSBoundParameters.ContainsKey('EndCommit') -or [string]::IsNullOrWhiteSpace($EndCommit)) {
|
||||
$EndCommit = $branchRef
|
||||
}
|
||||
}
|
||||
if (-not $PSBoundParameters.ContainsKey('EndCommit') -or [string]::IsNullOrWhiteSpace($EndCommit)) {
|
||||
$EndCommit = "HEAD"
|
||||
}
|
||||
$endSha = (git rev-parse --verify $EndCommit) 2>$null
|
||||
if (-not $endSha) { throw "EndCommit '$EndCommit' not found" }
|
||||
}
|
||||
catch {
|
||||
Write-Err $_
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Info "Collecting commits between $startSha..$endSha (excluding start, including end)."
|
||||
# Get list of commits reachable from end but not from start.
|
||||
# IMPORTANT: In PowerShell, the .. operator creates a numeric/char range. If $startSha and $endSha look like hex strings,
|
||||
# `$startSha..$endSha` must be passed as a single string argument.
|
||||
$rangeArg = "$startSha..$endSha"
|
||||
$commitList = git rev-list $rangeArg
|
||||
|
||||
# Normalize list (filter out empty strings)
|
||||
$normalizedCommits = $commitList | Where-Object { $_ -and $_.Trim() -ne '' }
|
||||
$commitCount = ($normalizedCommits | Measure-Object).Count
|
||||
Write-DebugMsg ("Raw commitList length (including blanks): {0}" -f (($commitList | Measure-Object).Count))
|
||||
Write-DebugMsg ("Normalized commit count: {0}" -f $commitCount)
|
||||
if ($commitCount -eq 0) {
|
||||
Write-Warn "No commits found in specified range ($startSha..$endSha)."; exit 0
|
||||
}
|
||||
Write-DebugMsg ("First 5 commits: {0}" -f (($normalizedCommits | Select-Object -First 5) -join ', '))
|
||||
|
||||
<#
|
||||
Extract PR numbers from commits.
|
||||
Patterns handled:
|
||||
1. Merge commits: 'Merge pull request #12345 from ...'
|
||||
2. Squash commits: 'Some feature change (#12345)' (GitHub default squash format)
|
||||
We collect both. If a commit matches both (unlikely), it's deduped later.
|
||||
#>
|
||||
# Extract PR numbers from merge or squash commits
|
||||
$mergeCommits = @()
|
||||
foreach ($c in $normalizedCommits) {
|
||||
$subject = git show -s --format=%s $c
|
||||
$matched = $false
|
||||
# Pattern 1: Traditional merge commit
|
||||
if ($subject -match 'Merge pull request #([0-9]+) ') {
|
||||
$prNumber = [int]$matches[1]
|
||||
$mergeCommits += [PSCustomObject]@{ Sha = $c; Pr = $prNumber; Subject = $subject; Pattern = 'merge' }
|
||||
Write-DebugMsg "Matched merge PR #$prNumber in commit $c"
|
||||
$matched = $true
|
||||
}
|
||||
# Pattern 2: Squash merge subject line with ' (#12345)' at end (allow possible whitespace before paren)
|
||||
if ($subject -match '\(#([0-9]+)\)$') {
|
||||
$prNumber2 = [int]$matches[1]
|
||||
# Avoid duplicate object if pattern 1 already captured same number for same commit
|
||||
if (-not ($mergeCommits | Where-Object { $_.Sha -eq $c -and $_.Pr -eq $prNumber2 })) {
|
||||
$mergeCommits += [PSCustomObject]@{ Sha = $c; Pr = $prNumber2; Subject = $subject; Pattern = 'squash' }
|
||||
Write-DebugMsg "Matched squash PR #$prNumber2 in commit $c"
|
||||
}
|
||||
$matched = $true
|
||||
}
|
||||
if (-not $matched) {
|
||||
Write-DebugMsg "No PR pattern in commit $c : $subject"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $mergeCommits -or $mergeCommits.Count -eq 0) {
|
||||
Write-Warn "No merge commits with PR numbers found in range."; exit 0
|
||||
}
|
||||
|
||||
# Deduplicate PR numbers (in case of revert or merges across branches)
|
||||
$prNumbers = $mergeCommits | Select-Object -ExpandProperty Pr -Unique | Sort-Object
|
||||
Write-Info ("Found {0} unique PRs: {1}" -f $prNumbers.Count, ($prNumbers -join ', '))
|
||||
Write-DebugMsg ("Total merge commits examined: {0}" -f $mergeCommits.Count)
|
||||
|
||||
# Build a map of PR number → list of commit SHAs (for co-author extraction)
|
||||
$prToCommits = @{}
|
||||
foreach ($mc in $mergeCommits) {
|
||||
if (-not $prToCommits.ContainsKey($mc.Pr)) {
|
||||
$prToCommits[$mc.Pr] = @()
|
||||
}
|
||||
$prToCommits[$mc.Pr] += $mc.Sha
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get all authors (including co-authors) for a set of commits via GitHub GraphQL API.
|
||||
.DESCRIPTION
|
||||
Uses the Commit.authors field in GitHub's GraphQL API which natively includes
|
||||
co-authors (from Co-authored-by trailers). Returns GitHub usernames (login)
|
||||
without any email parsing — GitHub resolves the association for us.
|
||||
|
||||
NOTE: For squash merges this captures all co-authors correctly because GitHub
|
||||
preserves Co-authored-by trailers in the squash commit. For traditional merge
|
||||
commits, only the merger's author is returned — co-authors on individual PR
|
||||
commits are not traversed. This is acceptable because PowerToys primarily uses
|
||||
squash merging.
|
||||
#>
|
||||
function Get-CommitAuthors {
|
||||
param(
|
||||
[string[]]$CommitShas,
|
||||
[string]$RepoFullName = "microsoft/PowerToys"
|
||||
)
|
||||
$parts = $RepoFullName -split '/'
|
||||
$owner = $parts[0]
|
||||
$repoName = $parts[1]
|
||||
$allAuthors = @()
|
||||
|
||||
foreach ($sha in $CommitShas) {
|
||||
try {
|
||||
$query = "{ repository(owner: `"$owner`", name: `"$repoName`") { object(expression: `"$sha`") { ... on Commit { authors(first: 20) { nodes { user { login } name } } } } } }"
|
||||
$result = gh api graphql -f query="$query" 2>$null | ConvertFrom-Json
|
||||
$nodes = $result.data.repository.object.authors.nodes
|
||||
if ($nodes) {
|
||||
foreach ($node in $nodes) {
|
||||
if ($node.user -and $node.user.login) {
|
||||
$allAuthors += $node.user.login
|
||||
} else {
|
||||
# User without a GitHub account (rare) — use display name as fallback
|
||||
Write-DebugMsg "Commit $sha has an author without GitHub account: $($node.name)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-DebugMsg "GraphQL authors query failed for commit ${sha}: $_"
|
||||
}
|
||||
}
|
||||
|
||||
return $allAuthors | Select-Object -Unique
|
||||
}
|
||||
|
||||
# Query GitHub for each PR
|
||||
$prDetails = @()
|
||||
function Get-CopilotSummaryFromPrJson {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]$PrJson,
|
||||
[switch]$VerboseMode
|
||||
)
|
||||
# Returns a hashtable with Summary and Source keys.
|
||||
$result = @{ Summary = ""; Source = "" }
|
||||
if (-not $PrJson) { return $result }
|
||||
|
||||
$candidateAuthors = @(
|
||||
'github-copilot[bot]', 'github-copilot', 'copilot'
|
||||
)
|
||||
|
||||
# 1. Reviews (preferred) – pick the LONGEST valid Copilot body, not the most recent
|
||||
$reviews = $PrJson.reviews
|
||||
if ($reviews) {
|
||||
$copilotReviews = $reviews | Where-Object {
|
||||
($candidateAuthors -contains $_.author.login -or $_.author.login -like '*copilot*') -and $_.body -and $_.body.Trim() -ne ''
|
||||
}
|
||||
if ($copilotReviews) {
|
||||
$longest = $copilotReviews | Sort-Object { $_.body.Length } -Descending | Select-Object -First 1
|
||||
if ($longest) {
|
||||
$body = $longest.body
|
||||
$norm = ($body -replace "`r", '') -replace "`n", ' '
|
||||
$norm = $norm -replace '\s+', ' '
|
||||
$result.Summary = $norm
|
||||
$result.Source = 'review'
|
||||
if ($VerboseMode) { Write-DebugMsg "Selected Copilot review length=$($body.Length) (longest)." }
|
||||
return $result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Comments fallback (some repos surface Copilot summaries as PR comments rather than review objects)
|
||||
if ($null -eq $PrJson.comments) {
|
||||
try {
|
||||
# Lazy fetch comments only if needed
|
||||
$commentsJson = gh pr view $PrJson.number --repo $Repo --json comments 2>$null | ConvertFrom-Json
|
||||
if ($commentsJson -and $commentsJson.comments) {
|
||||
$PrJson | Add-Member -NotePropertyName comments -NotePropertyValue $commentsJson.comments -Force
|
||||
}
|
||||
} catch {
|
||||
if ($VerboseMode) { Write-DebugMsg "Failed to fetch comments for PR #$($PrJson.number): $_" }
|
||||
}
|
||||
}
|
||||
if ($PrJson.comments) {
|
||||
$copilotComments = $PrJson.comments | Where-Object {
|
||||
($candidateAuthors -contains $_.author.login -or $_.author.login -like '*copilot*') -and $_.body -and $_.body.Trim() -ne ''
|
||||
}
|
||||
if ($copilotComments) {
|
||||
$longestC = $copilotComments | Sort-Object { $_.body.Length } -Descending | Select-Object -First 1
|
||||
if ($longestC) {
|
||||
$body = $longestC.body
|
||||
$norm = ($body -replace "`r", '') -replace "`n", ' '
|
||||
$norm = $norm -replace '\s+', ' '
|
||||
$result.Summary = $norm
|
||||
$result.Source = 'comment'
|
||||
if ($VerboseMode) { Write-DebugMsg "Selected Copilot comment length=$($body.Length) (longest)." }
|
||||
return $result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
foreach ($pr in $prNumbers) {
|
||||
Write-Info "Fetching PR #$pr ..."
|
||||
try {
|
||||
# Include comments only if Verbose asked; if not, we lazily pull when reviews are missing
|
||||
$fields = 'number,title,labels,author,url,body,reviews'
|
||||
if ($PSBoundParameters.ContainsKey('Verbose')) { $fields += ',comments' }
|
||||
$json = gh pr view $pr --repo $Repo --json $fields 2>$null | ConvertFrom-Json
|
||||
if ($null -eq $json) { throw "Empty response" }
|
||||
|
||||
$copilot = Get-CopilotSummaryFromPrJson -PrJson $json -VerboseMode:($PSBoundParameters.ContainsKey('Verbose'))
|
||||
if ($copilot.Summary -and $copilot.Source -and $PSBoundParameters.ContainsKey('Verbose')) {
|
||||
Write-DebugMsg "Copilot summary source=$($copilot.Source) chars=$($copilot.Summary.Length)"
|
||||
} elseif (-not $copilot.Summary) {
|
||||
Write-DebugMsg "No Copilot summary found for PR #$pr"
|
||||
}
|
||||
|
||||
# Filter labels
|
||||
$filteredLabels = $json.labels | Where-Object {
|
||||
($_.name -like "Product-*") -or
|
||||
($_.name -like "Area-*") -or
|
||||
($_.name -like "GitHub*") -or
|
||||
($_.name -like "*Plugin") -or
|
||||
($_.name -like "Issue-*")
|
||||
}
|
||||
$labelNames = ($filteredLabels | ForEach-Object { $_.name }) -join ", "
|
||||
|
||||
$bodyValue = if ($json.body) { ($json.body -replace "`r", '') -replace "`n", ' ' } else { '' }
|
||||
$bodyValue = $bodyValue -replace '\s+', ' '
|
||||
|
||||
# Collect all contributors: PR author + co-authors from commit messages
|
||||
$authorLogin = $json.author.login
|
||||
$allContributors = @($authorLogin)
|
||||
|
||||
# Extract all authors (including co-authors) from associated commits via GitHub GraphQL API
|
||||
if ($prToCommits.ContainsKey([int]$pr)) {
|
||||
$commitAuthors = Get-CommitAuthors -CommitShas $prToCommits[[int]$pr] -RepoFullName $Repo
|
||||
if ($commitAuthors) {
|
||||
$allContributors += $commitAuthors
|
||||
}
|
||||
}
|
||||
|
||||
# Deduplicate contributors (case-insensitive)
|
||||
$allContributors = $allContributors | Where-Object { $_ } | Sort-Object -Unique
|
||||
|
||||
# Filter to only external contributors (not in member list) for thanks
|
||||
$externalContributors = @()
|
||||
if ($memberList.Count -gt 0) {
|
||||
$externalContributors = $allContributors | Where-Object { -not ($memberList -contains $_) }
|
||||
} else {
|
||||
$externalContributors = $allContributors
|
||||
}
|
||||
|
||||
# Author column: all contributors (comma-separated)
|
||||
$authorField = ($allContributors -join ', ')
|
||||
|
||||
# NeedThanks column: comma-separated list of external contributors who
|
||||
# deserve thanks attribution. Empty string means no thanks needed.
|
||||
$needThanksField = ($externalContributors -join ', ')
|
||||
|
||||
$prDetails += [PSCustomObject]@{
|
||||
Id = $json.number
|
||||
Title = $json.title
|
||||
Labels = $labelNames
|
||||
Author = $authorField
|
||||
Url = $json.url
|
||||
Body = $bodyValue
|
||||
CopilotSummary = $copilot.Summary
|
||||
NeedThanks = $needThanksField
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$err = $_
|
||||
Write-Warn ("Failed to fetch PR #{0}: {1}" -f $pr, $err)
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $prDetails) { Write-Warn "No PR details fetched."; exit 0 }
|
||||
|
||||
# Sort by Labels like original script (first label alphabetical)
|
||||
$sorted = $prDetails | Sort-Object { ($_.Labels -split ',')[0] }
|
||||
|
||||
# Output JSON raw (optional)
|
||||
$sorted | ConvertTo-Json -Depth 6 | Out-File -Encoding UTF8 $OutputJson
|
||||
|
||||
Write-Info "Saving CSV to $OutputCsv ..."
|
||||
$sorted | Export-Csv $OutputCsv -NoTypeInformation
|
||||
Write-Host "✅ Done. Generated $($prDetails.Count) PR rows." -ForegroundColor Green
|
||||
@@ -1,80 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Find a commit on a branch that has the same subject line as a reference commit.
|
||||
|
||||
.DESCRIPTION
|
||||
Given a commit SHA (often from a release tag) and a branch name, this script
|
||||
resolves the reference commit's subject, then searches the branch history for
|
||||
commits with the exact same subject line. Useful when the release tag commit
|
||||
is not reachable from your current branch history.
|
||||
|
||||
.PARAMETER Commit
|
||||
The reference commit SHA or ref (e.g., v0.96.1 or a full SHA).
|
||||
|
||||
.PARAMETER Branch
|
||||
The branch to search (e.g., stable or main). Defaults to stable.
|
||||
|
||||
.PARAMETER RepoPath
|
||||
Path to the local repo. Defaults to current directory.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh ./find-commit-by-title.ps1 -Commit b62f6421845f7e5c92b8186868d98f46720db442 -Branch stable
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Commit,
|
||||
[string]$Branch = "stable",
|
||||
[string]$RepoPath = "."
|
||||
)
|
||||
|
||||
function Write-Info($msg) { Write-Host $msg -ForegroundColor Cyan }
|
||||
function Write-Err($msg) { Write-Host $msg -ForegroundColor Red }
|
||||
|
||||
Push-Location $RepoPath
|
||||
try {
|
||||
Write-Info "Fetching latest '$Branch' from origin (with tags)..."
|
||||
git fetch origin $Branch --tags | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "git fetch origin $Branch --tags failed" }
|
||||
|
||||
$commitSha = (git rev-parse --verify $Commit) 2>$null
|
||||
if (-not $commitSha) { throw "Commit '$Commit' not found" }
|
||||
|
||||
$subject = (git show -s --format=%s $commitSha) 2>$null
|
||||
if (-not $subject) { throw "Unable to read subject for '$commitSha'" }
|
||||
|
||||
$branchRef = $Branch
|
||||
$branchSha = (git rev-parse --verify $branchRef) 2>$null
|
||||
if (-not $branchSha) {
|
||||
$branchRef = "origin/$Branch"
|
||||
$branchSha = (git rev-parse --verify $branchRef) 2>$null
|
||||
}
|
||||
if (-not $branchSha) { throw "Branch '$Branch' not found" }
|
||||
|
||||
Write-Info "Reference commit: $commitSha"
|
||||
Write-Info "Reference title: $subject"
|
||||
Write-Info "Searching branch: $branchRef"
|
||||
|
||||
$matches = git log $branchRef --format="%H|%s" | Where-Object { $_ -match '\|' }
|
||||
$results = @()
|
||||
foreach ($line in $matches) {
|
||||
$parts = $line -split '\|', 2
|
||||
if ($parts.Count -eq 2 -and $parts[1] -eq $subject) {
|
||||
$results += [PSCustomObject]@{ Sha = $parts[0]; Title = $parts[1] }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $results -or $results.Count -eq 0) {
|
||||
Write-Info "No matching commit found on $branchRef for the given title."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Info ("Found {0} matching commit(s):" -f $results.Count)
|
||||
$results | ForEach-Object { Write-Host ("{0} {1}" -f $_.Sha, $_.Title) }
|
||||
}
|
||||
catch {
|
||||
Write-Err $_
|
||||
exit 1
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Group PR rows by their Labels column and emit per-label CSV files.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads a milestone PR CSV (usually produced by dump-prs-information / dump-prs-since-commit scripts),
|
||||
splits rows by label list, normalizes/sorts individual labels, and writes one CSV per unique label combination.
|
||||
Each output preserves the original row ordering within that subset and column order from the source.
|
||||
|
||||
.PARAMETER CsvPath
|
||||
Input CSV containing PR rows with a 'Labels' column (comma-separated list).
|
||||
|
||||
.PARAMETER OutDir
|
||||
Output directory to place grouped CSVs (created if missing). Default: 'grouped_csv'.
|
||||
|
||||
.NOTES
|
||||
Label combinations are joined using ' | ' when multiple labels present. Filenames are sanitized (invalid characters,
|
||||
whitespace collapsed) and truncated to <= 120 characters.
|
||||
#>
|
||||
param(
|
||||
[string]$CsvPath = "sorted_prs.csv",
|
||||
[string]$OutDir = "grouped_csv"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Info($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
|
||||
function Write-Warn($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $CsvPath)) { throw "CSV not found: $CsvPath" }
|
||||
|
||||
Write-Info "Reading CSV: $CsvPath"
|
||||
$rows = Import-Csv -LiteralPath $CsvPath
|
||||
Write-Info ("Loaded {0} rows" -f $rows.Count)
|
||||
|
||||
function ConvertTo-SafeFileName {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Name
|
||||
)
|
||||
if ([string]::IsNullOrWhiteSpace($Name)) { return 'Unnamed' }
|
||||
$s = $Name -replace '[<>:"/\\|?*]', '-' # invalid path chars
|
||||
$s = $s -replace '\s+', '-' # spaces to dashes
|
||||
$s = $s -replace '-{2,}', '-' # collapse dashes
|
||||
$s = $s.Trim('-')
|
||||
if ($s.Length -gt 120) { $s = $s.Substring(0,120).Trim('-') }
|
||||
if ([string]::IsNullOrWhiteSpace($s)) { return 'Unnamed' }
|
||||
return $s
|
||||
}
|
||||
|
||||
# Build groups keyed by normalized, sorted label combinations. Preserve original CSV row order.
|
||||
$groups = @{}
|
||||
foreach ($row in $rows) {
|
||||
$labelsRaw = $row.Labels
|
||||
if ([string]::IsNullOrWhiteSpace($labelsRaw)) {
|
||||
$labelParts = @('Unlabeled')
|
||||
} else {
|
||||
$parts = $labelsRaw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||
if (-not $parts -or $parts.Count -eq 0) { $labelParts = @('Unlabeled') }
|
||||
else { $labelParts = $parts | Sort-Object }
|
||||
}
|
||||
|
||||
$key = ($labelParts -join ' | ')
|
||||
if (-not $groups.ContainsKey($key)) { $groups[$key] = New-Object System.Collections.ArrayList }
|
||||
[void]$groups[$key].Add($row)
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $OutDir)) {
|
||||
Write-Info "Creating output directory: $OutDir"
|
||||
New-Item -ItemType Directory -Path $OutDir | Out-Null
|
||||
}
|
||||
|
||||
Write-Info ("Generating {0} grouped CSV file(s) into: {1}" -f $groups.Count, $OutDir)
|
||||
|
||||
foreach ($key in $groups.Keys) {
|
||||
$labelParts = if ($key -eq 'Unlabeled') { @('Unlabeled') } else { $key -split '\s\|\s' }
|
||||
$safeName = ($labelParts | ForEach-Object { ConvertTo-SafeFileName -Name $_ }) -join '-'
|
||||
$filePath = Join-Path $OutDir ("$safeName.csv")
|
||||
|
||||
# Keep same columns and order
|
||||
$groups[$key] | Export-Csv -LiteralPath $filePath -NoTypeInformation -Encoding UTF8
|
||||
}
|
||||
|
||||
Write-Info "Done. Sample output files:"
|
||||
Get-ChildItem -LiteralPath $OutDir | Select-Object -First 10 Name | Format-Table -HideTableHeaders
|
||||
@@ -1,334 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Prepares the binary assets for a PowerToys GitHub release: downloads the
|
||||
four installers (per-user/per-machine x x64/arm64) and the symbol archives
|
||||
from an ADO pipeline build, computes SHA256 hashes, and emits the
|
||||
"Installer Hashes" markdown table.
|
||||
|
||||
.DESCRIPTION
|
||||
Given an ADO Dart pipeline build id (e.g. from
|
||||
https://microsoft.visualstudio.com/Dart/_build/results?buildId=NNN),
|
||||
downloads the four installer EXEs and the per-arch symbol zips into a
|
||||
single per-version folder, then writes a hashes.md alongside them with a
|
||||
markdown table ready to paste into the GitHub release notes.
|
||||
|
||||
Requires: az login (Azure CLI authenticated), az devops extension.
|
||||
|
||||
.EXAMPLE
|
||||
.\prepare-release-assets.ps1 -BuildId 145505247
|
||||
.\prepare-release-assets.ps1 -BuildId 145505247 -OutputFolder D:\Releases
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]$BuildId,
|
||||
|
||||
[string]$OutputFolder = "$env:USERPROFILE\Downloads",
|
||||
|
||||
[string]$Organization = "https://dev.azure.com/microsoft",
|
||||
[string]$Project = "Dart",
|
||||
|
||||
[string]$GitHubRepo = "microsoft/PowerToys"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$env:AZURE_CORE_NO_PROMPT = "true"
|
||||
|
||||
# --- Helpers -----------------------------------------------------------------
|
||||
|
||||
# Invoke an `az` CLI command and capture stderr in $script:LastAzError so
|
||||
# callers can surface the underlying message (expired login, blocked extension,
|
||||
# tenant policy, ...) instead of swallowing it with `2>$null`.
|
||||
function Invoke-Az {
|
||||
$tmpErr = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
$output = & az @args 2>$tmpErr
|
||||
# Get-Content -Raw returns $null for an empty file, and calling .Trim()
|
||||
# on $null throws under $ErrorActionPreference = 'Stop' -- which would
|
||||
# turn every successful (no-stderr) az call into a fatal error. Guard
|
||||
# explicitly so $script:LastAzError is always a (possibly empty) string.
|
||||
$rawErr = Get-Content $tmpErr -Raw -ErrorAction SilentlyContinue
|
||||
$script:LastAzError = if ($null -eq $rawErr) { '' } else { $rawErr.Trim() }
|
||||
return $output
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tmpErr -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# Build an ADO artifact download URL from scratch instead of regex-replacing
|
||||
# the URL returned by `az pipelines runs artifact list`. Preserves any other
|
||||
# query parameters and only swaps `format` and `subPath`, so we don't break if
|
||||
# the upstream URL shape ever changes.
|
||||
function Get-ArtifactDownloadUrl {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$BaseUrl,
|
||||
[Parameter(Mandatory)][string]$SubPath,
|
||||
[Parameter(Mandatory)][ValidateSet('file', 'zip')][string]$Format
|
||||
)
|
||||
$encodedSubPath = [Uri]::EscapeDataString($SubPath)
|
||||
$idx = $BaseUrl.IndexOf('?')
|
||||
if ($idx -lt 0) {
|
||||
return "${BaseUrl}?format=${Format}&subPath=${encodedSubPath}"
|
||||
}
|
||||
$base = $BaseUrl.Substring(0, $idx)
|
||||
$kept = $BaseUrl.Substring($idx + 1) -split '&' | Where-Object {
|
||||
$_ -and -not ($_ -match '^(format|subPath)=')
|
||||
}
|
||||
$kept = @($kept) + @("format=$Format", "subPath=$encodedSubPath")
|
||||
return "${base}?$($kept -join '&')"
|
||||
}
|
||||
|
||||
# Download a single ADO artifact file with bearer auth and a small retry/backoff
|
||||
# loop. A transient network blip on a ~200 MB installer or symbol zip otherwise
|
||||
# aborts the entire release-prep run.
|
||||
function Invoke-AdoDownload {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Url,
|
||||
[Parameter(Mandatory)][string]$DestPath,
|
||||
[Parameter(Mandatory)][string]$Token,
|
||||
[int]$MaxAttempts = 3
|
||||
)
|
||||
$lastError = $null
|
||||
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
|
||||
$webClient = New-Object System.Net.WebClient
|
||||
$webClient.Headers.Add("Authorization", "Bearer $Token")
|
||||
try {
|
||||
$webClient.DownloadFile($Url, $DestPath)
|
||||
return
|
||||
}
|
||||
catch {
|
||||
$lastError = $_
|
||||
if (Test-Path $DestPath) {
|
||||
Remove-Item $DestPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($attempt -lt $MaxAttempts) {
|
||||
$backoffSec = [int][Math]::Pow(2, $attempt) # 2, 4, 8 ...
|
||||
Write-Host " Attempt $attempt failed: $($_.Exception.Message). Retrying in ${backoffSec}s..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds $backoffSec
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$webClient.Dispose()
|
||||
}
|
||||
}
|
||||
throw "Download failed after $MaxAttempts attempts. Last error: $($lastError.Exception.Message)`nURL: $Url"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Work around broken az extensions: if the default extension dir has
|
||||
# inaccessible files, redirect to a clean directory.
|
||||
$defaultExtDir = "$env:USERPROFILE\.azure\cliextensions"
|
||||
if (-not $env:AZURE_EXTENSION_DIR -and (Test-Path $defaultExtDir)) {
|
||||
$broken = Get-ChildItem "$defaultExtDir\*\*.dist-info" -Directory -ErrorAction SilentlyContinue | Where-Object {
|
||||
try { [System.IO.Directory]::GetFiles($_.FullName) | Out-Null; $false } catch { $true }
|
||||
}
|
||||
if ($broken) {
|
||||
$cleanDir = "$env:USERPROFILE\.azure\cliextensions_clean"
|
||||
Write-Host " Detected broken az extension, redirecting to $cleanDir" -ForegroundColor Yellow
|
||||
$env:AZURE_EXTENSION_DIR = $cleanDir
|
||||
if (-not (Test-Path $cleanDir)) { New-Item -ItemType Directory -Path $cleanDir -Force | Out-Null }
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure azure-devops extension is installed
|
||||
$ext = Invoke-Az extension list --query "[?name=='azure-devops']" -o tsv
|
||||
if (-not $ext) {
|
||||
Write-Host "Installing azure-devops extension..." -ForegroundColor Yellow
|
||||
Invoke-Az extension add --name azure-devops --yes | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Failed to install azure-devops extension. (az: $script:LastAzError)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Configure az devops defaults
|
||||
Invoke-Az devops configure --defaults organization=$Organization project=$Project | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Failed to configure az devops defaults. (az: $script:LastAzError)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Step 1: Get build info to determine version ---
|
||||
Write-Host "Fetching build $BuildId info..." -ForegroundColor Cyan
|
||||
$buildJson = Invoke-Az pipelines build show --id $BuildId --output json
|
||||
if (-not $buildJson) {
|
||||
Write-Error "Could not fetch build $BuildId. Are you logged in (az login)? (az: $script:LastAzError)"
|
||||
exit 1
|
||||
}
|
||||
$build = $buildJson | ConvertFrom-Json
|
||||
|
||||
$versionParam = $build.templateParameters.VersionNumber
|
||||
if (-not $versionParam) {
|
||||
Write-Error "Could not determine version from build $BuildId"
|
||||
exit 1
|
||||
}
|
||||
Write-Host " Version: $versionParam" -ForegroundColor DarkGray
|
||||
|
||||
# --- Step 2: Get artifact metadata once ---
|
||||
Write-Host "Fetching artifact metadata..." -ForegroundColor Cyan
|
||||
$artifactsJson = Invoke-Az pipelines runs artifact list --run-id $BuildId --output json
|
||||
if (-not $artifactsJson) {
|
||||
Write-Error "Could not list artifacts for build $BuildId. (az: $script:LastAzError)"
|
||||
exit 1
|
||||
}
|
||||
$artifacts = $artifactsJson | ConvertFrom-Json
|
||||
|
||||
# --- Step 3: Prepare destination folder ---
|
||||
$destFolder = Join-Path $OutputFolder "PowerToys-v$versionParam"
|
||||
if (-not (Test-Path $destFolder)) {
|
||||
New-Item -ItemType Directory -Path $destFolder -Force | Out-Null
|
||||
}
|
||||
Write-Host " Destination: $destFolder" -ForegroundColor DarkGray
|
||||
|
||||
# --- Step 4: Get an ADO access token once ---
|
||||
$token = Invoke-Az account get-access-token --resource "499b84ac-1321-427f-aa17-267ca6975798" --query accessToken -o tsv
|
||||
if (-not $token) {
|
||||
Write-Error "Failed to acquire ADO access token. Run 'az login' first. (az: $script:LastAzError)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Step 5: Define the four installers to download ---
|
||||
$targets = @(
|
||||
[pscustomobject]@{ Description = "Per user - x64"; Scope = "perUser"; Arch = "x64"; Artifact = "build-x64-Release"; FileName = "PowerToysUserSetup-$versionParam-x64.exe" }
|
||||
[pscustomobject]@{ Description = "Per user - ARM64"; Scope = "perUser"; Arch = "arm64"; Artifact = "build-arm64-Release"; FileName = "PowerToysUserSetup-$versionParam-arm64.exe" }
|
||||
[pscustomobject]@{ Description = "Machine wide - x64"; Scope = "perMachine"; Arch = "x64"; Artifact = "build-x64-Release"; FileName = "PowerToysSetup-$versionParam-x64.exe" }
|
||||
[pscustomobject]@{ Description = "Machine wide - ARM64"; Scope = "perMachine"; Arch = "arm64"; Artifact = "build-arm64-Release"; FileName = "PowerToysSetup-$versionParam-arm64.exe" }
|
||||
)
|
||||
|
||||
# --- Step 6: Download each installer (skip if already present) ---
|
||||
foreach ($t in $targets) {
|
||||
$destPath = Join-Path $destFolder $t.FileName
|
||||
|
||||
if (Test-Path $destPath) {
|
||||
$sizeMB = [math]::Round((Get-Item $destPath).Length / 1MB, 1)
|
||||
Write-Host "[skip] $($t.FileName) already exists ($sizeMB MB)" -ForegroundColor DarkGray
|
||||
continue
|
||||
}
|
||||
|
||||
$artifact = $artifacts | Where-Object { $_.name -eq $t.Artifact }
|
||||
if (-not $artifact) {
|
||||
Write-Error "Artifact '$($t.Artifact)' not found in build $BuildId. Available: $(($artifacts | ForEach-Object name) -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$fileUrl = Get-ArtifactDownloadUrl -BaseUrl $artifact.resource.downloadUrl -SubPath "/$($t.FileName)" -Format file
|
||||
|
||||
Write-Host "Downloading $($t.FileName) ..." -ForegroundColor Cyan
|
||||
try {
|
||||
Invoke-AdoDownload -Url $fileUrl -DestPath $destPath -Token $token
|
||||
}
|
||||
catch {
|
||||
Write-Error "Download failed for $($t.FileName): $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$sizeMB = [math]::Round((Get-Item $destPath).Length / 1MB, 1)
|
||||
Write-Host " Saved ($sizeMB MB)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# --- Step 6b: Download symbols (one zip per arch) ---
|
||||
$symbolTargets = @(
|
||||
[pscustomobject]@{ Arch = "x64"; Artifact = "build-x64-Release"; SubPath = "/symbols-x64" }
|
||||
[pscustomobject]@{ Arch = "arm64"; Artifact = "build-arm64-Release"; SubPath = "/symbols-arm64" }
|
||||
)
|
||||
|
||||
foreach ($s in $symbolTargets) {
|
||||
$finalZip = Join-Path $destFolder "symbols-$($s.Arch).zip"
|
||||
if (Test-Path $finalZip) {
|
||||
$sizeMB = [math]::Round((Get-Item $finalZip).Length / 1MB, 1)
|
||||
Write-Host "[skip] symbols-$($s.Arch).zip already exists ($sizeMB MB)" -ForegroundColor DarkGray
|
||||
continue
|
||||
}
|
||||
|
||||
$artifact = $artifacts | Where-Object { $_.name -eq $s.Artifact }
|
||||
if (-not $artifact) {
|
||||
Write-Error "Artifact '$($s.Artifact)' not found in build $BuildId."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Symbols are downloaded as a folder => keep format=zip and append subPath
|
||||
$symbolsUrl = Get-ArtifactDownloadUrl -BaseUrl $artifact.resource.downloadUrl -SubPath $s.SubPath -Format zip
|
||||
|
||||
$tmpZip = Join-Path ([System.IO.Path]::GetTempPath()) ("ptsym-$($s.Arch)-$([Guid]::NewGuid().ToString('N')).zip")
|
||||
$tmpExtract = Join-Path ([System.IO.Path]::GetTempPath()) ("ptsym-$($s.Arch)-$([Guid]::NewGuid().ToString('N'))")
|
||||
$stageRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("ptsym-stage-$([Guid]::NewGuid().ToString('N'))")
|
||||
|
||||
try {
|
||||
Write-Host "Downloading symbols-$($s.Arch).zip ..." -ForegroundColor Cyan
|
||||
try {
|
||||
Invoke-AdoDownload -Url $symbolsUrl -DestPath $tmpZip -Token $token
|
||||
}
|
||||
catch {
|
||||
Write-Error "Symbols download failed for $($s.Arch): $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host " Extracting..." -ForegroundColor DarkGray
|
||||
Expand-Archive -Path $tmpZip -DestinationPath $tmpExtract -Force
|
||||
|
||||
# Walk down while the current dir holds exactly one subfolder and no files.
|
||||
$current = Get-Item $tmpExtract
|
||||
while ($true) {
|
||||
$children = Get-ChildItem -LiteralPath $current.FullName -Force
|
||||
$subDirs = @($children | Where-Object { $_.PSIsContainer })
|
||||
$files = @($children | Where-Object { -not $_.PSIsContainer })
|
||||
if ($subDirs.Count -eq 1 -and $files.Count -eq 0) {
|
||||
$current = $subDirs[0]
|
||||
}
|
||||
else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Stage to a folder named symbols-<arch> so the zip extracts to that name.
|
||||
$stageInner = Join-Path $stageRoot "symbols-$($s.Arch)"
|
||||
New-Item -ItemType Directory -Path $stageInner -Force | Out-Null
|
||||
Get-ChildItem -LiteralPath $current.FullName -Force | ForEach-Object {
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $stageInner -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Host " Repacking to $finalZip ..." -ForegroundColor DarkGray
|
||||
if (Test-Path $finalZip) { Remove-Item $finalZip -Force }
|
||||
Compress-Archive -Path "$stageInner\*" -DestinationPath $finalZip -CompressionLevel Optimal
|
||||
|
||||
$sizeMB = [math]::Round((Get-Item $finalZip).Length / 1MB, 1)
|
||||
Write-Host " Saved symbols-$($s.Arch).zip ($sizeMB MB)" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
# Don't leave a half-built zip behind if anything in the pipeline blew up.
|
||||
if (Test-Path $finalZip) { Remove-Item $finalZip -Force -ErrorAction SilentlyContinue }
|
||||
throw
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $tmpExtract -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $stageRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# --- Step 7: Compute SHA256 and build markdown ---
|
||||
Write-Host "`nComputing SHA256 hashes..." -ForegroundColor Cyan
|
||||
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
[void]$sb.AppendLine("## Installer Hashes")
|
||||
[void]$sb.AppendLine("")
|
||||
[void]$sb.AppendLine("| Description | Filename | sha256 hash |")
|
||||
[void]$sb.AppendLine("| --- | --- | --- |")
|
||||
|
||||
foreach ($t in $targets) {
|
||||
$destPath = Join-Path $destFolder $t.FileName
|
||||
$hash = (Get-FileHash -Path $destPath -Algorithm SHA256).Hash.ToUpper()
|
||||
[void]$sb.AppendLine("| $($t.Description) | $($t.FileName) | $hash |")
|
||||
Write-Host " $($t.FileName) $hash" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
$markdown = $sb.ToString()
|
||||
$mdPath = Join-Path $destFolder "hashes.md"
|
||||
Set-Content -Path $mdPath -Value $markdown -Encoding UTF8
|
||||
|
||||
Write-Host "`nMarkdown written to: $mdPath" -ForegroundColor Green
|
||||
Write-Host "`n----- Installer Hashes -----`n" -ForegroundColor Yellow
|
||||
Write-Host $markdown
|
||||
|
||||
Write-Host "Draft a new GitHub release at: https://github.com/$GitHubRepo/releases/new?tag=v$versionParam" -ForegroundColor Green
|
||||
21
.github/skills/winmd-api-search/LICENSE.txt
vendored
21
.github/skills/winmd-api-search/LICENSE.txt
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
192
.github/skills/winmd-api-search/SKILL.md
vendored
192
.github/skills/winmd-api-search/SKILL.md
vendored
@@ -1,192 +0,0 @@
|
||||
---
|
||||
name: winmd-api-search
|
||||
description: 'Find and explore Windows desktop APIs. Use when building features that need platform capabilities — camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc. Discovers the right API for a task and retrieves full type details (methods, properties, events, enumeration values).'
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# WinMD API Search
|
||||
|
||||
This skill helps you find the right Windows API for any capability and get its full details. It searches a local cache of all WinMD metadata from:
|
||||
|
||||
- **Windows Platform SDK** — all `Windows.*` WinRT APIs (always available, no restore needed)
|
||||
- **WinAppSDK / WinUI** — bundled as a baseline in the cache generator (always available, no restore needed)
|
||||
- **NuGet packages** — any additional packages in restored projects that contain `.winmd` files
|
||||
- **Project-output WinMD** — class libraries (C++/WinRT, C#) that produce `.winmd` as build output
|
||||
|
||||
Even on a fresh clone with no restore or build, you still get full Platform SDK + WinAppSDK coverage.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- User wants to build a feature and you need to find which API provides that capability
|
||||
- User asks "how do I do X?" where X involves a platform feature (camera, files, notifications, sensors, AI, etc.)
|
||||
- You need the exact methods, properties, events, or enumeration values of a type before writing code
|
||||
- You're unsure which control, class, or interface to use for a UI or system task
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **.NET SDK 8.0 or later** — required to build the cache generator. Install from [dotnet.microsoft.com](https://dotnet.microsoft.com/download) if not available.
|
||||
|
||||
## Cache Setup (Required Before First Use)
|
||||
|
||||
All query and search commands read from a local JSON cache. **You must generate the cache before running any queries.**
|
||||
|
||||
```powershell
|
||||
# All projects in the repo (recommended for first run)
|
||||
.\.github\skills\winmd-api-search\scripts\Update-WinMdCache.ps1
|
||||
|
||||
# Single project
|
||||
.\.github\skills\winmd-api-search\scripts\Update-WinMdCache.ps1 -ProjectDir <project-folder>
|
||||
```
|
||||
|
||||
No project restore or build is needed for baseline coverage (Platform SDK + WinAppSDK). For additional NuGet packages, the project needs `dotnet restore` (which generates `project.assets.json`) or a `packages.config` file.
|
||||
|
||||
Cache is stored at `Generated Files\winmd-cache\`, deduplicated per-package+version.
|
||||
|
||||
### What gets indexed
|
||||
|
||||
| Source | When available |
|
||||
|--------|----------------|
|
||||
| Windows Platform SDK | Always (reads from local SDK install) |
|
||||
| WinAppSDK (latest) | Always (bundled as baseline in cache generator) |
|
||||
| WinAppSDK Runtime | When installed on the system (detected via `Get-AppxPackage`) |
|
||||
| Project NuGet packages | After `dotnet restore` or with `packages.config` |
|
||||
| Project-output `.winmd` | After project build (class libraries that produce WinMD) |
|
||||
|
||||
> **Note:** This cache directory should be in `.gitignore` — it's generated, not source.
|
||||
|
||||
## How to Use
|
||||
|
||||
Pick the path that matches the situation:
|
||||
|
||||
---
|
||||
|
||||
### Discover — "I don't know which API to use"
|
||||
|
||||
The user describes a capability in their own words. You need to find the right API.
|
||||
|
||||
**0. Ensure the cache exists**
|
||||
|
||||
If the cache hasn't been generated yet, run `Update-WinMdCache.ps1` first — see [Cache Setup](#cache-setup-required-before-first-use) above.
|
||||
|
||||
**1. Translate user language → search keywords**
|
||||
|
||||
Map the user's daily language to programming terms. Try multiple variations:
|
||||
|
||||
| User says | Search keywords to try (in order) |
|
||||
|-----------|-----------------------------------|
|
||||
| "take a picture" | `camera`, `capture`, `photo`, `MediaCapture` |
|
||||
| "load from disk" | `file open`, `picker`, `FileOpen`, `StorageFile` |
|
||||
| "describe what's in it" | `image description`, `Vision`, `Recognition` |
|
||||
| "show a popup" | `dialog`, `flyout`, `popup`, `ContentDialog` |
|
||||
| "drag and drop" | `drag`, `drop`, `DragDrop` |
|
||||
| "save settings" | `settings`, `ApplicationData`, `LocalSettings` |
|
||||
|
||||
Start with simple everyday words. If results are weak or irrelevant, try the more technical variation.
|
||||
|
||||
**2. Run searches**
|
||||
|
||||
```powershell
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action search -Query "<keyword>"
|
||||
```
|
||||
|
||||
This returns ranked namespaces with top matching types and the **JSON file path**.
|
||||
|
||||
If results have **low scores (below 60) or are irrelevant**, fall back to searching online documentation:
|
||||
|
||||
1. Use web search to find the right API on Microsoft Learn, for example:
|
||||
- `site:learn.microsoft.com/uwp/api <capability keywords>` for `Windows.*` APIs
|
||||
- `site:learn.microsoft.com/windows/windows-app-sdk/api/winrt <capability keywords>` for `Microsoft.*` WinAppSDK APIs
|
||||
2. Read the documentation pages to identify which type matches the user's requirement.
|
||||
3. Once you know the type name, come back and use `-Action members` or `-Action enums` to get the exact local signatures.
|
||||
|
||||
**3. Read the JSON to choose the right API**
|
||||
|
||||
Read the file at the path(s) from the top results. The JSON has all types in that namespace — full members, signatures, parameters, return types, enumeration values.
|
||||
|
||||
Read and decide which types and members fit the user's requirement.
|
||||
|
||||
**4. Look up official documentation for context**
|
||||
|
||||
The cache contains only signatures — no descriptions or usage guidance. For explanations, examples, and remarks, look up the type on Microsoft Learn:
|
||||
|
||||
| Namespace prefix | Documentation base URL |
|
||||
|-----------------|----------------------|
|
||||
| `Windows.*` | `https://learn.microsoft.com/uwp/api/{fully.qualified.typename}` |
|
||||
| `Microsoft.*` (WinAppSDK) | `https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/{fully.qualified.typename}` |
|
||||
|
||||
For example, `Microsoft.UI.Xaml.Controls.NavigationView` maps to:
|
||||
`https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.navigationview`
|
||||
|
||||
**5. Use the API knowledge to answer or write code**
|
||||
|
||||
---
|
||||
|
||||
### Lookup — "I know the API, show me the details"
|
||||
|
||||
You already know (or suspect) the type or namespace name. Go direct:
|
||||
|
||||
```powershell
|
||||
# Get all members of a known type
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action members -TypeName "Microsoft.UI.Xaml.Controls.NavigationView"
|
||||
|
||||
# Get enum values
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action enums -TypeName "Microsoft.UI.Xaml.Visibility"
|
||||
|
||||
# List all types in a namespace
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action types -Namespace "Microsoft.UI.Xaml.Controls"
|
||||
|
||||
# Browse namespaces
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action namespaces -Filter "Microsoft.UI"
|
||||
```
|
||||
|
||||
If you need full detail beyond what `-Action members` shows, use `-Action search` to get the JSON file path, then read the JSON file directly.
|
||||
|
||||
---
|
||||
|
||||
### Other Commands
|
||||
|
||||
```powershell
|
||||
# List cached projects
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action projects
|
||||
|
||||
# List packages for a project
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action packages
|
||||
|
||||
# Show stats
|
||||
.\.github\skills\winmd-api-search\scripts\Invoke-WinMdQuery.ps1 -Action stats
|
||||
```
|
||||
|
||||
> If only one project is cached, `-Project` is auto-selected.
|
||||
> If multiple projects exist, add `-Project <name>` (use `-Action projects` to see available names).
|
||||
> In scan mode, manifest names include a short hash suffix to avoid collisions; you can pass the base project name without the suffix if it's unambiguous.
|
||||
|
||||
## Search Scoring
|
||||
|
||||
The search ranks type names and member names against your query:
|
||||
|
||||
| Score | Match type | Example |
|
||||
|-------|-----------|---------|
|
||||
| 100 | Exact name | `Button` → `Button` |
|
||||
| 80 | Starts with | `Navigation` → `NavigationView` |
|
||||
| 60 | Contains | `Dialog` → `ContentDialog` |
|
||||
| 50 | PascalCase initials | `ASB` → `AutoSuggestBox` |
|
||||
| 40 | Multi-keyword AND | `navigation item` → `NavigationViewItem` |
|
||||
| 20 | Fuzzy character match | `NavVw` → `NavigationView` |
|
||||
|
||||
Results are grouped by namespace. Higher-scored namespaces appear first.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| "Cache not found" | Run `Update-WinMdCache.ps1` |
|
||||
| "Multiple projects cached" | Add `-Project <name>` |
|
||||
| "Namespace not found" | Use `-Action namespaces` to list available ones |
|
||||
| "Type not found" | Use fully qualified name (e.g., `Microsoft.UI.Xaml.Controls.Button`) |
|
||||
| Stale after NuGet update | Re-run `Update-WinMdCache.ps1` |
|
||||
| Cache in git history | Add `Generated Files/` to `.gitignore` |
|
||||
|
||||
## References
|
||||
|
||||
- [Windows Platform SDK API reference](https://learn.microsoft.com/uwp/api/) — documentation for `Windows.*` namespaces
|
||||
- [Windows App SDK API reference](https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/) — documentation for `Microsoft.*` WinAppSDK namespaces
|
||||
@@ -1,505 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Query WinMD API metadata from cached JSON files.
|
||||
|
||||
.DESCRIPTION
|
||||
Reads pre-built JSON cache of WinMD types, members, and namespaces.
|
||||
The cache is organized per-package (deduplicated) with project manifests
|
||||
that map each project to its referenced packages.
|
||||
|
||||
Supports listing namespaces, types, members, searching, enum value lookup,
|
||||
and listing cached projects/packages.
|
||||
|
||||
.PARAMETER Action
|
||||
The query action to perform:
|
||||
- projects : List cached projects
|
||||
- packages : List packages for a project
|
||||
- stats : Show aggregate statistics for a project
|
||||
- namespaces : List all namespaces (optional -Filter prefix)
|
||||
- types : List types in a namespace (-Namespace required)
|
||||
- members : List members of a type (-TypeName required)
|
||||
- search : Search types and members by name (-Query required)
|
||||
- enums : List enum values (-TypeName required)
|
||||
|
||||
.PARAMETER Project
|
||||
Project name to query. Auto-selected if only one project is cached.
|
||||
Use -Action projects to list available projects.
|
||||
|
||||
.PARAMETER Namespace
|
||||
Namespace to query types from (used with -Action types).
|
||||
|
||||
.PARAMETER TypeName
|
||||
Full type name e.g. "Microsoft.UI.Xaml.Controls.Button" (used with -Action members, enums).
|
||||
|
||||
.PARAMETER Query
|
||||
Search query string (used with -Action search).
|
||||
|
||||
.PARAMETER Filter
|
||||
Optional prefix filter for namespaces (used with -Action namespaces).
|
||||
|
||||
.PARAMETER CacheDir
|
||||
Path to the winmd-cache directory. Defaults to "Generated Files\winmd-cache"
|
||||
relative to the workspace root.
|
||||
|
||||
.PARAMETER MaxResults
|
||||
Maximum number of results to return for search. Defaults to 30.
|
||||
|
||||
.EXAMPLE
|
||||
.\Invoke-WinMdQuery.ps1 -Action projects
|
||||
.\Invoke-WinMdQuery.ps1 -Action packages -Project BlankWinUI
|
||||
.\Invoke-WinMdQuery.ps1 -Action stats -Project BlankWinUI
|
||||
.\Invoke-WinMdQuery.ps1 -Action namespaces -Filter "Microsoft.UI"
|
||||
.\Invoke-WinMdQuery.ps1 -Action types -Namespace "Microsoft.UI.Xaml.Controls"
|
||||
.\Invoke-WinMdQuery.ps1 -Action members -TypeName "Microsoft.UI.Xaml.Controls.Button"
|
||||
.\Invoke-WinMdQuery.ps1 -Action search -Query "NavigationView"
|
||||
.\Invoke-WinMdQuery.ps1 -Action enums -TypeName "Microsoft.UI.Xaml.Visibility"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet('projects', 'packages', 'stats', 'namespaces', 'types', 'members', 'search', 'enums')]
|
||||
[string]$Action,
|
||||
|
||||
[string]$Project,
|
||||
[string]$Namespace,
|
||||
[string]$TypeName,
|
||||
[string]$Query,
|
||||
[string]$Filter,
|
||||
[string]$CacheDir,
|
||||
[int]$MaxResults = 30
|
||||
)
|
||||
|
||||
# ─── Resolve cache directory ─────────────────────────────────────────────────
|
||||
|
||||
if (-not $CacheDir) {
|
||||
# Convention: skill lives at .github/skills/winmd-api-search/scripts/
|
||||
# so workspace root is 4 levels up from $PSScriptRoot.
|
||||
$scriptDir = $PSScriptRoot
|
||||
$root = (Resolve-Path (Join-Path $scriptDir '..\..\..\..')).Path
|
||||
$CacheDir = Join-Path $root 'Generated Files\winmd-cache'
|
||||
}
|
||||
|
||||
if (-not (Test-Path $CacheDir)) {
|
||||
Write-Error "Cache not found at: $CacheDir`nRun: .\Update-WinMdCache.ps1 (from .github\skills\winmd-api-search\scripts\)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Project resolution helpers ──────────────────────────────────────────────
|
||||
|
||||
function Get-CachedProjects {
|
||||
$projectsDir = Join-Path $CacheDir 'projects'
|
||||
if (-not (Test-Path $projectsDir)) { return @() }
|
||||
Get-ChildItem $projectsDir -Filter '*.json' | ForEach-Object { $_.BaseName }
|
||||
}
|
||||
|
||||
function Resolve-ProjectManifest {
|
||||
param([string]$Name)
|
||||
|
||||
$projectsDir = Join-Path $CacheDir 'projects'
|
||||
if (-not (Test-Path $projectsDir)) {
|
||||
Write-Error "No projects cached. Run Update-WinMdCache.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Name) {
|
||||
$path = Join-Path $projectsDir "$Name.json"
|
||||
if (-not (Test-Path $path)) {
|
||||
# Scan mode appends a hash suffix -- try prefix match
|
||||
$matching = @(Get-ChildItem $projectsDir -Filter "${Name}_*.json" -ErrorAction SilentlyContinue)
|
||||
if ($matching.Count -eq 1) {
|
||||
return Get-Content $matching[0].FullName -Raw | ConvertFrom-Json
|
||||
}
|
||||
if ($matching.Count -gt 1) {
|
||||
$names = ($matching | ForEach-Object { $_.BaseName }) -join ', '
|
||||
Write-Error "Multiple projects match '$Name'. Specify the full name: $names"
|
||||
exit 1
|
||||
}
|
||||
$available = (Get-CachedProjects) -join ', '
|
||||
Write-Error "Project '$Name' not found. Available: $available"
|
||||
exit 1
|
||||
}
|
||||
return Get-Content $path -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
# Auto-select if only one project
|
||||
$manifests = Get-ChildItem $projectsDir -Filter '*.json' -ErrorAction SilentlyContinue
|
||||
if ($manifests.Count -eq 0) {
|
||||
Write-Error "No projects cached. Run Update-WinMdCache.ps1 first."
|
||||
exit 1
|
||||
}
|
||||
if ($manifests.Count -eq 1) {
|
||||
return Get-Content $manifests[0].FullName -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
$available = ($manifests | ForEach-Object { $_.BaseName }) -join ', '
|
||||
Write-Error "Multiple projects cached -- use -Project to specify. Available: $available"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Get-PackageCacheDirs {
|
||||
param($Manifest)
|
||||
$dirs = @()
|
||||
foreach ($pkg in $Manifest.packages) {
|
||||
$dir = Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version
|
||||
if (Test-Path $dir) {
|
||||
$dirs += $dir
|
||||
}
|
||||
}
|
||||
return $dirs
|
||||
}
|
||||
|
||||
# ─── Action: projects ────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Projects {
|
||||
$projects = Get-CachedProjects
|
||||
if ($projects.Count -eq 0) {
|
||||
Write-Output "No projects cached."
|
||||
return
|
||||
}
|
||||
Write-Output "Cached projects ($($projects.Count)):"
|
||||
foreach ($p in $projects) {
|
||||
$manifest = Get-Content (Join-Path (Join-Path $CacheDir 'projects') "$p.json") -Raw | ConvertFrom-Json
|
||||
$pkgCount = $manifest.packages.Count
|
||||
Write-Output " $p ($pkgCount package(s))"
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: packages ────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Packages {
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
Write-Output "Packages for project '$($manifest.projectName)' ($($manifest.packages.Count)):"
|
||||
foreach ($pkg in $manifest.packages) {
|
||||
$metaPath = Join-Path (Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version) 'meta.json'
|
||||
if (Test-Path $metaPath) {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
Write-Output " $($pkg.id)@$($pkg.version) -- $($meta.totalTypes) types, $($meta.totalMembers) members"
|
||||
} else {
|
||||
Write-Output " $($pkg.id)@$($pkg.version) -- (cache missing)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: stats ───────────────────────────────────────────────────────────
|
||||
|
||||
function Show-Stats {
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$totalTypes = 0
|
||||
$totalMembers = 0
|
||||
$totalNamespaces = 0
|
||||
$totalWinMd = 0
|
||||
|
||||
foreach ($pkg in $manifest.packages) {
|
||||
$metaPath = Join-Path (Join-Path (Join-Path (Join-Path $CacheDir 'packages') $pkg.id) $pkg.version) 'meta.json'
|
||||
if (Test-Path $metaPath) {
|
||||
$meta = Get-Content $metaPath -Raw | ConvertFrom-Json
|
||||
$totalTypes += $meta.totalTypes
|
||||
$totalMembers += $meta.totalMembers
|
||||
$totalNamespaces += $meta.totalNamespaces
|
||||
$totalWinMd += $meta.winMdFiles.Count
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "WinMD Index Statistics -- $($manifest.projectName)"
|
||||
Write-Output "======================================"
|
||||
Write-Output " Packages: $($manifest.packages.Count)"
|
||||
Write-Output " Namespaces: $totalNamespaces (may overlap across packages)"
|
||||
Write-Output " Types: $totalTypes"
|
||||
Write-Output " Members: $totalMembers"
|
||||
Write-Output " WinMD files: $totalWinMd"
|
||||
}
|
||||
|
||||
# ─── Action: namespaces ──────────────────────────────────────────────────────
|
||||
|
||||
function Get-Namespaces {
|
||||
param([string]$Prefix)
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
$allNs = @()
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$nsFile = Join-Path $dir 'namespaces.json'
|
||||
if (Test-Path $nsFile) {
|
||||
$allNs += (Get-Content $nsFile -Raw | ConvertFrom-Json)
|
||||
}
|
||||
}
|
||||
|
||||
$allNs = $allNs | Sort-Object -Unique
|
||||
if ($Prefix) {
|
||||
$allNs = $allNs | Where-Object { $_ -like "$Prefix*" }
|
||||
}
|
||||
$allNs | ForEach-Object { Write-Output $_ }
|
||||
}
|
||||
|
||||
# ─── Action: types ───────────────────────────────────────────────────────────
|
||||
|
||||
function Get-TypesInNamespace {
|
||||
param([string]$Ns)
|
||||
if (-not $Ns) {
|
||||
Write-Error "-Namespace is required for 'types' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
$safeFile = $Ns.Replace('.', '_') + '.json'
|
||||
$found = $false
|
||||
$seen = @{}
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
$found = $true
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
foreach ($t in $types) {
|
||||
if ($seen.ContainsKey($t.fullName)) { continue }
|
||||
$seen[$t.fullName] = $true
|
||||
Write-Output "$($t.kind) $($t.fullName)$(if ($t.baseType) { " : $($t.baseType)" } else { '' })"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $found) {
|
||||
Write-Error "Namespace not found: $Ns"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Action: members ─────────────────────────────────────────────────────────
|
||||
|
||||
function Get-MembersOfType {
|
||||
param([string]$FullName)
|
||||
if (-not $FullName) {
|
||||
Write-Error "-TypeName is required for 'members' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$lastDot = $FullName.LastIndexOf('.')
|
||||
if ($lastDot -lt 0) {
|
||||
Write-Error "-TypeName must include a namespace (for example: 'MyNamespace.MyType'). Provided: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ns = $FullName.Substring(0, $lastDot)
|
||||
$safeFile = $ns.Replace('.', '_') + '.json'
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
$type = $types | Where-Object { $_.fullName -eq $FullName }
|
||||
if (-not $type) { continue }
|
||||
|
||||
Write-Output "$($type.kind) $($type.fullName)"
|
||||
if ($type.baseType) { Write-Output " Extends: $($type.baseType)" }
|
||||
Write-Output ""
|
||||
foreach ($m in $type.members) {
|
||||
Write-Output " [$($m.kind)] $($m.signature)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Error "Type not found: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Action: search ──────────────────────────────────────────────────────────
|
||||
# Ranks namespaces by best match score on type names and member names.
|
||||
# Outputs: ranked namespaces with top matching types and the JSON file path.
|
||||
# The agent can then read the JSON file to inspect all members intelligently.
|
||||
|
||||
function Search-WinMd {
|
||||
param([string]$SearchQuery, [int]$Max)
|
||||
if (-not $SearchQuery) {
|
||||
Write-Error "-Query is required for 'search' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
# Collect: namespace -> { bestScore, matchingTypes[], filePath }
|
||||
$nsResults = @{}
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$nsFile = Join-Path $dir 'namespaces.json'
|
||||
if (-not (Test-Path $nsFile)) { continue }
|
||||
$nsList = Get-Content $nsFile -Raw | ConvertFrom-Json
|
||||
|
||||
foreach ($n in $nsList) {
|
||||
$safeFile = $n.Replace('.', '_') + '.json'
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
foreach ($t in $types) {
|
||||
$typeScore = Get-MatchScore -Name $t.name -FullName $t.fullName -Query $SearchQuery
|
||||
|
||||
# Also search member names for matches
|
||||
$bestMemberScore = 0
|
||||
$matchingMember = $null
|
||||
if ($t.members) {
|
||||
foreach ($m in $t.members) {
|
||||
$memberName = $m.name
|
||||
$mScore = Get-MatchScore -Name $memberName -FullName "$($t.fullName).$memberName" -Query $SearchQuery
|
||||
if ($mScore -gt $bestMemberScore) {
|
||||
$bestMemberScore = $mScore
|
||||
$matchingMember = $m.signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$score = [Math]::Max($typeScore, $bestMemberScore)
|
||||
if ($score -le 0) { continue }
|
||||
|
||||
if (-not $nsResults.ContainsKey($n)) {
|
||||
$nsResults[$n] = @{ BestScore = 0; Types = @(); FilePaths = @() }
|
||||
}
|
||||
$entry = $nsResults[$n]
|
||||
if ($score -gt $entry.BestScore) { $entry.BestScore = $score }
|
||||
if ($entry.FilePaths -notcontains $filePath) {
|
||||
$entry.FilePaths += $filePath
|
||||
}
|
||||
|
||||
if ($typeScore -ge $bestMemberScore) {
|
||||
$entry.Types += @{ Text = "$($t.kind) $($t.fullName) [$typeScore]"; Score = $typeScore }
|
||||
} else {
|
||||
$entry.Types += @{ Text = "$($t.kind) $($t.fullName) -> $matchingMember [$bestMemberScore]"; Score = $bestMemberScore }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($nsResults.Count -eq 0) {
|
||||
Write-Output "No results found for: $SearchQuery"
|
||||
return
|
||||
}
|
||||
|
||||
$ranked = $nsResults.GetEnumerator() |
|
||||
Sort-Object { $_.Value.BestScore } -Descending |
|
||||
Select-Object -First $Max
|
||||
|
||||
foreach ($r in $ranked) {
|
||||
$ns = $r.Key
|
||||
$info = $r.Value
|
||||
Write-Output "[$($info.BestScore)] $ns"
|
||||
foreach ($fp in $info.FilePaths) {
|
||||
Write-Output " File: $fp"
|
||||
}
|
||||
# Show top 5 highest-scoring matching types in this namespace
|
||||
$info.Types | Sort-Object { $_.Score } -Descending |
|
||||
Select-Object -First 5 |
|
||||
ForEach-Object { Write-Output " $($_.Text)" }
|
||||
Write-Output ""
|
||||
}
|
||||
}
|
||||
|
||||
# ─── Search scoring ──────────────────────────────────────────────────────────
|
||||
# Simple ranked scoring on type names. Higher = better.
|
||||
# 100 = exact name 80 = starts-with 60 = substring
|
||||
# 50 = PascalCase 40 = multi-keyword 20 = fuzzy subsequence
|
||||
|
||||
function Get-MatchScore {
|
||||
param([string]$Name, [string]$FullName, [string]$Query)
|
||||
|
||||
$q = $Query.Trim()
|
||||
if (-not $q) { return 0 }
|
||||
|
||||
if ($Name -eq $q) { return 100 }
|
||||
if ($Name -like "$q*") { return 80 }
|
||||
if ($Name -like "*$q*" -or $FullName -like "*$q*") { return 60 }
|
||||
|
||||
$initials = ($Name.ToCharArray() | Where-Object { [char]::IsUpper($_) }) -join ''
|
||||
if ($initials.Length -ge 2 -and $initials -like "*$q*") { return 50 }
|
||||
|
||||
$words = $q -split '\s+' | Where-Object { $_.Length -gt 0 }
|
||||
if ($words.Count -gt 1) {
|
||||
$allFound = $true
|
||||
foreach ($w in $words) {
|
||||
if ($Name -notlike "*$w*" -and $FullName -notlike "*$w*") {
|
||||
$allFound = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($allFound) { return 40 }
|
||||
}
|
||||
|
||||
if (Test-FuzzySubsequence -Text $Name -Pattern $q) { return 20 }
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function Test-FuzzySubsequence {
|
||||
param([string]$Text, [string]$Pattern)
|
||||
$ti = 0
|
||||
$tLower = $Text.ToLowerInvariant()
|
||||
$pLower = $Pattern.ToLowerInvariant()
|
||||
foreach ($ch in $pLower.ToCharArray()) {
|
||||
$idx = $tLower.IndexOf($ch, $ti)
|
||||
if ($idx -lt 0) { return $false }
|
||||
$ti = $idx + 1
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
# ─── Action: enums ───────────────────────────────────────────────────────────
|
||||
|
||||
function Get-EnumValues {
|
||||
param([string]$FullName)
|
||||
if (-not $FullName) {
|
||||
Write-Error "-TypeName is required for 'enums' action."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$lastDot = $FullName.LastIndexOf('.')
|
||||
if ($lastDot -lt 1) {
|
||||
Write-Error "-TypeName must be a fully-qualified type name including namespace, e.g. 'Namespace.TypeName'. Provided: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ns = $FullName.Substring(0, $lastDot)
|
||||
$safeFile = $ns.Replace('.', '_') + '.json'
|
||||
|
||||
$manifest = Resolve-ProjectManifest -Name $Project
|
||||
$dirs = Get-PackageCacheDirs -Manifest $manifest
|
||||
|
||||
foreach ($dir in $dirs) {
|
||||
$filePath = Join-Path $dir "types\$safeFile"
|
||||
if (-not (Test-Path $filePath)) { continue }
|
||||
|
||||
$types = Get-Content $filePath -Raw | ConvertFrom-Json
|
||||
$type = $types | Where-Object { $_.fullName -eq $FullName }
|
||||
if (-not $type) { continue }
|
||||
|
||||
if ($type.kind -ne 'Enum') {
|
||||
Write-Error "$FullName is not an Enum (kind: $($type.kind))"
|
||||
exit 1
|
||||
}
|
||||
Write-Output "Enum $($type.fullName)"
|
||||
if ($type.enumValues) {
|
||||
$type.enumValues | ForEach-Object { Write-Output " $_" }
|
||||
} else {
|
||||
Write-Output " (no values)"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Write-Error "Type not found: $FullName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ─── Dispatch ─────────────────────────────────────────────────────────────────
|
||||
|
||||
switch ($Action) {
|
||||
'projects' { Show-Projects }
|
||||
'packages' { Show-Packages }
|
||||
'stats' { Show-Stats }
|
||||
'namespaces' { Get-Namespaces -Prefix $Filter }
|
||||
'types' { Get-TypesInNamespace -Ns $Namespace }
|
||||
'members' { Get-MembersOfType -FullName $TypeName }
|
||||
'search' { Search-WinMd -SearchQuery $Query -Max $MaxResults }
|
||||
'enums' { Get-EnumValues -FullName $TypeName }
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generate or refresh the WinMD cache for the Agent Skill.
|
||||
|
||||
.DESCRIPTION
|
||||
Builds and runs the standalone cache generator to export cached JSON files
|
||||
from all WinMD metadata found in project NuGet packages and Windows SDK.
|
||||
|
||||
The cache is per-package+version: if two projects reference the same
|
||||
package at the same version, the WinMD data is parsed once and shared.
|
||||
|
||||
Supports single project or recursive scan of an entire repo.
|
||||
|
||||
.PARAMETER ProjectDir
|
||||
Path to a project directory (contains .csproj/.vcxproj), or a project file itself.
|
||||
Defaults to scanning the workspace root.
|
||||
|
||||
.PARAMETER Scan
|
||||
Recursively discover all .csproj/.vcxproj files under ProjectDir.
|
||||
|
||||
.PARAMETER OutputDir
|
||||
Path to the cache output directory. Defaults to "Generated Files\winmd-cache".
|
||||
|
||||
.EXAMPLE
|
||||
.\Update-WinMdCache.ps1
|
||||
.\Update-WinMdCache.ps1 -ProjectDir BlankWinUI
|
||||
.\Update-WinMdCache.ps1 -Scan -ProjectDir .
|
||||
.\Update-WinMdCache.ps1 -ProjectDir "src\MyApp\MyApp.csproj"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$ProjectDir,
|
||||
[switch]$Scan,
|
||||
[string]$OutputDir = 'Generated Files\winmd-cache'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Convention: skill lives at .github/skills/winmd-api-search/scripts/
|
||||
# so workspace root is 4 levels up from $PSScriptRoot.
|
||||
$root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')).Path
|
||||
$generatorProj = Join-Path (Join-Path $PSScriptRoot 'cache-generator') 'CacheGenerator.csproj'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WinAppSDK version detection -- look only at the repo root folder (no recursion)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-WinAppSdkVersionFromDirectoryPackagesProps {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract Microsoft.WindowsAppSDK version from a Directory.Packages.props
|
||||
(Central Package Management) at the repo root.
|
||||
#>
|
||||
param([string]$RepoRoot)
|
||||
$propsFile = Join-Path $RepoRoot 'Directory.Packages.props'
|
||||
if (-not (Test-Path $propsFile)) { return $null }
|
||||
try {
|
||||
[xml]$xml = Get-Content $propsFile -Raw
|
||||
$node = $xml.SelectNodes('//PackageVersion') |
|
||||
Where-Object { $_.Include -eq 'Microsoft.WindowsAppSDK' } |
|
||||
Select-Object -First 1
|
||||
if ($node) { return $node.Version }
|
||||
} catch {
|
||||
Write-Verbose "Could not parse $propsFile : $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-WinAppSdkVersionFromPackagesConfig {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract Microsoft.WindowsAppSDK version from a packages.config at the repo root.
|
||||
#>
|
||||
param([string]$RepoRoot)
|
||||
$configFile = Join-Path $RepoRoot 'packages.config'
|
||||
if (-not (Test-Path $configFile)) { return $null }
|
||||
try {
|
||||
[xml]$xml = Get-Content $configFile -Raw
|
||||
$node = $xml.SelectNodes('//package') |
|
||||
Where-Object { $_.id -eq 'Microsoft.WindowsAppSDK' } |
|
||||
Select-Object -First 1
|
||||
if ($node) { return $node.version }
|
||||
} catch {
|
||||
Write-Verbose "Could not parse $configFile : $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Try Directory.Packages.props first (CPM), then packages.config
|
||||
$winAppSdkVersion = Get-WinAppSdkVersionFromDirectoryPackagesProps -RepoRoot $root
|
||||
if (-not $winAppSdkVersion) {
|
||||
$winAppSdkVersion = Get-WinAppSdkVersionFromPackagesConfig -RepoRoot $root
|
||||
}
|
||||
if ($winAppSdkVersion) {
|
||||
Write-Host "Detected WinAppSDK version from repo: $winAppSdkVersion" -ForegroundColor Cyan
|
||||
} else {
|
||||
Write-Host "No WinAppSDK version found at repo root; will use latest (Version=*)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Default: if no ProjectDir, scan the workspace root
|
||||
if (-not $ProjectDir) {
|
||||
$ProjectDir = $root
|
||||
$Scan = $true
|
||||
}
|
||||
|
||||
Push-Location $root
|
||||
|
||||
try {
|
||||
# Detect installed .NET SDK -- require >= 8.0, prefer stable over preview
|
||||
$dotnetSdks = dotnet --list-sdks 2>$null
|
||||
$bestMajor = $dotnetSdks |
|
||||
Where-Object { $_ -notmatch 'preview|rc|alpha|beta' } |
|
||||
ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
|
||||
Where-Object { $_ -ge 8 } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
# Fall back to preview SDKs if no stable SDK found
|
||||
if (-not $bestMajor) {
|
||||
$bestMajor = $dotnetSdks |
|
||||
ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
|
||||
Where-Object { $_ -ge 8 } |
|
||||
Sort-Object -Descending |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
if (-not $bestMajor) {
|
||||
Write-Error "No .NET SDK >= 8.0 found. Install from https://dotnet.microsoft.com/download"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$targetFramework = "net$bestMajor.0"
|
||||
Write-Host "Using .NET SDK: $targetFramework" -ForegroundColor Cyan
|
||||
|
||||
# Build MSBuild properties -- pass detected WinAppSDK version when available
|
||||
$sdkVersionProp = ''
|
||||
if ($winAppSdkVersion) {
|
||||
$sdkVersionProp = "-p:WinAppSdkVersion=$winAppSdkVersion"
|
||||
}
|
||||
|
||||
Write-Host "Building cache generator..." -ForegroundColor Cyan
|
||||
$restoreArgs = @($generatorProj, "-p:TargetFramework=$targetFramework", '--nologo', '-v', 'q')
|
||||
if ($sdkVersionProp) { $restoreArgs += $sdkVersionProp }
|
||||
dotnet restore @restoreArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Restore failed"
|
||||
exit 1
|
||||
}
|
||||
$buildArgs = @($generatorProj, '-c', 'Release', '--nologo', '-v', 'q', "-p:TargetFramework=$targetFramework", '--no-restore')
|
||||
if ($sdkVersionProp) { $buildArgs += $sdkVersionProp }
|
||||
dotnet build @buildArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Build failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run the built executable directly (avoids dotnet run target framework mismatch issues)
|
||||
$generatorDir = Join-Path $PSScriptRoot 'cache-generator'
|
||||
$exePath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.exe"
|
||||
if (-not (Test-Path $exePath)) {
|
||||
# Fallback: try dll with dotnet
|
||||
$dllPath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.dll"
|
||||
if (Test-Path $dllPath) {
|
||||
$exePath = $null
|
||||
} else {
|
||||
Write-Error "Built executable not found at: $exePath"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$runArgs = @()
|
||||
if ($Scan) {
|
||||
$runArgs += '--scan'
|
||||
}
|
||||
|
||||
# Detect installed WinAppSDK runtime via Get-AppxPackage (the WindowsApps
|
||||
# folder is ACL-restricted so C# cannot enumerate it directly).
|
||||
# WinMD files are architecture-independent metadata, so pick whichever arch
|
||||
# matches the current OS to ensure the package is present.
|
||||
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
|
||||
$runtimePkg = Get-AppxPackage -Name 'Microsoft.WindowsAppRuntime.*' -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -notmatch 'CBS' -and $_.Architecture -eq $osArch } |
|
||||
Sort-Object -Property Version -Descending |
|
||||
Select-Object -First 1
|
||||
if ($runtimePkg -and $runtimePkg.InstallLocation -and (Test-Path $runtimePkg.InstallLocation)) {
|
||||
Write-Host "Detected WinAppSDK runtime: $($runtimePkg.Name) v$($runtimePkg.Version)" -ForegroundColor Cyan
|
||||
$runArgs += '--winappsdk-runtime'
|
||||
$runArgs += $runtimePkg.InstallLocation
|
||||
}
|
||||
|
||||
$runArgs += $ProjectDir
|
||||
$runArgs += $OutputDir
|
||||
|
||||
Write-Host "Exporting WinMD cache..." -ForegroundColor Cyan
|
||||
if ($exePath) {
|
||||
& $exePath @runArgs
|
||||
} else {
|
||||
dotnet $dllPath @runArgs
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "Cache export failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Cache updated at: $OutputDir" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<!-- Default fallback; Update-WinMdCache.ps1 overrides via -p:TargetFramework=net{X}.0 -->
|
||||
<TargetFramework Condition="'$(TargetFramework)' == ''">net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<!-- System.Reflection.Metadata is inbox in net9.0+, only needed for net8.0 -->
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Baseline WinAppSDK packages: downloaded during restore so the cache generator
|
||||
can always index WinAppSDK APIs, even if the target project hasn't been restored.
|
||||
ExcludeAssets="all" means they're downloaded but don't affect this tool's build.
|
||||
|
||||
When the repo has a known version (passed via -p:WinAppSdkVersion=X.Y.Z from
|
||||
Update-WinMdCache.ps1), prefer that version to avoid unnecessary NuGet downloads.
|
||||
Falls back to Version="*" (latest) on fresh clones with no restore.
|
||||
-->
|
||||
<ItemGroup Condition="'$(WinAppSdkVersion)' != ''">
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="$(WinAppSdkVersion)" ExcludeAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(WinAppSdkVersion)' == ''">
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="*" ExcludeAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level build configuration -->
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level build targets -->
|
||||
</Project>
|
||||
@@ -1,3 +0,0 @@
|
||||
<Project>
|
||||
<!-- Isolate this standalone tool from the repo-level Central Package Management -->
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
165
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
165
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
@@ -1,165 +0,0 @@
|
||||
---
|
||||
name: wpf-to-winui3-migration
|
||||
description: Guide for migrating PowerToys modules from WPF to WinUI 3 (Windows App SDK). Use when asked to migrate WPF code, convert WPF XAML to WinUI, replace System.Windows namespaces with Microsoft.UI.Xaml, update Dispatcher to DispatcherQueue, replace DynamicResource with ThemeResource, migrate imaging APIs from System.Windows.Media.Imaging to Windows.Graphics.Imaging, convert WPF Window to WinUI Window, migrate .resx to .resw resources, migrate custom Observable/RelayCommand to CommunityToolkit.Mvvm source generators, handle WPF-UI (Lepo) to WinUI native control migration, or fix installer/build pipeline issues after migration. Keywords: WPF, WinUI, WinUI3, migration, porting, convert, namespace, XAML, Dispatcher, DispatcherQueue, imaging, BitmapImage, Window, ContentDialog, ThemeResource, DynamicResource, ResourceLoader, resw, resx, CommunityToolkit, ObservableProperty, WPF-UI, SizeToContent, AppWindow, SoftwareBitmap.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
# WPF to WinUI 3 Migration Skill
|
||||
|
||||
Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.UI.Xaml.*` / Windows App SDK). Based on patterns validated in the ImageResizer module migration.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Migrate a PowerToys module from WPF to WinUI 3
|
||||
- Convert WPF XAML files to WinUI 3 XAML
|
||||
- Replace `System.Windows` namespaces with `Microsoft.UI.Xaml`
|
||||
- Migrate `Dispatcher` usage to `DispatcherQueue`
|
||||
- Migrate custom `Observable`/`RelayCommand` to CommunityToolkit.Mvvm source generators
|
||||
- Replace WPF-UI (Lepo) controls with native WinUI 3 controls
|
||||
- Convert imaging code from `System.Windows.Media.Imaging` to `Windows.Graphics.Imaging`
|
||||
- Handle WPF `Window` vs WinUI `Window` differences (sizing, positioning, SizeToContent)
|
||||
- Migrate resource files from `.resx` to `.resw` with `ResourceLoader`
|
||||
- Fix installer/build pipeline issues after WinUI 3 migration
|
||||
- Update project files, NuGet packages, and signing config
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Visual Studio 2022 17.4+
|
||||
- Windows App SDK NuGet package (`Microsoft.WindowsAppSDK`)
|
||||
- .NET 8+ with `net8.0-windows10.0.19041.0` TFM
|
||||
- Windows 10 1803+ (April 2018 Update or newer)
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Recommended Order
|
||||
|
||||
1. **Project file** — Update TFM, NuGet packages, set `<UseWinUI>true</UseWinUI>`
|
||||
2. **Data models and business logic** — No UI dependencies, migrate first
|
||||
3. **MVVM framework** — Replace custom Observable/RelayCommand with CommunityToolkit.Mvvm
|
||||
4. **Resource strings** — Migrate `.resx` → `.resw`, introduce `ResourceLoaderInstance`
|
||||
5. **Services and utilities** — Replace `System.Windows` types, async-ify imaging code
|
||||
6. **ViewModels** — Update Dispatcher usage, binding patterns
|
||||
7. **Views/Pages** — Starting from leaf pages with fewest dependencies
|
||||
8. **Main page / shell** — Last, since it depends on everything
|
||||
9. **App.xaml / startup code** — Merge carefully (do NOT overwrite WinUI 3 boilerplate)
|
||||
10. **Installer & build pipeline** — Update WiX, signing, build events
|
||||
11. **Tests** — Adapt for WinUI 3 runtime, async patterns
|
||||
|
||||
### Key Principles
|
||||
|
||||
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different application lifecycle boilerplate. Merge your resources and initialization code into the generated WinUI 3 App class.
|
||||
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. This causes phantom build artifacts.
|
||||
- **Use `Lazy<T>` for resource-dependent statics** — `ResourceLoader` is not available at class-load time in all contexts.
|
||||
|
||||
## Quick Reference Tables
|
||||
|
||||
### Namespace Mapping
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
|
||||
### Critical API Replacements
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`) |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` | Property vs method |
|
||||
| `Application.Current.Dispatcher` | Store `DispatcherQueue` in static field | See [Threading](./references/threading-and-windowing.md) |
|
||||
| `MessageBox.Show()` | `ContentDialog` | Must set `XamlRoot` |
|
||||
| `DynamicResource` | `ThemeResource` | Theme-reactive only |
|
||||
| `clr-namespace:` | `using:` | XAML namespace prefix |
|
||||
| `{x:Static props:Resources.Key}` | `x:Uid` or `ResourceLoader.GetString()` | .resx → .resw |
|
||||
| `DataType="{x:Type m:Foo}"` | Remove or use code-behind | `x:Type` not supported |
|
||||
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
|
||||
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
|
||||
| `SizeToContent="Height"` | Custom `SizeToContent()` via `AppWindow.Resize()` | See [Windowing](./references/threading-and-windowing.md) |
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Mouse → Pointer events |
|
||||
| `Pack URI (pack://...)` | `ms-appx:///` | Resource URI scheme |
|
||||
| `Observable` (custom base) | `ObservableObject` + `[ObservableProperty]` | CommunityToolkit.Mvvm |
|
||||
| `RelayCommand` (custom) | `[RelayCommand]` source generator | CommunityToolkit.Mvvm |
|
||||
| `JpegBitmapEncoder` | `BitmapEncoder.CreateAsync(JpegEncoderId, stream)` | Async, unified API |
|
||||
| `encoder.QualityLevel = 85` | `BitmapPropertySet { "ImageQuality", 0.85f }` | int 1-100 → float 0-1 |
|
||||
|
||||
### NuGet Package Migration
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` |
|
||||
| `WPF-UI` (Lepo) | Remove — use native WinUI 3 controls |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` |
|
||||
| (none) | `Microsoft.WindowsAppSDK` |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` |
|
||||
| (none) | `WinUIEx` (optional, for window helpers) |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` |
|
||||
|
||||
### XAML Syntax Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` |
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind |
|
||||
| `{x:Type local:MyType}` | Not supported |
|
||||
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` |
|
||||
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` |
|
||||
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` |
|
||||
| `<ui:FluentWindow>` / `<ui:Button>` (WPF-UI) | Native `<Window>` / `<Button>` |
|
||||
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` |
|
||||
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / handle via KeyDown |
|
||||
| `<AccessText>` | Not available — use `AccessKey` property |
|
||||
| `<behaviors:Interaction.Triggers>` | Migrate to code-behind or WinUI behaviors |
|
||||
|
||||
## Detailed Reference Docs
|
||||
|
||||
Read only the section relevant to your current task:
|
||||
|
||||
- [Namespace and API Mapping](./references/namespace-api-mapping.md) — Full type mapping, NuGet changes, project file, CsWinRT interop
|
||||
- [XAML Migration Guide](./references/xaml-migration.md) — XAML syntax, WPF-UI removal, markup extensions, styles, resources, data binding
|
||||
- [Threading and Window Management](./references/threading-and-windowing.md) — Dispatcher, DispatcherQueue, SizeToContent, AppWindow, HWND interop, custom entry point
|
||||
- [Imaging API Migration](./references/imaging-migration.md) — BitmapEncoder/Decoder, SoftwareBitmap, CodecHelper, async patterns, int→uint
|
||||
- [PowerToys-Specific Patterns](./references/powertoys-patterns.md) — MVVM migration, ResourceLoader, Lazy init, installer, signing, test adaptation, build pipeline
|
||||
|
||||
## Common Pitfalls (from ImageResizer migration)
|
||||
|
||||
| Pitfall | Solution |
|
||||
|---------|----------|
|
||||
| `ContentDialog` throws "does not have a XamlRoot" | Set `dialog.XamlRoot = this.Content.XamlRoot` before `ShowAsync()` |
|
||||
| `FilePicker` throws error in desktop app | Call `WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd)` |
|
||||
| `Window.Dispatcher` returns null | Use `Window.DispatcherQueue` instead |
|
||||
| Resources on `Window` element not found | Move resources to root layout container (`Grid.Resources`) |
|
||||
| `VisualStateManager` on `Window` fails | Use `UserControl` or `Page` inside the Window |
|
||||
| Satellite assembly installer errors (`WIX0103`) | Remove `.resources.dll` refs from `Resources.wxs`; WinUI 3 uses `.pri` |
|
||||
| Phantom `.exe`/`.deps.json` in root output dir | Avoid Exe→WinExe `ProjectReference`; use Library project |
|
||||
| `ResourceLoader` crash at static init | Wrap in `Lazy<T>` or null-coalescing property — see [Lazy Init](./references/powertoys-patterns.md#lazy-initialization-for-resource-dependent-statics) |
|
||||
| `SizeToContent` not available | Implement manual content measurement + `AppWindow.Resize()` with DPI scaling |
|
||||
| `x:Bind` default mode is `OneTime` | Explicitly set `Mode=OneWay` or `Mode=TwoWay` |
|
||||
| `DynamicResource` / `x:Static` not compiling | Replace with `ThemeResource` / `ResourceLoader` or `x:Uid` |
|
||||
| `IValueConverter.Convert` signature mismatch | Last param: `CultureInfo` → `string` (language tag) |
|
||||
| Test project can't resolve WPF types | Add `<UseWPF>true</UseWPF>` temporarily; remove after imaging migration |
|
||||
| Pixel dimension type mismatch (`int` vs `uint`) | WinRT uses `uint` for pixel sizes — add `u` suffix in test assertions |
|
||||
| `$(SolutionDir)` empty in standalone project build | Use `$(MSBuildThisFileDirectory)` with relative paths instead |
|
||||
| JPEG quality value wrong after migration | WPF: int 1-100; WinRT: float 0.0-1.0 |
|
||||
| MSIX packaging fails in PreBuildEvent | Move to PostBuildEvent; artifacts not ready at PreBuild time |
|
||||
| RC file icon path with forward slashes | Use double-backslash escaping: `..\\ui\\Assets\\icon.ico` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Build fails after namespace rename | Check for lingering `System.Windows` usings; some types have no direct equivalent |
|
||||
| Missing `PresentationCore.dll` at runtime | Ensure ALL imaging code uses `Windows.Graphics.Imaging`, not `System.Windows.Media.Imaging` |
|
||||
| `DataContext` not working on Window | WinUI 3 `Window` is not a `DependencyObject`; use a root `Page` or `UserControl` |
|
||||
| XAML designer not available | WinUI 3 does not support XAML Designer; use Hot Reload instead |
|
||||
| NuGet restore failures | Run `build-essentials.cmd` after adding `Microsoft.WindowsAppSDK` package |
|
||||
| `Parallel.ForEach` compilation error | Migrate to `Parallel.ForEachAsync` for async imaging operations |
|
||||
| Signing check fails on leaked artifacts | Run `generateAllFileComponents.ps1`; verify only `WinUI3Apps\\` paths in signing config |
|
||||
@@ -1,287 +0,0 @@
|
||||
# Imaging API Migration
|
||||
|
||||
Migrating from WPF (`System.Windows.Media.Imaging` / `PresentationCore.dll`) to WinRT (`Windows.Graphics.Imaging`). Based on the ImageResizer migration.
|
||||
|
||||
## Why This Migration Is Required
|
||||
|
||||
WinUI 3 apps deployed as self-contained do NOT include `PresentationCore.dll`. Any code using `System.Windows.Media.Imaging` will throw `FileNotFoundException` at runtime. ALL imaging code must use WinRT APIs.
|
||||
|
||||
| Purpose | Namespace |
|
||||
|---------|-----------|
|
||||
| UI display (`Image.Source`) | `Microsoft.UI.Xaml.Media.Imaging` |
|
||||
| Image processing (encode/decode/transform) | `Windows.Graphics.Imaging` |
|
||||
|
||||
## Architecture Change: Pipeline vs Declarative
|
||||
|
||||
The fundamental architecture differs:
|
||||
|
||||
**WPF**: In-memory pipeline of bitmap objects. Decode → transform → encode synchronously.
|
||||
```csharp
|
||||
var decoder = BitmapDecoder.Create(stream, ...);
|
||||
var transform = new TransformedBitmap(decoder.Frames[0], new ScaleTransform(...));
|
||||
var encoder = new JpegBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(transform, ...));
|
||||
encoder.Save(outputStream);
|
||||
```
|
||||
|
||||
**WinRT**: Declarative transform model. Configure transforms on the encoder, which handles pixel manipulation internally. All async.
|
||||
```csharp
|
||||
var decoder = await BitmapDecoder.CreateAsync(winrtStream);
|
||||
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
|
||||
encoder.BitmapTransform.ScaledWidth = newWidth;
|
||||
encoder.BitmapTransform.ScaledHeight = newHeight;
|
||||
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
|
||||
await encoder.FlushAsync();
|
||||
```
|
||||
|
||||
## Core Type Mapping
|
||||
|
||||
### Decoders
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `BitmapDecoder.Create(stream, options, cache)` | `BitmapDecoder.CreateAsync(stream)` | Async, auto-detects format |
|
||||
| `JpegBitmapDecoder` / `PngBitmapDecoder` / etc. | `BitmapDecoder.CreateAsync(stream)` | Single unified decoder |
|
||||
| `decoder.Frames[0]` | `await decoder.GetFrameAsync(0)` | Async frame access |
|
||||
| `decoder.Frames.Count` | `decoder.FrameCount` (uint) | `int` → `uint` |
|
||||
| `decoder.CodecInfo.ContainerFormat` | `decoder.DecoderInformation.CodecId` | Different property path |
|
||||
| `decoder.Frames[0].PixelWidth` (int) | `decoder.PixelWidth` (uint) | `int` → `uint` |
|
||||
| `WmpBitmapDecoder` | Not available | WMP/HDP not supported |
|
||||
|
||||
### Encoders
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `new JpegBitmapEncoder()` | `BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream)` | Async factory |
|
||||
| `new PngBitmapEncoder()` | `BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream)` | No interlace control |
|
||||
| `encoder.Frames.Add(frame)` | `encoder.SetSoftwareBitmap(bitmap)` | Different API |
|
||||
| `encoder.Save(stream)` | `await encoder.FlushAsync()` | Async |
|
||||
|
||||
### Encoder Properties (Strongly-Typed → BitmapPropertySet)
|
||||
|
||||
WPF had type-specific encoder subclasses. WinRT uses a generic property set:
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
case JpegBitmapEncoder jpeg: jpeg.QualityLevel = 85; // int 1-100
|
||||
case PngBitmapEncoder png: png.Interlace = PngInterlaceOption.On;
|
||||
case TiffBitmapEncoder tiff: tiff.Compression = TiffCompressOption.Lzw;
|
||||
|
||||
// WinRT — JPEG quality (float 0.0-1.0)
|
||||
await encoder.BitmapProperties.SetPropertiesAsync(new BitmapPropertySet
|
||||
{
|
||||
{ "ImageQuality", new BitmapTypedValue(0.85f, PropertyType.Single) }
|
||||
});
|
||||
|
||||
// WinRT — TIFF compression (via BitmapPropertySet at creation time)
|
||||
var props = new BitmapPropertySet
|
||||
{
|
||||
{ "TiffCompressionMethod", new BitmapTypedValue((byte)2, PropertyType.UInt8) }
|
||||
};
|
||||
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.TiffEncoderId, stream, props);
|
||||
```
|
||||
|
||||
**JPEG quality scale change**: WPF int `1-100` → WinRT float `0.0-1.0`. Divide by 100.
|
||||
|
||||
### Bitmap Types
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `BitmapSource` | `SoftwareBitmap` | Central pixel-data type |
|
||||
| `BitmapImage` | `BitmapImage` (in `Microsoft.UI.Xaml.Media.Imaging`) | UI display only |
|
||||
| `FormatConvertedBitmap` | `SoftwareBitmap.Convert()` | |
|
||||
| `TransformedBitmap` + `ScaleTransform` | `BitmapTransform` via encoder | Declarative |
|
||||
| `CroppedBitmap` | `BitmapTransform.Bounds` | |
|
||||
|
||||
### Metadata
|
||||
|
||||
| WPF | WinRT | Notes |
|
||||
|-----|-------|-------|
|
||||
| `BitmapMetadata` | `BitmapProperties` | Different API surface |
|
||||
| `BitmapMetadata.Clone()` | No equivalent | Cannot selectively clone |
|
||||
| Selective metadata removal | Not supported | All-or-nothing only |
|
||||
|
||||
**Two encoder creation strategies for metadata:**
|
||||
- `CreateForTranscodingAsync()` — preserves ALL metadata from source
|
||||
- `CreateAsync()` — creates fresh encoder with NO metadata
|
||||
|
||||
This eliminated ~258 lines of manual metadata manipulation code (`BitmapMetadataExtension.cs`) in ImageResizer.
|
||||
|
||||
### Interpolation Modes
|
||||
|
||||
| WPF `BitmapScalingMode` | WinRT `BitmapInterpolationMode` |
|
||||
|------------------------|-------------------------------|
|
||||
| `HighQuality` / `Fant` | `Fant` |
|
||||
| `Linear` | `Linear` |
|
||||
| `NearestNeighbor` | `NearestNeighbor` |
|
||||
| `Unspecified` / `LowQuality` | `Linear` |
|
||||
|
||||
## Stream Interop
|
||||
|
||||
WinRT imaging requires `IRandomAccessStream` instead of `System.IO.Stream`:
|
||||
|
||||
```csharp
|
||||
using var stream = File.OpenRead(path);
|
||||
var winrtStream = stream.AsRandomAccessStream(); // Extension method
|
||||
var decoder = await BitmapDecoder.CreateAsync(winrtStream);
|
||||
```
|
||||
|
||||
**Critical**: For transcode, seek the input stream back to 0 before creating the encoder:
|
||||
```csharp
|
||||
winrtStream.Seek(0);
|
||||
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
|
||||
```
|
||||
|
||||
## CodecHelper Pattern (from ImageResizer)
|
||||
|
||||
WPF stored container format GUIDs in `settings.json`. WinRT uses different codec IDs. Create a `CodecHelper` to bridge them:
|
||||
|
||||
```csharp
|
||||
internal static class CodecHelper
|
||||
{
|
||||
// Maps WPF container format GUIDs (stored in settings JSON) to WinRT encoder IDs
|
||||
private static readonly Dictionary<Guid, Guid> LegacyGuidToEncoderId = new()
|
||||
{
|
||||
[new Guid("19e4a5aa-5662-4fc5-a0c0-1758028e1057")] = BitmapEncoder.JpegEncoderId,
|
||||
[new Guid("1b7cfaf4-713f-473c-bbcd-6137425faeaf")] = BitmapEncoder.PngEncoderId,
|
||||
[new Guid("0af1d87e-fcfe-4188-bdeb-a7906471cbe3")] = BitmapEncoder.BmpEncoderId,
|
||||
[new Guid("163bcc30-e2e9-4f0b-961d-a3e9fdb788a3")] = BitmapEncoder.TiffEncoderId,
|
||||
[new Guid("1f8a5601-7d4d-4cbd-9c82-1bc8d4eeb9a5")] = BitmapEncoder.GifEncoderId,
|
||||
};
|
||||
|
||||
// Maps decoder IDs to corresponding encoder IDs
|
||||
private static readonly Dictionary<Guid, Guid> DecoderIdToEncoderId = new()
|
||||
{
|
||||
[BitmapDecoder.JpegDecoderId] = BitmapEncoder.JpegEncoderId,
|
||||
[BitmapDecoder.PngDecoderId] = BitmapEncoder.PngEncoderId,
|
||||
// ...
|
||||
};
|
||||
|
||||
public static Guid GetEncoderIdFromLegacyGuid(Guid legacyGuid)
|
||||
=> LegacyGuidToEncoderId.GetValueOrDefault(legacyGuid, Guid.Empty);
|
||||
|
||||
public static Guid GetEncoderIdForDecoder(BitmapDecoder decoder)
|
||||
=> DecoderIdToEncoderId.GetValueOrDefault(decoder.DecoderInformation.CodecId, Guid.Empty);
|
||||
}
|
||||
```
|
||||
|
||||
This preserves backward compatibility with existing `settings.json` files that contain WPF-era GUIDs.
|
||||
|
||||
## ImagingEnums Pattern (from ImageResizer)
|
||||
|
||||
WPF-specific enums (`PngInterlaceOption`, `TiffCompressOption`) from `System.Windows.Media.Imaging` are used in settings JSON. Create custom enums with identical integer values for backward-compatible deserialization:
|
||||
|
||||
```csharp
|
||||
// Replace System.Windows.Media.Imaging.PngInterlaceOption
|
||||
public enum PngInterlaceOption { Default = 0, On = 1, Off = 2 }
|
||||
|
||||
// Replace System.Windows.Media.Imaging.TiffCompressOption
|
||||
public enum TiffCompressOption { Default = 0, None = 1, Ccitt3 = 2, Ccitt4 = 3, Lzw = 4, Rle = 5, Zip = 6 }
|
||||
```
|
||||
|
||||
## Async Migration Patterns
|
||||
|
||||
### Method Signatures
|
||||
|
||||
All imaging operations become async:
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| `void Execute(file, settings)` | `async Task ExecuteAsync(file, settings)` |
|
||||
| `IEnumerable<Error> Process()` | `async Task<IEnumerable<Error>> ProcessAsync()` |
|
||||
|
||||
### Parallel Processing
|
||||
|
||||
```csharp
|
||||
// WPF (synchronous)
|
||||
Parallel.ForEach(Files, new ParallelOptions { MaxDegreeOfParallelism = ... },
|
||||
(file, state, i) => { Execute(file, settings); });
|
||||
|
||||
// WinRT (async)
|
||||
await Parallel.ForEachAsync(Files, new ParallelOptions { MaxDegreeOfParallelism = ... },
|
||||
async (file, ct) => { await ExecuteAsync(file, settings); });
|
||||
```
|
||||
|
||||
### CLI Async Bridge
|
||||
|
||||
CLI entry points must bridge async to sync:
|
||||
```csharp
|
||||
return RunSilentModeAsync(cliOptions).GetAwaiter().GetResult();
|
||||
```
|
||||
|
||||
### Task.Factory.StartNew → Task.Run
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
_ = Task.Factory.StartNew(StartExecutingWork, token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
|
||||
// WinUI 3
|
||||
_ = Task.Run(() => StartExecutingWorkAsync());
|
||||
```
|
||||
|
||||
## SoftwareBitmap as Interface Type
|
||||
|
||||
When modules expose imaging interfaces (e.g., AI super-resolution), change parameter/return types:
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
BitmapSource ApplySuperResolution(BitmapSource source, int scale, string filePath);
|
||||
|
||||
// WinRT
|
||||
SoftwareBitmap ApplySuperResolution(SoftwareBitmap source, int scale, string filePath);
|
||||
```
|
||||
|
||||
This eliminates manual `BitmapSource ↔ SoftwareBitmap` conversion code (unsafe `IMemoryBufferByteAccess` COM interop).
|
||||
|
||||
## MultiFrame Image Handling
|
||||
|
||||
```csharp
|
||||
// WinRT multi-frame encode (e.g., multi-page TIFF, animated GIF)
|
||||
for (uint i = 0; i < decoder.FrameCount; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
await encoder.GoToNextFrameAsync();
|
||||
|
||||
var frame = await decoder.GetFrameAsync(i);
|
||||
var bitmap = await frame.GetSoftwareBitmapAsync(
|
||||
frame.BitmapPixelFormat,
|
||||
BitmapAlphaMode.Premultiplied,
|
||||
transform,
|
||||
ExifOrientationMode.IgnoreExifOrientation,
|
||||
ColorManagementMode.DoNotColorManage);
|
||||
encoder.SetSoftwareBitmap(bitmap);
|
||||
}
|
||||
await encoder.FlushAsync();
|
||||
```
|
||||
|
||||
## int → uint for Pixel Dimensions
|
||||
|
||||
WinRT uses `uint` for all pixel dimensions. This affects:
|
||||
- `decoder.PixelWidth` / `decoder.PixelHeight` — `uint`
|
||||
- `BitmapTransform.ScaledWidth` / `ScaledHeight` — `uint`
|
||||
- `SoftwareBitmap` constructor — `uint` parameters
|
||||
- Test assertions: `Assert.AreEqual(96, ...)` → `Assert.AreEqual(96u, ...)`
|
||||
|
||||
## Display SoftwareBitmap in UI
|
||||
|
||||
```csharp
|
||||
var source = new SoftwareBitmapSource();
|
||||
// Must convert to Bgra8/Premultiplied for display
|
||||
if (bitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
|
||||
bitmap.BitmapAlphaMode != BitmapAlphaMode.Premultiplied)
|
||||
{
|
||||
bitmap = SoftwareBitmap.Convert(bitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
||||
}
|
||||
await source.SetBitmapAsync(bitmap);
|
||||
myImage.Source = source;
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
| Feature | WPF | WinRT | Impact |
|
||||
|---------|-----|-------|--------|
|
||||
| PNG interlace | `PngBitmapEncoder.Interlace` | Not available | Always non-interlaced |
|
||||
| Metadata stripping | Selective via `BitmapMetadata.Clone()` | All-or-nothing | Orientation EXIF also removed |
|
||||
| Pixel formats | Many (`Pbgra32`, `Bgr24`, `Indexed8`, ...) | Primarily `Bgra8`, `Rgba8`, `Gray8/16` | Convert to `Bgra8` |
|
||||
| WMP/HDP format | `WmpBitmapDecoder` | Not available | Not supported |
|
||||
| Pixel differences | WPF scaler | `BitmapInterpolationMode.Fant` | Not bit-identical |
|
||||
@@ -1,226 +0,0 @@
|
||||
# Namespace and API Mapping Reference
|
||||
|
||||
Complete reference for mapping WPF types to WinUI 3 equivalents, based on the ImageResizer migration.
|
||||
|
||||
## Root Namespace Mapping
|
||||
|
||||
| WPF Namespace | WinUI 3 Namespace |
|
||||
|---------------|-------------------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` |
|
||||
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` |
|
||||
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
|
||||
## Core Type Mapping
|
||||
|
||||
| WPF Type | WinUI 3 Type |
|
||||
|----------|-------------|
|
||||
| `System.Windows.Application` | `Microsoft.UI.Xaml.Application` |
|
||||
| `System.Windows.Window` | `Microsoft.UI.Xaml.Window` (NOT a DependencyObject) |
|
||||
| `System.Windows.DependencyObject` | `Microsoft.UI.Xaml.DependencyObject` |
|
||||
| `System.Windows.DependencyProperty` | `Microsoft.UI.Xaml.DependencyProperty` |
|
||||
| `System.Windows.FrameworkElement` | `Microsoft.UI.Xaml.FrameworkElement` |
|
||||
| `System.Windows.UIElement` | `Microsoft.UI.Xaml.UIElement` |
|
||||
| `System.Windows.Visibility` | `Microsoft.UI.Xaml.Visibility` |
|
||||
| `System.Windows.Thickness` | `Microsoft.UI.Xaml.Thickness` |
|
||||
| `System.Windows.CornerRadius` | `Microsoft.UI.Xaml.CornerRadius` |
|
||||
| `System.Windows.Media.Color` | `Windows.UI.Color` (note: `Windows.UI`, not `Microsoft.UI`) |
|
||||
| `System.Windows.Media.Colors` | `Microsoft.UI.Colors` |
|
||||
|
||||
## Controls Mapping
|
||||
|
||||
### Direct Mapping (namespace-only change)
|
||||
|
||||
These controls exist in both frameworks with the same name — change `System.Windows.Controls` to `Microsoft.UI.Xaml.Controls`:
|
||||
|
||||
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
|
||||
|
||||
### Controls With Different Names or Behavior
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `MessageBox` | `ContentDialog` | Must set `XamlRoot` before `ShowAsync()` |
|
||||
| `ContextMenu` | `MenuFlyout` | Different API surface |
|
||||
| `TabControl` | `TabView` | Different API |
|
||||
| `Menu` | `MenuBar` | Different API |
|
||||
| `StatusBar` | Custom `StackPanel` layout | No built-in equivalent |
|
||||
| `AccessText` | Not available | Use `AccessKey` property on target control |
|
||||
|
||||
### WPF-UI (Lepo) to Native WinUI 3
|
||||
|
||||
ImageResizer used the `WPF-UI` library (Lepo) for Fluent styling. These must be replaced with native WinUI 3 equivalents:
|
||||
|
||||
| WPF-UI (Lepo) | WinUI 3 Native | Notes |
|
||||
|----------------|---------------|-------|
|
||||
| `<ui:FluentWindow>` | `<Window>` | Native window + `ExtendsContentIntoTitleBar` |
|
||||
| `<ui:Button>` | `<Button>` | Native button |
|
||||
| `<ui:NumberBox>` | `<NumberBox>` | Built into WinUI 3 |
|
||||
| `<ui:ProgressRing>` | `<ProgressRing>` | Built into WinUI 3 |
|
||||
| `<ui:SymbolIcon>` | `<SymbolIcon>` or `<FontIcon>` | Built into WinUI 3 |
|
||||
| `<ui:InfoBar>` | `<InfoBar>` | Built into WinUI 3 |
|
||||
| `<ui:TitleBar>` | Custom title bar via `SetTitleBar()` | Use `ExtendsContentIntoTitleBar` |
|
||||
| `<ui:ThemesDictionary>` | `<XamlControlsResources>` | In merged dictionaries |
|
||||
| `<ui:ControlsDictionary>` | Remove | Not needed — WinUI 3 has its own control styles |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` | Named style keys |
|
||||
|
||||
## Input Event Mapping
|
||||
|
||||
| WPF Event | WinUI 3 Event | Notes |
|
||||
|-----------|--------------|-------|
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Check `IsLeftButtonPressed` on args |
|
||||
| `MouseLeftButtonUp` | `PointerReleased` | Check pointer properties |
|
||||
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` with right button check |
|
||||
| `MouseMove` | `PointerMoved` | Uses `PointerRoutedEventArgs` |
|
||||
| `MouseWheel` | `PointerWheelChanged` | Different event args |
|
||||
| `MouseEnter` | `PointerEntered` | |
|
||||
| `MouseLeave` | `PointerExited` | |
|
||||
| `MouseDoubleClick` | `DoubleTapped` | Different event args |
|
||||
| `KeyDown` | `KeyDown` | Same name, args type: `KeyRoutedEventArgs` |
|
||||
| `PreviewKeyDown` | No direct equivalent | Use `KeyDown` with handled pattern |
|
||||
|
||||
## IValueConverter Signature Change
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Convert(object value, Type targetType, object parameter, CultureInfo culture)` | `Convert(object value, Type targetType, object parameter, string language)` |
|
||||
| `ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)` | `ConvertBack(object value, Type targetType, object parameter, string language)` |
|
||||
|
||||
Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All converter classes must be updated.
|
||||
|
||||
## Types That Moved to Different Hierarchies
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `System.Windows.Threading.Dispatcher` | `Microsoft.UI.Dispatching.DispatcherQueue` | Completely different API |
|
||||
| `System.Windows.Threading.DispatcherPriority` | `Microsoft.UI.Dispatching.DispatcherQueuePriority` | Only 3 levels: High/Normal/Low |
|
||||
| `System.Windows.Interop.HwndSource` | `WinRT.Interop.WindowNative` | For HWND interop |
|
||||
| `System.Windows.Interop.WindowInteropHelper` | `WinRT.Interop.WindowNative.GetWindowHandle()` | |
|
||||
| `System.Windows.SystemColors` | Resource keys via `ThemeResource` | No direct static class |
|
||||
| `System.Windows.SystemParameters` | Win32 API or `DisplayInformation` | No direct equivalent |
|
||||
|
||||
## NuGet Package Migration
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| Built into .NET (no NuGet needed) | `Microsoft.WindowsAppSDK` | Required |
|
||||
| `PresentationCore` / `PresentationFramework` | `Microsoft.WinUI` (transitive) | |
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` | |
|
||||
| `WPF-UI` (Lepo) | **Remove** — use native WinUI 3 controls | |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) | |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` | |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` | Required |
|
||||
| (none) | `WinUIEx` | Optional, window helpers |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Extensions` | Optional |
|
||||
| (none) | `Microsoft.Web.WebView2` | If using WebView |
|
||||
|
||||
## Project File Changes
|
||||
|
||||
### WPF .csproj
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationManifest>ImageResizerUI.dev.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Resources\ImageResizer.ico</ApplicationIcon>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
### WinUI 3 .csproj
|
||||
|
||||
```xml
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<SelfContained>true</SelfContained>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Assets\ImageResizer\ImageResizer.ico</ApplicationIcon>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN,TRACE</DefineConstants>
|
||||
<ProjectPriFileName>PowerToys.ModuleName.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
```
|
||||
|
||||
Key changes:
|
||||
- `UseWPF` → `UseWinUI`
|
||||
- TFM: `net8.0-windows` → `net8.0-windows10.0.19041.0`
|
||||
- Add `WindowsPackageType=None` for unpackaged desktop apps
|
||||
- Add `SelfContained=true` + `WindowsAppSDKSelfContained=true`
|
||||
- Add `DISABLE_XAML_GENERATED_MAIN` if using custom `Program.cs` entry point
|
||||
- Set `ProjectPriFileName` to match your module's assembly name
|
||||
- Move icon from `Resources/` to `Assets/<Module>/`
|
||||
|
||||
### XAML ApplicationDefinition Setup
|
||||
|
||||
WinUI 3 requires explicit `ApplicationDefinition` declaration:
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<Page Remove="ImageResizerXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="ImageResizerXAML\App.xaml" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### CsWinRT Interop (for GPO and native references)
|
||||
|
||||
If the module references native C++ projects (like `GPOWrapper`):
|
||||
|
||||
```xml
|
||||
<PropertyGroup>
|
||||
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
</PropertyGroup>
|
||||
```
|
||||
|
||||
Change `GPOWrapperProjection.csproj` reference to direct `GPOWrapper.vcxproj` reference.
|
||||
|
||||
### InternalsVisibleTo Migration
|
||||
|
||||
Move from code file to `.csproj`:
|
||||
|
||||
```csharp
|
||||
// DELETE: Properties/InternalsVisibleTo.cs
|
||||
// [assembly: InternalsVisibleTo("ImageResizer.Test")]
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- ADD to .csproj: -->
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ImageResizer.Test" />
|
||||
</ItemGroup>
|
||||
```
|
||||
|
||||
### Items to Remove from .csproj
|
||||
|
||||
```xml
|
||||
<!-- DELETE: WPF resource embedding -->
|
||||
<EmbeddedResource Update="Properties\Resources.resx">...</EmbeddedResource>
|
||||
<Resource Include="Resources\ImageResizer.ico" />
|
||||
<Compile Update="Properties\Resources.Designer.cs">...</Compile>
|
||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" /> <!-- from CLI project -->
|
||||
```
|
||||
@@ -1,516 +0,0 @@
|
||||
# PowerToys-Specific Migration Patterns
|
||||
|
||||
Patterns and conventions specific to the PowerToys codebase, based on the ImageResizer migration.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Before (WPF Module)
|
||||
|
||||
```
|
||||
src/modules/<module>/
|
||||
├── <Module>UI/
|
||||
│ ├── <Module>UI.csproj # OutputType=WinExe, UseWPF=true
|
||||
│ ├── App.xaml / App.xaml.cs
|
||||
│ ├── MainWindow.xaml / .cs
|
||||
│ ├── Views/
|
||||
│ ├── ViewModels/
|
||||
│ ├── Helpers/
|
||||
│ │ ├── Observable.cs # Custom INotifyPropertyChanged
|
||||
│ │ └── RelayCommand.cs # Custom ICommand
|
||||
│ ├── Properties/
|
||||
│ │ ├── Resources.resx # WPF resource strings
|
||||
│ │ ├── Resources.Designer.cs
|
||||
│ │ └── InternalsVisibleTo.cs
|
||||
│ └── Telemetry/
|
||||
├── <Module>CLI/
|
||||
│ └── <Module>CLI.csproj # OutputType=Exe
|
||||
└── tests/
|
||||
```
|
||||
|
||||
### After (WinUI 3 Module)
|
||||
|
||||
```
|
||||
src/modules/<module>/
|
||||
├── <Module>UI/
|
||||
│ ├── <Module>UI.csproj # OutputType=WinExe, UseWinUI=true
|
||||
│ ├── Program.cs # Custom entry point (DISABLE_XAML_GENERATED_MAIN)
|
||||
│ ├── app.manifest # Single manifest file
|
||||
│ ├── ImageResizerXAML/
|
||||
│ │ ├── App.xaml / App.xaml.cs # WinUI 3 App class
|
||||
│ │ ├── MainWindow.xaml / .cs
|
||||
│ │ └── Views/
|
||||
│ ├── Converters/ # WinUI 3 IValueConverter (string language)
|
||||
│ ├── ViewModels/
|
||||
│ ├── Helpers/
|
||||
│ │ └── ResourceLoaderInstance.cs # Static ResourceLoader accessor
|
||||
│ ├── Utilities/
|
||||
│ │ └── CodecHelper.cs # WPF→WinRT codec ID mapping (if imaging)
|
||||
│ ├── Models/
|
||||
│ │ └── ImagingEnums.cs # Custom enums replacing WPF imaging enums
|
||||
│ ├── Strings/
|
||||
│ │ └── en-us/
|
||||
│ │ └── Resources.resw # WinUI 3 resource strings
|
||||
│ └── Assets/
|
||||
│ └── <Module>/
|
||||
│ └── <Module>.ico # Moved from Resources/
|
||||
├── <Module>Common/ # NEW: shared library for CLI
|
||||
│ └── <Module>Common.csproj # OutputType=Library
|
||||
├── <Module>CLI/
|
||||
│ └── <Module>CLI.csproj # References Common, NOT UI
|
||||
└── tests/
|
||||
```
|
||||
|
||||
### Critical: CLI Dependency Pattern
|
||||
|
||||
**Do NOT** create `ProjectReference` from Exe to WinExe. This causes phantom build artifacts (`.exe`, `.deps.json`, `.runtimeconfig.json`) in the root output directory.
|
||||
|
||||
```
|
||||
WRONG: ImageResizerCLI (Exe) → ImageResizerUI (WinExe) ← phantom artifacts
|
||||
CORRECT: ImageResizerCLI (Exe) → ImageResizerCommon (Library)
|
||||
ImageResizerUI (WinExe) → ImageResizerCommon (Library)
|
||||
```
|
||||
|
||||
Follow the `FancyZonesCLI` → `FancyZonesEditorCommon` pattern.
|
||||
|
||||
### Files to Delete
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `Properties/Resources.resx` | Replaced by `Strings/en-us/Resources.resw` |
|
||||
| `Properties/Resources.Designer.cs` | Auto-generated; no longer needed |
|
||||
| `Properties/InternalsVisibleTo.cs` | Moved to `.csproj` `<InternalsVisibleTo>` |
|
||||
| `Helpers/Observable.cs` | Replaced by `CommunityToolkit.Mvvm.ObservableObject` |
|
||||
| `Helpers/RelayCommand.cs` | Replaced by `CommunityToolkit.Mvvm.Input` |
|
||||
| `Resources/*.ico` / `Resources/*.png` | Moved to `Assets/<Module>/` |
|
||||
| WPF `.dev.manifest` / `.prod.manifest` | Replaced by single `app.manifest` |
|
||||
| WPF-specific converters | Replaced by WinUI 3 converters with `string language` |
|
||||
|
||||
---
|
||||
|
||||
## MVVM Migration: Custom → CommunityToolkit.Mvvm Source Generators
|
||||
|
||||
### Observable Base Class → ObservableObject + [ObservableProperty]
|
||||
|
||||
**Before (custom Observable):**
|
||||
```csharp
|
||||
public class ResizeSize : Observable
|
||||
{
|
||||
private int _id;
|
||||
public int Id { get => _id; set => Set(ref _id, value); }
|
||||
|
||||
private ResizeFit _fit;
|
||||
public ResizeFit Fit
|
||||
{
|
||||
get => _fit;
|
||||
set
|
||||
{
|
||||
Set(ref _fit, value);
|
||||
UpdateShowHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _showHeight = true;
|
||||
public bool ShowHeight { get => _showHeight; set => Set(ref _showHeight, value); }
|
||||
private void UpdateShowHeight() { ShowHeight = Fit == ResizeFit.Stretch || Unit != ResizeUnit.Percent; }
|
||||
}
|
||||
```
|
||||
|
||||
**After (CommunityToolkit.Mvvm source generators):**
|
||||
```csharp
|
||||
public partial class ResizeSize : ObservableObject // MUST be partial
|
||||
{
|
||||
[ObservableProperty]
|
||||
[JsonPropertyName("Id")]
|
||||
private int _id;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShowHeight))] // Replaces manual UpdateShowHeight()
|
||||
private ResizeFit _fit;
|
||||
|
||||
// Computed property — no backing field, no manual update method
|
||||
public bool ShowHeight => Fit == ResizeFit.Stretch || Unit != ResizeUnit.Percent;
|
||||
}
|
||||
```
|
||||
|
||||
Key changes:
|
||||
- Class must be `partial` for source generators
|
||||
- `Observable` → `ObservableObject` (from CommunityToolkit.Mvvm)
|
||||
- Manual `Set(ref _field, value)` → `[ObservableProperty]` attribute
|
||||
- `PropertyChanged` dependencies → `[NotifyPropertyChangedFor(nameof(...))]`
|
||||
- Computed properties with manual `UpdateXxx()` → direct expression body
|
||||
|
||||
### Custom Name Setter with Transform
|
||||
|
||||
For properties that transform the value before storing:
|
||||
|
||||
```csharp
|
||||
// Cannot use [ObservableProperty] because of value transformation
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, ReplaceTokens(value)); // SetProperty from ObservableObject
|
||||
}
|
||||
```
|
||||
|
||||
### RelayCommand → [RelayCommand] Source Generator
|
||||
|
||||
```csharp
|
||||
// DELETE: Helpers/RelayCommand.cs (custom ICommand)
|
||||
|
||||
// Before
|
||||
public ICommand ResizeCommand { get; } = new RelayCommand(Execute);
|
||||
|
||||
// After
|
||||
[RelayCommand]
|
||||
private void Resize() { /* ... */ }
|
||||
// Source generator creates ResizeCommand property automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resource String Migration (.resx → .resw)
|
||||
|
||||
### ResourceLoaderInstance Helper
|
||||
|
||||
```csharp
|
||||
internal static class ResourceLoaderInstance
|
||||
{
|
||||
internal static ResourceLoader ResourceLoader { get; private set; }
|
||||
|
||||
static ResourceLoaderInstance()
|
||||
{
|
||||
ResourceLoader = new ResourceLoader("PowerToys.ImageResizer.pri");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Use the single-argument `ResourceLoader` constructor. The two-argument version (`ResourceLoader("file.pri", "path/Resources")`) may fail if the resource map path doesn't match the actual PRI structure.
|
||||
|
||||
### Usage
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
using ImageResizer.Properties;
|
||||
string text = Resources.MyStringKey;
|
||||
|
||||
// WinUI 3
|
||||
string text = ResourceLoaderInstance.ResourceLoader.GetString("MyStringKey");
|
||||
```
|
||||
|
||||
### Lazy Initialization for Resource-Dependent Statics
|
||||
|
||||
`ResourceLoader` is not available at class-load time in all contexts (CLI mode, test harness). Use lazy initialization:
|
||||
|
||||
**Before (crashes at class load):**
|
||||
```csharp
|
||||
private static readonly CompositeFormat _format =
|
||||
CompositeFormat.Parse(Resources.Error_Format);
|
||||
|
||||
private static readonly Dictionary<string, string> _tokens = new()
|
||||
{
|
||||
["$small$"] = Resources.Small,
|
||||
["$medium$"] = Resources.Medium,
|
||||
};
|
||||
```
|
||||
|
||||
**After (lazy, safe):**
|
||||
```csharp
|
||||
private static CompositeFormat _format;
|
||||
private static CompositeFormat Format => _format ??=
|
||||
CompositeFormat.Parse(ResourceLoaderInstance.ResourceLoader.GetString("Error_Format"));
|
||||
|
||||
private static readonly Lazy<Dictionary<string, string>> _tokens = new(() =>
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["$small$"] = ResourceLoaderInstance.ResourceLoader.GetString("Small"),
|
||||
["$medium$"] = ResourceLoaderInstance.ResourceLoader.GetString("Medium"),
|
||||
});
|
||||
// Usage: _tokens.Value.TryGetValue(...)
|
||||
```
|
||||
|
||||
### XAML: x:Static → x:Uid
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<Button Content="{x:Static p:Resources.Cancel}" />
|
||||
<!-- WinUI 3 -->
|
||||
<Button x:Uid="Cancel" />
|
||||
```
|
||||
|
||||
In `.resw`, use property-suffixed keys: `Cancel.Content`, `Header.Text`, etc.
|
||||
|
||||
---
|
||||
|
||||
## CLI Options Migration
|
||||
|
||||
`System.CommandLine.Option<T>` constructor signature changed:
|
||||
|
||||
```csharp
|
||||
// WPF era — string[] aliases
|
||||
public DestinationOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Destination)
|
||||
|
||||
// WinUI 3 — single string name
|
||||
public DestinationOption()
|
||||
: base(_aliases[0], ResourceLoaderInstance.ResourceLoader.GetString("CLI_Option_Destination"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installer Updates
|
||||
|
||||
### WiX Changes
|
||||
|
||||
#### 1. Remove Satellite Assembly References
|
||||
|
||||
Remove from `installer/PowerToysSetupVNext/Resources.wxs`:
|
||||
- `<Component>` entries for `<Module>.resources.dll`
|
||||
- `<RemoveFolder>` entries for locale directories
|
||||
- Module from `WinUI3AppsInstallFolder` `ParentDirectory` loop
|
||||
|
||||
#### 2. Update File Component Generation
|
||||
|
||||
Run `generateAllFileComponents.ps1` after migration. For Exe→WinExe dependency issues, add cleanup logic:
|
||||
|
||||
```powershell
|
||||
# Strip phantom ImageResizer files from BaseApplications.wxs
|
||||
$content = $content -replace 'PowerToys\.ImageResizer\.exe', ''
|
||||
$content = $content -replace 'PowerToys\.ImageResizer\.deps\.json', ''
|
||||
$content = $content -replace 'PowerToys\.ImageResizer\.runtimeconfig\.json', ''
|
||||
```
|
||||
|
||||
#### 3. Output Directory
|
||||
|
||||
WinUI 3 modules output to `WinUI3Apps/`:
|
||||
```xml
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
|
||||
```
|
||||
|
||||
### ESRP Signing
|
||||
|
||||
Update `.pipelines/ESRPSigning_core.json` — all module binaries must use `WinUI3Apps\\` paths:
|
||||
|
||||
```json
|
||||
{
|
||||
"FileList": [
|
||||
"WinUI3Apps\\PowerToys.ImageResizer.exe",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerExt.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerContextMenu.dll"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Pipeline Fixes
|
||||
|
||||
### $(SolutionDir) → $(MSBuildThisFileDirectory)
|
||||
|
||||
`$(SolutionDir)` is empty when building individual projects outside the solution. Replace with relative paths from the project file:
|
||||
|
||||
```xml
|
||||
<!-- Before (breaks on standalone project build) -->
|
||||
<Exec Command="powershell $(SolutionDir)tools\build\convert-resx-to-rc.ps1" />
|
||||
|
||||
<!-- After (works always) -->
|
||||
<Exec Command="powershell $(MSBuildThisFileDirectory)..\..\..\..\tools\build\convert-resx-to-rc.ps1" />
|
||||
```
|
||||
|
||||
### MSIX Packaging: PreBuild → PostBuild
|
||||
|
||||
MSIX packaging must happen AFTER the build (artifacts not ready at PreBuild):
|
||||
|
||||
```xml
|
||||
<!-- Before -->
|
||||
<PreBuildEvent>MakeAppx.exe pack /d . /p "$(OutDir)Package.msix" /o</PreBuildEvent>
|
||||
|
||||
<!-- After -->
|
||||
<PostBuildEvent>
|
||||
if exist "$(OutDir)Package.msix" del "$(OutDir)Package.msix"
|
||||
MakeAppx.exe pack /d "$(MSBuildThisFileDirectory)." /p "$(OutDir)Package.msix" /o
|
||||
</PostBuildEvent>
|
||||
```
|
||||
|
||||
### RC File Icon Path Escaping
|
||||
|
||||
Windows Resource Compiler requires double-backslash paths:
|
||||
|
||||
```c
|
||||
// Before (breaks)
|
||||
IDI_ICON1 ICON "..\\ui\Assets\ImageResizer\ImageResizer.ico"
|
||||
// After
|
||||
IDI_ICON1 ICON "..\\ui\\Assets\\ImageResizer\\ImageResizer.ico"
|
||||
```
|
||||
|
||||
### BOM/Encoding Normalization
|
||||
|
||||
Migration may strip UTF-8 BOM from C# files (`// Copyright` → `// Copyright`). This is cosmetic and safe, but be aware it will show as changes in diff.
|
||||
|
||||
---
|
||||
|
||||
## Test Adaptation
|
||||
|
||||
### Tests Requiring WPF Runtime
|
||||
|
||||
If tests still need WPF types (e.g., comparing old vs new output), temporarily add:
|
||||
```xml
|
||||
<UseWPF>true</UseWPF>
|
||||
```
|
||||
Remove this after fully migrating all test code to WinRT APIs.
|
||||
|
||||
### Tests Using ResourceLoader
|
||||
|
||||
Unit tests cannot easily initialize WinUI 3 `ResourceLoader`. Options:
|
||||
- Hardcode expected strings in tests: `"Value must be between '{0}' and '{1}'."`
|
||||
- Delete tests that only verify resource string lookup
|
||||
- Avoid creating `App` instances in test harness (WinUI App cannot be instantiated in tests)
|
||||
|
||||
### Async Test Methods
|
||||
|
||||
All imaging tests become async:
|
||||
```csharp
|
||||
// Before
|
||||
[TestMethod]
|
||||
public void ResizesImage() { ... }
|
||||
|
||||
// After
|
||||
[TestMethod]
|
||||
public async Task ResizesImageAsync() { ... }
|
||||
```
|
||||
|
||||
### uint Assertions
|
||||
|
||||
```csharp
|
||||
// Before
|
||||
Assert.AreEqual(96, image.Frames[0].PixelWidth);
|
||||
// After
|
||||
Assert.AreEqual(96u, decoder.PixelWidth);
|
||||
```
|
||||
|
||||
### Pixel Data Access in Tests
|
||||
|
||||
```csharp
|
||||
// Before (WPF)
|
||||
public static Color GetFirstPixel(this BitmapSource source)
|
||||
{
|
||||
var pixel = new byte[4];
|
||||
new FormatConvertedBitmap(
|
||||
new CroppedBitmap(source, new Int32Rect(0, 0, 1, 1)),
|
||||
PixelFormats.Bgra32, null, 0).CopyPixels(pixel, 4, 0);
|
||||
return Color.FromArgb(pixel[3], pixel[2], pixel[1], pixel[0]);
|
||||
}
|
||||
|
||||
// After (WinRT)
|
||||
public static async Task<(byte R, byte G, byte B, byte A)> GetFirstPixelAsync(
|
||||
this BitmapDecoder decoder)
|
||||
{
|
||||
using var bitmap = await decoder.GetSoftwareBitmapAsync(
|
||||
BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
||||
var buffer = new Windows.Storage.Streams.Buffer(
|
||||
(uint)(bitmap.PixelWidth * bitmap.PixelHeight * 4));
|
||||
bitmap.CopyToBuffer(buffer);
|
||||
using var reader = DataReader.FromBuffer(buffer);
|
||||
byte b = reader.ReadByte(), g = reader.ReadByte(),
|
||||
r = reader.ReadByte(), a = reader.ReadByte();
|
||||
return (r, g, b, a);
|
||||
}
|
||||
```
|
||||
|
||||
### Metadata Assertions
|
||||
|
||||
```csharp
|
||||
// Before
|
||||
Assert.AreEqual("Test", ((BitmapMetadata)image.Frames[0].Metadata).Comment);
|
||||
|
||||
// After
|
||||
var props = await decoder.BitmapProperties.GetPropertiesAsync(
|
||||
new[] { "System.Photo.DateTaken" });
|
||||
Assert.IsTrue(props.ContainsKey("System.Photo.DateTaken"),
|
||||
"Metadata should be preserved during transcode");
|
||||
```
|
||||
|
||||
### AllowUnsafeBlocks for SoftwareBitmap Tests
|
||||
|
||||
If tests access pixel data via `IMemoryBufferByteAccess`, add:
|
||||
```xml
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings JSON Backward Compatibility
|
||||
|
||||
- Settings are stored in `%LOCALAPPDATA%\Microsoft\PowerToys\<ModuleName>\`
|
||||
- Schema must remain backward-compatible across upgrades
|
||||
- Add new fields with defaults; never remove or rename existing fields
|
||||
- Create custom enums matching WPF enum integer values for deserialization (e.g., `ImagingEnums.cs`)
|
||||
- See: `src/settings-ui/Settings.UI.Library/`
|
||||
|
||||
## IPC Contract
|
||||
|
||||
If the module communicates with the runner or settings UI:
|
||||
1. Update BOTH sides of the IPC contract
|
||||
2. Test settings changes are received by the module
|
||||
3. Test module state changes are reflected in settings UI
|
||||
4. Reference: `doc/devdocs/core/settings/runner-ipc.md`
|
||||
|
||||
---
|
||||
|
||||
## Checklist for PowerToys Module Migration
|
||||
|
||||
### Project & Dependencies
|
||||
- [ ] Update `.csproj`: `UseWPF` → `UseWinUI`, TFM → `net8.0-windows10.0.19041.0`
|
||||
- [ ] Add `WindowsPackageType=None`, `SelfContained=true`, `WindowsAppSDKSelfContained=true`
|
||||
- [ ] Add `DISABLE_XAML_GENERATED_MAIN` if using custom `Program.cs`
|
||||
- [ ] Replace NuGet packages (WPF-UI → remove, add WindowsAppSDK, etc.)
|
||||
- [ ] Update project references (GPOWrapperProjection → GPOWrapper + CsWinRT)
|
||||
- [ ] Move `InternalsVisibleTo` from code to `.csproj`
|
||||
- [ ] Extract CLI shared logic to Library project (avoid Exe→WinExe dependency)
|
||||
|
||||
### MVVM & Resources
|
||||
- [ ] Replace custom `Observable`/`RelayCommand` with CommunityToolkit.Mvvm source generators
|
||||
- [ ] Migrate `.resx` → `.resw` (`Properties/Resources.resx` → `Strings/en-us/Resources.resw`)
|
||||
- [ ] Create `ResourceLoaderInstance` helper
|
||||
- [ ] Wrap resource-dependent statics in `Lazy<T>` or null-coalescing properties
|
||||
- [ ] Delete `Properties/Resources.Designer.cs`, `Observable.cs`, `RelayCommand.cs`
|
||||
|
||||
### XAML
|
||||
- [ ] Replace `clr-namespace:` → `using:` in all xmlns declarations
|
||||
- [ ] Remove WPF-UI (Lepo) xmlns and controls — use native WinUI 3
|
||||
- [ ] Replace `{x:Static p:Resources.Key}` → `x:Uid` with `.resw` keys
|
||||
- [ ] Replace `{DynamicResource}` → `{ThemeResource}`
|
||||
- [ ] Replace `DataType="{x:Type ...}"` → `x:DataType="..."`
|
||||
- [ ] Replace `<Style.Triggers>` → `VisualStateManager`
|
||||
- [ ] Add `<XamlControlsResources/>` to `App.xaml` merged dictionaries
|
||||
- [ ] Move `Window.Resources` to root container's `Resources`
|
||||
- [ ] Run XamlStyler: `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
|
||||
### Code-Behind & APIs
|
||||
- [ ] Replace all `System.Windows.*` namespaces with `Microsoft.UI.Xaml.*`
|
||||
- [ ] Replace `Dispatcher` with `DispatcherQueue`
|
||||
- [ ] Store `DispatcherQueue` reference explicitly (no `Application.Current.Dispatcher`)
|
||||
- [ ] Implement `SizeToContent()` via AppWindow if needed
|
||||
- [ ] Update `ContentDialog` calls to set `XamlRoot`
|
||||
- [ ] Update `FilePicker` calls with HWND initialization
|
||||
- [ ] Migrate imaging code to `Windows.Graphics.Imaging` (async, `SoftwareBitmap`)
|
||||
- [ ] Create `CodecHelper` for legacy GUID → WinRT codec ID mapping (if imaging)
|
||||
- [ ] Create custom imaging enums for JSON backward compatibility (if imaging)
|
||||
- [ ] Update all `IValueConverter` signatures (`CultureInfo` → `string`)
|
||||
|
||||
### Build & Installer
|
||||
- [ ] Update WiX installer: remove satellite assembly refs from `Resources.wxs`
|
||||
- [ ] Run `generateAllFileComponents.ps1`; handle phantom artifacts
|
||||
- [ ] Update ESRP signing paths to `WinUI3Apps\\`
|
||||
- [ ] Fix `$(SolutionDir)` → `$(MSBuildThisFileDirectory)` in build events
|
||||
- [ ] Move MSIX packaging from PreBuild to PostBuild
|
||||
- [ ] Fix RC file path escaping (double-backslash)
|
||||
- [ ] Verify output dir is `WinUI3Apps/`
|
||||
|
||||
### Testing & Validation
|
||||
- [ ] Update test project: async methods, `uint` assertions
|
||||
- [ ] Handle ResourceLoader unavailability in tests (hardcode strings or skip)
|
||||
- [ ] Build clean: `cd` to project folder, `tools/build/build.cmd`, exit code 0
|
||||
- [ ] Run tests for affected module
|
||||
- [ ] Verify settings JSON backward compatibility
|
||||
- [ ] Test IPC contracts (runner ↔ settings UI)
|
||||
@@ -1,314 +0,0 @@
|
||||
# Threading and Window Management Migration
|
||||
|
||||
Based on patterns from the ImageResizer migration.
|
||||
|
||||
## Dispatcher → DispatcherQueue
|
||||
|
||||
### API Mapping
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Dispatcher.Invoke(Action)` | `DispatcherQueue.TryEnqueue(Action)` |
|
||||
| `Dispatcher.BeginInvoke(Action)` | `DispatcherQueue.TryEnqueue(Action)` |
|
||||
| `Dispatcher.Invoke(DispatcherPriority, Action)` | `DispatcherQueue.TryEnqueue(DispatcherQueuePriority, Action)` |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` |
|
||||
| `Dispatcher.VerifyAccess()` | Check `DispatcherQueue.HasThreadAccess` (no exception-throwing method) |
|
||||
|
||||
### Priority Mapping
|
||||
|
||||
WinUI 3 has only 3 levels: `High`, `Normal`, `Low`.
|
||||
|
||||
| WPF `DispatcherPriority` | WinUI 3 `DispatcherQueuePriority` |
|
||||
|-------------------------|----------------------------------|
|
||||
| `Send` | `High` |
|
||||
| `Normal` / `Input` / `Loaded` / `Render` / `DataBind` | `Normal` |
|
||||
| `Background` / `ContextIdle` / `ApplicationIdle` / `SystemIdle` | `Low` |
|
||||
|
||||
### Pattern: Global DispatcherQueue Access (from ImageResizer)
|
||||
|
||||
WPF provided `Application.Current.Dispatcher` globally. WinUI 3 requires explicit storage:
|
||||
|
||||
```csharp
|
||||
// Store DispatcherQueue at app startup
|
||||
private static DispatcherQueue _uiDispatcherQueue;
|
||||
|
||||
public static void InitializeDispatcher()
|
||||
{
|
||||
_uiDispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
```
|
||||
|
||||
Usage with thread-check pattern (from `Settings.Reload()`):
|
||||
```csharp
|
||||
var currentDispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
if (currentDispatcher != null)
|
||||
{
|
||||
// Already on UI thread
|
||||
ReloadCore(jsonSettings);
|
||||
}
|
||||
else if (_uiDispatcherQueue != null)
|
||||
{
|
||||
// Dispatch to UI thread
|
||||
_uiDispatcherQueue.TryEnqueue(() => ReloadCore(jsonSettings));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback (e.g., CLI mode, no UI)
|
||||
ReloadCore(jsonSettings);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern: DispatcherQueue in ViewModels (from ProgressViewModel)
|
||||
|
||||
```csharp
|
||||
public class ProgressViewModel
|
||||
{
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
public ProgressViewModel()
|
||||
{
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
private void OnProgressChanged(double progress)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Progress = progress;
|
||||
// other UI updates...
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern: Async Dispatch (await)
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
await this.Dispatcher.InvokeAsync(() => { /* UI work */ });
|
||||
|
||||
// WinUI 3 (using TaskCompletionSource)
|
||||
var tcs = new TaskCompletionSource();
|
||||
this.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
try { /* UI work */ tcs.SetResult(); }
|
||||
catch (Exception ex) { tcs.SetException(ex); }
|
||||
});
|
||||
await tcs.Task;
|
||||
```
|
||||
|
||||
### C++/WinRT Threading
|
||||
|
||||
| Old API | New API |
|
||||
|---------|---------|
|
||||
| `winrt::resume_foreground(CoreDispatcher)` | `wil::resume_foreground(DispatcherQueue)` |
|
||||
| `CoreDispatcher.RunAsync()` | `DispatcherQueue.TryEnqueue()` |
|
||||
|
||||
Add `Microsoft.Windows.ImplementationLibrary` NuGet for `wil::resume_foreground`.
|
||||
|
||||
---
|
||||
|
||||
## Window Management
|
||||
|
||||
### WPF Window vs WinUI 3 Window
|
||||
|
||||
| Feature | WPF `Window` | WinUI 3 `Window` |
|
||||
|---------|-------------|------------------|
|
||||
| Base class | `ContentControl` → `DependencyObject` | **NOT** a control, NOT a `DependencyObject` |
|
||||
| `Resources` property | Yes | No — use root container's `Resources` |
|
||||
| `DataContext` property | Yes | No — use root `Page`/`UserControl` |
|
||||
| `VisualStateManager` | Yes | No — use inside child controls |
|
||||
| `Load`/`Unload` events | Yes | No |
|
||||
| `SizeToContent` | Yes (`Height`/`Width`/`WidthAndHeight`) | No — must implement manually |
|
||||
| `WindowState` (min/max/normal) | Yes | No — use `AppWindow.Presenter` |
|
||||
| `WindowStyle` | Yes | No — use `AppWindow` title bar APIs |
|
||||
| `ResizeMode` | Yes | No — use `AppWindow.Presenter` |
|
||||
| `WindowStartupLocation` | Yes | No — calculate manually |
|
||||
| `Icon` | `Window.Icon` | `AppWindow.SetIcon()` |
|
||||
| `Title` | `Window.Title` | `AppWindow.Title` (or `Window.Title`) |
|
||||
| Size (Width/Height) | Yes | No — use `AppWindow.Resize()` |
|
||||
| Position (Left/Top) | Yes | No — use `AppWindow.Move()` |
|
||||
| `IsDefault`/`IsCancel` on buttons | Yes | No — handle Enter/Escape in code-behind |
|
||||
|
||||
### Getting AppWindow from Window
|
||||
|
||||
```csharp
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using WinRT.Interop;
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
AppWindow appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
```
|
||||
|
||||
### Pattern: SizeToContent Replacement (from ImageResizer)
|
||||
|
||||
WinUI 3 has no `SizeToContent`. ImageResizer implemented a manual equivalent:
|
||||
|
||||
```csharp
|
||||
private void SizeToContent()
|
||||
{
|
||||
if (Content is not FrameworkElement content)
|
||||
return;
|
||||
|
||||
// Measure desired content size
|
||||
content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
var desiredHeight = content.DesiredSize.Height + WindowChromeHeight + Padding;
|
||||
|
||||
// Account for DPI scaling
|
||||
var scaleFactor = Content.XamlRoot.RasterizationScale;
|
||||
var pixelHeight = (int)(desiredHeight * scaleFactor);
|
||||
var pixelWidth = (int)(WindowWidth * scaleFactor);
|
||||
|
||||
// Resize via AppWindow
|
||||
var hwnd = WindowNative.GetWindowHandle(this);
|
||||
var windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
var appWindow = AppWindow.GetFromWindowId(windowId);
|
||||
appWindow.Resize(new Windows.Graphics.SizeInt32(pixelWidth, pixelHeight));
|
||||
}
|
||||
```
|
||||
|
||||
**Key details:**
|
||||
- `WindowChromeHeight` ≈ 32px for the title bar
|
||||
- Must multiply by `RasterizationScale` for DPI-aware sizing
|
||||
- Call `SizeToContent()` after page navigation or content changes
|
||||
- Unsubscribe previous event handlers before subscribing new ones to avoid memory leaks
|
||||
|
||||
### Window Positioning (Center Screen)
|
||||
|
||||
```csharp
|
||||
var displayArea = DisplayArea.GetFromWindowId(windowId, DisplayAreaFallback.Nearest);
|
||||
var centerX = (displayArea.WorkArea.Width - appWindow.Size.Width) / 2;
|
||||
var centerY = (displayArea.WorkArea.Height - appWindow.Size.Height) / 2;
|
||||
appWindow.Move(new Windows.Graphics.PointInt32(centerX, centerY));
|
||||
```
|
||||
|
||||
### Window State (Minimize/Maximize)
|
||||
|
||||
```csharp
|
||||
(appWindow.Presenter as OverlappedPresenter)?.Maximize();
|
||||
(appWindow.Presenter as OverlappedPresenter)?.Minimize();
|
||||
(appWindow.Presenter as OverlappedPresenter)?.Restore();
|
||||
```
|
||||
|
||||
### Title Bar Customization
|
||||
|
||||
```csharp
|
||||
// Extend content into title bar
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
this.SetTitleBar(AppTitleBar); // AppTitleBar is a XAML element
|
||||
|
||||
// Or via AppWindow API
|
||||
if (AppWindowTitleBar.IsCustomizationSupported())
|
||||
{
|
||||
var titleBar = appWindow.TitleBar;
|
||||
titleBar.ExtendsContentIntoTitleBar = true;
|
||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||
}
|
||||
```
|
||||
|
||||
### Tracking the Main Window
|
||||
|
||||
```csharp
|
||||
public partial class App : Application
|
||||
{
|
||||
public static Window MainWindow { get; private set; }
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
MainWindow = new MainWindow();
|
||||
MainWindow.Activate();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ContentDialog Requires XamlRoot
|
||||
|
||||
```csharp
|
||||
var dialog = new ContentDialog
|
||||
{
|
||||
Title = "Confirm",
|
||||
Content = "Are you sure?",
|
||||
PrimaryButtonText = "Yes",
|
||||
CloseButtonText = "No",
|
||||
XamlRoot = this.Content.XamlRoot // REQUIRED
|
||||
};
|
||||
var result = await dialog.ShowAsync();
|
||||
```
|
||||
|
||||
### File Pickers Require HWND
|
||||
|
||||
```csharp
|
||||
var picker = new FileOpenPicker();
|
||||
picker.FileTypeFilter.Add(".jpg");
|
||||
|
||||
// REQUIRED for desktop apps
|
||||
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
|
||||
WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd);
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
```
|
||||
|
||||
### Window Close Handling
|
||||
|
||||
```csharp
|
||||
// WPF
|
||||
protected override void OnClosing(CancelEventArgs e) { e.Cancel = true; this.Hide(); }
|
||||
|
||||
// WinUI 3
|
||||
this.AppWindow.Closing += (s, e) => { e.Cancel = true; this.AppWindow.Hide(); };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Entry Point (DISABLE_XAML_GENERATED_MAIN)
|
||||
|
||||
ImageResizer uses a custom `Program.cs` entry point instead of the WinUI 3 auto-generated `Main`. This is needed for:
|
||||
- CLI mode (process files without showing UI)
|
||||
- Custom initialization before the WinUI 3 App starts
|
||||
- Single-instance enforcement
|
||||
|
||||
### Setup
|
||||
|
||||
In `.csproj`:
|
||||
```xml
|
||||
<DefineConstants>DISABLE_XAML_GENERATED_MAIN,TRACE</DefineConstants>
|
||||
```
|
||||
|
||||
Create `Program.cs`:
|
||||
```csharp
|
||||
public static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
// CLI mode — no UI
|
||||
return RunCli(args);
|
||||
}
|
||||
|
||||
// GUI mode
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
Application.Start((p) =>
|
||||
{
|
||||
var context = new DispatcherQueueSynchronizationContext(
|
||||
DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WPF App Constructor Removal
|
||||
|
||||
WPF modules often created `new App()` to initialize the WPF `Application` and get `Application.Current.Dispatcher`. This is no longer needed — the WinUI 3 `Application.Start()` handles this.
|
||||
|
||||
```csharp
|
||||
// DELETE (WPF pattern):
|
||||
_imageResizerApp = new App();
|
||||
// REPLACE with: Store DispatcherQueue explicitly (see Global DispatcherQueue Access above)
|
||||
```
|
||||
@@ -1,365 +0,0 @@
|
||||
# XAML Migration Guide
|
||||
|
||||
Detailed reference for migrating XAML from WPF to WinUI 3, based on the ImageResizer migration.
|
||||
|
||||
## XML Namespace Declaration Changes
|
||||
|
||||
### Before (WPF)
|
||||
|
||||
```xml
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MyApp"
|
||||
xmlns:m="clr-namespace:ImageResizer.Models"
|
||||
xmlns:p="clr-namespace:ImageResizer.Properties"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
|
||||
x:Class="MyApp.MainWindow">
|
||||
```
|
||||
|
||||
### After (WinUI 3)
|
||||
|
||||
```xml
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:MyApp"
|
||||
xmlns:m="using:ImageResizer.Models"
|
||||
xmlns:converters="using:ImageResizer.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Class="MyApp.MainWindow">
|
||||
```
|
||||
|
||||
### Key Changes
|
||||
|
||||
| WPF Syntax | WinUI 3 Syntax | Notes |
|
||||
|------------|---------------|-------|
|
||||
| `clr-namespace:Foo` | `using:Foo` | CLR namespace mapping |
|
||||
| `clr-namespace:Foo;assembly=Bar` | `using:Foo` | Assembly qualification not needed |
|
||||
| `xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"` | **Remove entirely** | WPF-UI namespace no longer needed |
|
||||
| `xmlns:p="clr-namespace:...Properties"` | **Remove** | No more `.resx` string bindings |
|
||||
| `sys:String` (from mscorlib) | `x:String` | XAML intrinsic types |
|
||||
| `sys:Int32` | `x:Int32` | XAML intrinsic types |
|
||||
| `sys:Boolean` | `x:Boolean` | XAML intrinsic types |
|
||||
| `sys:Double` | `x:Double` | XAML intrinsic types |
|
||||
|
||||
## Unsupported Markup Extensions
|
||||
|
||||
| WPF Markup Extension | WinUI 3 Alternative |
|
||||
|----------------------|---------------------|
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` (theme-reactive) or `{StaticResource Key}` |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` to a static property, or code-behind |
|
||||
| `{x:Type local:MyType}` | Not supported; use code-behind |
|
||||
| `{x:Array}` | Not supported; create collections in code-behind |
|
||||
| `{x:Code}` | Not supported |
|
||||
|
||||
### DynamicResource → ThemeResource
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<TextBlock Foreground="{DynamicResource MyBrush}" />
|
||||
|
||||
<!-- WinUI 3 -->
|
||||
<TextBlock Foreground="{ThemeResource MyBrush}" />
|
||||
```
|
||||
|
||||
`ThemeResource` automatically updates when the app theme changes (Light/Dark/HighContrast). For truly dynamic non-theme resources, set values in code-behind or use data binding.
|
||||
|
||||
### x:Static Resource Strings → x:Uid
|
||||
|
||||
This is the most pervasive XAML change. WPF used `{x:Static}` to bind to strongly-typed `.resx` resource strings. WinUI 3 uses `x:Uid` with `.resw` files.
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<Button Content="{x:Static p:Resources.Cancel}" />
|
||||
<TextBlock Text="{x:Static p:Resources.Input_Header}" />
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<Button x:Uid="Cancel" />
|
||||
<TextBlock x:Uid="Input_Header" />
|
||||
```
|
||||
|
||||
In `Strings/en-us/Resources.resw`:
|
||||
```xml
|
||||
<data name="Cancel.Content" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="Input_Header.Text" xml:space="preserve">
|
||||
<value>Select a size</value>
|
||||
</data>
|
||||
```
|
||||
|
||||
The `x:Uid` suffix (`.Content`, `.Text`, `.Header`, `.PlaceholderText`, etc.) matches the target property name.
|
||||
|
||||
### DataType with x:Type → Remove
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<DataTemplate DataType="{x:Type m:ResizeSize}">
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<DataTemplate x:DataType="m:ResizeSize">
|
||||
```
|
||||
|
||||
## WPF-UI (Lepo) Controls Removal
|
||||
|
||||
If the module uses the `WPF-UI` library, replace all Lepo controls with native WinUI 3 equivalents.
|
||||
|
||||
### Window
|
||||
|
||||
```xml
|
||||
<!-- WPF (WPF-UI) -->
|
||||
<ui:FluentWindow
|
||||
ExtendsContentIntoTitleBar="True"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<ui:TitleBar Title="Image Resizer" />
|
||||
...
|
||||
</ui:FluentWindow>
|
||||
|
||||
<!-- WinUI 3 (native) -->
|
||||
<Window>
|
||||
<!-- Title bar managed via code-behind: this.ExtendsContentIntoTitleBar = true; -->
|
||||
...
|
||||
</Window>
|
||||
```
|
||||
|
||||
### App.xaml Resources
|
||||
|
||||
```xml
|
||||
<!-- WPF (WPF-UI) -->
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemesDictionary Theme="Dark" />
|
||||
<ui:ControlsDictionary />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<!-- WinUI 3 (native) -->
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### Common Control Replacements
|
||||
|
||||
```xml
|
||||
<!-- WPF-UI NumberBox -->
|
||||
<ui:NumberBox Value="{Binding Width}" />
|
||||
<!-- WinUI 3 -->
|
||||
<NumberBox Value="{x:Bind ViewModel.Width, Mode=TwoWay}" />
|
||||
|
||||
<!-- WPF-UI InfoBar -->
|
||||
<ui:InfoBar Title="Warning" Message="..." IsOpen="True" Severity="Warning" />
|
||||
<!-- WinUI 3 -->
|
||||
<InfoBar Title="Warning" Message="..." IsOpen="True" Severity="Warning" />
|
||||
|
||||
<!-- WPF-UI ProgressRing -->
|
||||
<ui:ProgressRing IsIndeterminate="True" />
|
||||
<!-- WinUI 3 -->
|
||||
<ProgressRing IsActive="True" />
|
||||
|
||||
<!-- WPF-UI SymbolIcon -->
|
||||
<ui:SymbolIcon Symbol="Add" />
|
||||
<!-- WinUI 3 -->
|
||||
<SymbolIcon Symbol="Add" />
|
||||
```
|
||||
|
||||
### Button Patterns
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<Button IsDefault="True" Content="OK" />
|
||||
<Button IsCancel="True" Content="Cancel" />
|
||||
|
||||
<!-- WinUI 3 (no IsDefault/IsCancel) -->
|
||||
<Button Style="{StaticResource AccentButtonStyle}" Content="OK" />
|
||||
<Button Content="Cancel" />
|
||||
<!-- Handle Enter/Escape keys in code-behind if needed -->
|
||||
```
|
||||
|
||||
## Style and Template Changes
|
||||
|
||||
### Triggers → VisualStateManager
|
||||
|
||||
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<Style TargetType="Button">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="LightBlue"/>
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.Background" Value="LightBlue"/>
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<ContentPresenter />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
```
|
||||
|
||||
### No Binding in Setter.Value
|
||||
|
||||
```xml
|
||||
<!-- WPF (works) -->
|
||||
<Setter Property="Foreground" Value="{Binding TextColor}"/>
|
||||
|
||||
<!-- WinUI 3 (does NOT work — use StaticResource) -->
|
||||
<Setter Property="Foreground" Value="{StaticResource TextColorBrush}"/>
|
||||
```
|
||||
|
||||
### Visual State Name Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `MouseOver` | `PointerOver` |
|
||||
| `Disabled` | `Disabled` |
|
||||
| `Pressed` | `Pressed` |
|
||||
|
||||
## Resource Dictionary Changes
|
||||
|
||||
### Window.Resources → Grid.Resources
|
||||
|
||||
WinUI 3 `Window` is NOT a `DependencyObject` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
<Window>
|
||||
<Window.Resources>
|
||||
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
|
||||
</Window.Resources>
|
||||
<Grid>...</Grid>
|
||||
</Window>
|
||||
|
||||
<!-- WinUI 3 -->
|
||||
<Window>
|
||||
<Grid>
|
||||
<Grid.Resources>
|
||||
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
|
||||
</Grid.Resources>
|
||||
...
|
||||
</Grid>
|
||||
</Window>
|
||||
```
|
||||
|
||||
### Theme Dictionaries
|
||||
|
||||
```xml
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush x:Key="MyBrush" Color="#FF000000"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="MyBrush" Color="#FFFFFFFF"/>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="MyBrush" Color="{ThemeResource SystemColorWindowTextColor}"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
```
|
||||
|
||||
## URI Scheme Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `pack://application:,,,/MyAssembly;component/image.png` | `ms-appx:///Assets/image.png` |
|
||||
| `pack://application:,,,/image.png` | `ms-appx:///image.png` |
|
||||
| Relative path `../image.png` | `ms-appx:///image.png` |
|
||||
|
||||
Assets directory convention: `Resources/` → `Assets/<Module>/`
|
||||
|
||||
## Data Binding Changes
|
||||
|
||||
### {Binding} vs {x:Bind}
|
||||
|
||||
Both are available. Prefer `{x:Bind}` for compile-time safety and performance.
|
||||
|
||||
| Feature | `{Binding}` | `{x:Bind}` |
|
||||
|---------|------------|------------|
|
||||
| Default mode | `OneWay` | **`OneTime`** (explicit `Mode=OneWay` required!) |
|
||||
| Context | `DataContext` | Code-behind class |
|
||||
| Resolution | Runtime | Compile-time |
|
||||
| Performance | Reflection-based | Compiled |
|
||||
| Function binding | No | Yes |
|
||||
|
||||
### WPF-Specific Binding Features to Remove
|
||||
|
||||
```xml
|
||||
<!-- These WPF-only features must be removed or rewritten -->
|
||||
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<!-- WinUI 3: UpdateSourceTrigger not needed; TextBox uses PropertyChanged by default -->
|
||||
<TextBox Text="{x:Bind ViewModel.Value, Mode=TwoWay}" />
|
||||
|
||||
{Binding RelativeSource={RelativeSource Self}, ...}
|
||||
<!-- WinUI 3: Use x:Bind which binds to the page itself, or use ElementName -->
|
||||
|
||||
<ItemsControl ItemsSource="{Binding}" />
|
||||
<!-- WinUI 3: Must specify explicit path -->
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.Items}" />
|
||||
```
|
||||
|
||||
## WPF-Only Window Properties to Remove
|
||||
|
||||
These properties exist on WPF `Window` but not WinUI 3:
|
||||
|
||||
```xml
|
||||
<!-- Remove from XAML — handle in code-behind via AppWindow API -->
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ResizeMode="NoResize"
|
||||
ExtendsContentIntoTitleBar="True" <!-- Set in code-behind -->
|
||||
```
|
||||
|
||||
## XAML Control Property Changes
|
||||
|
||||
| WPF Property | WinUI 3 Property | Notes |
|
||||
|-------------|-----------------|-------|
|
||||
| `Focusable` | `IsTabStop` | Different name |
|
||||
| `SnapsToDevicePixels` | Not available | WinUI handles pixel snapping internally |
|
||||
| `UseLayoutRounding` | `UseLayoutRounding` | Same |
|
||||
| `IsHitTestVisible` | `IsHitTestVisible` | Same |
|
||||
| `TextBox.VerticalScrollBarVisibility` | `ScrollViewer.VerticalScrollBarVisibility` (attached) | Attached property |
|
||||
|
||||
## XAML Formatting (XamlStyler)
|
||||
|
||||
After migration, run XamlStyler to normalize formatting:
|
||||
- Alphabetize xmlns declarations and element attributes
|
||||
- Add UTF-8 BOM to all XAML files
|
||||
- Normalize comment spacing: `<!-- text -->` → `<!-- text -->`
|
||||
|
||||
PowerToys command: `.\.pipelines\applyXamlStyling.ps1 -Main`
|
||||
213
.github/workflows/auto-label-issues.yml
vendored
213
.github/workflows/auto-label-issues.yml
vendored
@@ -1,213 +0,0 @@
|
||||
name: Automatic Triaging on Issue Creation
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
# Manual trigger: go to Actions → "Automatic Triaging on Issue Creation" → Run workflow.
|
||||
# Enter one or more comma-separated issue numbers (e.g. "1234" or "1234,1235,1236")
|
||||
# to apply AI-generated area labels to existing untriaged issues.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_numbers:
|
||||
description: 'Comma-separated issue number(s) to label (e.g. 1234 or 1234,1235)'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
|
||||
concurrency:
|
||||
# Each workflow run gets its own concurrency group.
|
||||
# For issue events, group by issue number so a rapid close+reopen only runs once.
|
||||
# For manual dispatch (which may cover multiple issues), use the unique run ID.
|
||||
group: ${{ github.event_name == 'issues' && format('{0}-issue-{1}', github.workflow, github.event.issue.number) || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply area labels with AI
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
# actions/github-script does not propagate `github-token` to
|
||||
# process.env. Expose it explicitly so the inline script can
|
||||
# authenticate against the GitHub Models inference endpoint.
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// When triggered manually, process each supplied issue number in turn.
|
||||
// When triggered by an issue event, use the event's issue number.
|
||||
let issueNumbers;
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
issueNumbers = String(context.payload.inputs.issue_numbers)
|
||||
.split(',')
|
||||
.map(s => parseInt(s.trim(), 10))
|
||||
.filter(n => Number.isFinite(n) && n > 0);
|
||||
} else {
|
||||
issueNumbers = [context.issue.number];
|
||||
}
|
||||
|
||||
if (issueNumbers.length === 0) {
|
||||
console.log('No valid issue numbers to process; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const issueNumber of issueNumbers) {
|
||||
console.log(`\n--- Processing issue #${issueNumber} ---`);
|
||||
await labelIssue(issueNumber);
|
||||
}
|
||||
|
||||
async function labelIssue(issueNumber) {
|
||||
// Fetch the issue so both the automatic and manual paths have the same data.
|
||||
const { data: issue } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
|
||||
const title = issue.title ?? '';
|
||||
const body = issue.body ?? '';
|
||||
|
||||
if (!title && !body) {
|
||||
console.log(`Issue #${issueNumber} has no title or body; skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Truncation limit for issue body sent to the model. Keeps the
|
||||
// prompt within the model's context window and avoids high token usage.
|
||||
const MAX_BODY_LENGTH = 4000;
|
||||
|
||||
// Upper bound on model response tokens. A JSON array of label strings
|
||||
// is compact; 200 tokens is more than enough for any realistic response.
|
||||
const MAX_TOKENS = 200;
|
||||
|
||||
// All valid Product-* and Area-* labels the agent may choose from.
|
||||
const VALID_LABELS = [
|
||||
'Product-Advanced Paste',
|
||||
'Product-Always On Top',
|
||||
'Product-Awake',
|
||||
'Product-ColorPicker',
|
||||
'Product-Command Not Found',
|
||||
'Product-Command Palette',
|
||||
'Product-CropAndLock',
|
||||
'Product-Environment Variables',
|
||||
'Product-FancyZones',
|
||||
'Product-File Explorer',
|
||||
'Product-File Locksmith',
|
||||
'Product-Find My Mouse',
|
||||
'Product-Grab And Move',
|
||||
'Product-Hosts',
|
||||
'Product-Image Resizer',
|
||||
'Product-Keyboard Manager',
|
||||
'Product-LightSwitch',
|
||||
'Product-Mouse Highlighter',
|
||||
'Product-Mouse Jump',
|
||||
'Product-Mouse Pointer Crosshairs',
|
||||
'Product-Mouse Utilities',
|
||||
'Product-Mouse Without Borders',
|
||||
'Product-New+',
|
||||
'Product-Peek',
|
||||
'Product-Power Display',
|
||||
'Product-PowerRename',
|
||||
'Product-PowerToys Run',
|
||||
'Product-Quick Accent',
|
||||
'Product-Registry Preview',
|
||||
'Product-Screen Ruler',
|
||||
'Product-Settings',
|
||||
'Product-Shortcut Guide',
|
||||
'Product-Text Extractor',
|
||||
'Product-Workspaces',
|
||||
'Product-ZoomIt',
|
||||
'Area-Setup/Install',
|
||||
'Area-Localization',
|
||||
];
|
||||
|
||||
const systemPrompt = `You are a GitHub issue triage assistant for the microsoft/PowerToys repository.
|
||||
Your job is to classify issues by assigning the correct area label(s).
|
||||
|
||||
Rules:
|
||||
- Only return labels from the following list, exactly as written:
|
||||
${VALID_LABELS.map(l => ` • ${l}`).join('\n')}
|
||||
- Choose only the labels that clearly match the issue content.
|
||||
- If the issue mentions multiple areas, include a label for each one.
|
||||
- If no label fits, return an empty array.
|
||||
- Respond with ONLY a JSON array of label strings, no explanation.
|
||||
Example: ["Product-FancyZones","Product-Settings"]`;
|
||||
|
||||
const userPrompt = `Issue title: ${title}
|
||||
|
||||
Issue body:
|
||||
${body.slice(0, MAX_BODY_LENGTH)}`;
|
||||
|
||||
// Validate that the token is available before making the API call.
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
console.log('GITHUB_TOKEN is not set; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the GitHub Models inference endpoint (OpenAI-compatible).
|
||||
const response = await fetch(
|
||||
'https://models.inference.ai.azure.com/chat/completions',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-4o-mini',
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
max_tokens: MAX_TOKENS,
|
||||
// temperature: 0 ensures deterministic, consistent label
|
||||
// classification across similar issues.
|
||||
temperature: 0,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text();
|
||||
console.log(`GitHub Models API error: ${response.status} ${response.statusText} — ${errorBody}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const text = data.choices?.[0]?.message?.content?.trim() ?? '';
|
||||
console.log(`Model response: ${text}`);
|
||||
|
||||
let suggested;
|
||||
try {
|
||||
suggested = JSON.parse(text);
|
||||
} catch {
|
||||
console.log('Could not parse model response as JSON; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(suggested) || suggested.length === 0) {
|
||||
console.log('No labels suggested by the model.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Only apply labels that are in the allow-list.
|
||||
const validSet = new Set(VALID_LABELS);
|
||||
const toApply = [...new Set(suggested.filter(l => validSet.has(l)))];
|
||||
|
||||
if (toApply.length === 0) {
|
||||
console.log('Model returned no valid labels.');
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issueNumber,
|
||||
labels: toApply,
|
||||
});
|
||||
console.log(`Issue #${issueNumber}: added labels: ${toApply.join(', ')}`);
|
||||
}
|
||||
4
.github/workflows/msstore-submissions.yml
vendored
4
.github/workflows/msstore-submissions.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
export $(echo 'anypass_just_to_unlock' | gnome-keyring-daemon --start --components=gpg,pkcs11,secrets,ssh)
|
||||
|
||||
- name: Log in to Azure
|
||||
uses: azure/login@v3
|
||||
uses: azure/login@v2
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
- uses: microsoft/setup-msstore-cli@v1
|
||||
|
||||
- name: Fetch Store Credential
|
||||
uses: azure/cli@v3
|
||||
uses: azure/cli@v2
|
||||
with:
|
||||
azcliversion: latest
|
||||
inlineScript: |-
|
||||
|
||||
31
.github/workflows/spelling2.yml
vendored
31
.github/workflows/spelling2.yml
vendored
@@ -55,7 +55,7 @@ name: Spell checking
|
||||
# spelling:
|
||||
# # remove `security-events: write` and `use_sarif: 1`
|
||||
# # remove `experimental_apply_changes_via_bot: 1`
|
||||
# ... otherwise, adjust the `with:` as you wish
|
||||
# ... otherwise adjust the `with:` as you wish
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -74,8 +74,6 @@ on:
|
||||
types:
|
||||
- "created"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Check Spelling
|
||||
@@ -87,7 +85,7 @@ jobs:
|
||||
outputs:
|
||||
followup: ${{ steps.spelling.outputs.followup }}
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ (contains(github.event_name, 'pull_request') && github.event.pull_request.state == 'open') || github.event_name == 'push' }}
|
||||
if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }}
|
||||
concurrency:
|
||||
group: spelling-${{ github.event.pull_request.number || github.ref }}
|
||||
# note: If you use only_check_changed_files, you do not want cancel-in-progress
|
||||
@@ -95,7 +93,7 @@ jobs:
|
||||
steps:
|
||||
- name: check-spelling
|
||||
id: spelling
|
||||
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||
uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25
|
||||
with:
|
||||
config: .github/actions/spell-check
|
||||
suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
|
||||
@@ -137,12 +135,11 @@ jobs:
|
||||
cspell:cpp/compiler-msvc.txt
|
||||
cspell:python/common/extra.txt
|
||||
cspell:scala/scala.txt
|
||||
ignored: ignored-expect-variant
|
||||
|
||||
comment-push:
|
||||
name: Report (Push)
|
||||
# If your workflow isn't running on push, you can remove this job
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -150,23 +147,30 @@ jobs:
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push'
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||
uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25
|
||||
with:
|
||||
config: .github/actions/spell-check
|
||||
checkout: true
|
||||
spell_check_this: microsoft/PowerToys@main
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
|
||||
comment-pr:
|
||||
name: Report (PR)
|
||||
# If you workflow isn't running on pull_request*, you can remove this job
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-latest
|
||||
needs: spelling
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pull-requests: write
|
||||
if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request')
|
||||
steps:
|
||||
- name: comment
|
||||
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||
uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25
|
||||
with:
|
||||
config: .github/actions/spell-check
|
||||
checkout: true
|
||||
spell_check_this: check-spelling/spell-check-this@prerelease
|
||||
task: ${{ needs.spelling.outputs.followup }}
|
||||
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
|
||||
|
||||
@@ -176,13 +180,12 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
actions: read
|
||||
runs-on: ubuntu-slim
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{
|
||||
github.repository_owner != 'microsoft' &&
|
||||
github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request &&
|
||||
contains(github.event.comment.body, '@check-spelling-bot') &&
|
||||
contains(github.event.comment.body, 'apply') &&
|
||||
contains(github.event.comment.body, '@check-spelling-bot apply') &&
|
||||
contains(github.event.comment.body, 'https://')
|
||||
}}
|
||||
concurrency:
|
||||
@@ -190,7 +193,7 @@ jobs:
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: apply spelling updates
|
||||
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||
uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25
|
||||
with:
|
||||
experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }}
|
||||
checkout: true
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -359,14 +359,3 @@ src/common/Telemetry/*.etl
|
||||
|
||||
# PowerToysInstaller Build Temp Files
|
||||
installer/*/*.wxs.bk
|
||||
/src/modules/awake/.claude
|
||||
|
||||
# Claude AI local settings - local-only, not committed
|
||||
**/.claude/settings.local.json
|
||||
|
||||
# Squad / Copilot agents — local-only, not committed
|
||||
.copilot
|
||||
.squad
|
||||
.squad-workstream
|
||||
.github/agents/**squad**.md
|
||||
.github/workflows/**squad**.yml
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"StylesReportTool\\PowerToys.StylesReportTool.exe",
|
||||
|
||||
"CalculatorEngineCommon.dll",
|
||||
"PowerToys.Common.UI.Controls.dll",
|
||||
"PowerToys.ManagedTelemetry.dll",
|
||||
"PowerToys.ManagedCommon.dll",
|
||||
"PowerToys.ManagedCsWin32.dll",
|
||||
@@ -106,13 +105,7 @@
|
||||
"PowerToys.SvgThumbnailProvider.dll",
|
||||
"PowerToys.SvgThumbnailProvider.exe",
|
||||
"PowerToys.SvgThumbnailProviderCpp.dll",
|
||||
"PowerToys.KeyboardManager.dll",
|
||||
|
||||
"KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe",
|
||||
"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.exe",
|
||||
"WinUI3Apps\\PowerToys.KeyboardManagerEditorUI.dll",
|
||||
"KeyboardManagerEngine\\PowerToys.KeyboardManagerEngine.exe",
|
||||
"PowerToys.KeyboardManagerEditorLibraryWrapper.dll",
|
||||
"WinUI3Apps\\PowerToys.HostsModuleInterface.dll",
|
||||
"WinUI3Apps\\PowerToys.HostsUILib.dll",
|
||||
"WinUI3Apps\\PowerToys.Hosts.dll",
|
||||
@@ -124,7 +117,6 @@
|
||||
"WinUI3Apps\\PowerToys.FileLocksmithUI.dll",
|
||||
"WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll",
|
||||
"FileLocksmithContextMenuPackage.msix",
|
||||
"FileLocksmithCLI.exe",
|
||||
|
||||
"WinUI3Apps\\Peek.Common.dll",
|
||||
"WinUI3Apps\\Peek.FilePreviewer.dll",
|
||||
@@ -132,22 +124,16 @@
|
||||
"WinUI3Apps\\Powertoys.Peek.UI.exe",
|
||||
"WinUI3Apps\\Powertoys.Peek.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.QuickAccess.dll",
|
||||
"WinUI3Apps\\PowerToys.QuickAccess.exe",
|
||||
"WinUI3Apps\\PowerToys.Settings.UI.Controls.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll",
|
||||
"WinUI3Apps\\PowerToys.EnvironmentVariablesUILib.dll",
|
||||
"WinUI3Apps\\PowerToys.EnvironmentVariables.dll",
|
||||
"WinUI3Apps\\PowerToys.EnvironmentVariables.exe",
|
||||
|
||||
"WinUI3Apps\\PowerToys.ImageResizer.exe",
|
||||
"WinUI3Apps\\PowerToys.ImageResizer.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerCLI.exe",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerCLI.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerExt.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerContextMenu.dll",
|
||||
"WinUI3Apps\\ImageResizerContextMenuPackage.msix",
|
||||
"PowerToys.ImageResizer.exe",
|
||||
"PowerToys.ImageResizer.dll",
|
||||
"PowerToys.ImageResizerExt.dll",
|
||||
"PowerToys.ImageResizerContextMenu.dll",
|
||||
"ImageResizerContextMenuPackage.msix",
|
||||
|
||||
"PowerToys.LightSwitchModuleInterface.dll",
|
||||
"LightSwitchService\\PowerToys.LightSwitchService.exe",
|
||||
@@ -217,12 +203,6 @@
|
||||
"PowerToys.PowerAccentModuleInterface.dll",
|
||||
"PowerToys.PowerAccentKeyboardService.dll",
|
||||
|
||||
"PowerToys.PowerDisplayModuleInterface.dll",
|
||||
"WinUI3Apps\\PowerToys.PowerDisplay.dll",
|
||||
"WinUI3Apps\\PowerToys.PowerDisplay.exe",
|
||||
"PowerDisplay.Lib.dll",
|
||||
"PowerDisplay.Models.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.PowerRenameExt.dll",
|
||||
"WinUI3Apps\\PowerToys.PowerRename.exe",
|
||||
"WinUI3Apps\\PowerToys.PowerRenameContextMenu.dll",
|
||||
@@ -250,9 +230,6 @@
|
||||
"PowerToys.ZoomItModuleInterface.dll",
|
||||
"PowerToys.ZoomItSettingsInterop.dll",
|
||||
|
||||
"PowerToys.GrabAndMove.exe",
|
||||
"PowerToys.GrabAndMoveModuleInterface.dll",
|
||||
|
||||
"WinUI3Apps\\PowerToys.Settings.dll",
|
||||
"WinUI3Apps\\PowerToys.Settings.exe",
|
||||
|
||||
@@ -264,8 +241,8 @@
|
||||
"Workspaces.ModuleServices.dll",
|
||||
"Microsoft.CommandPalette.Extensions.dll",
|
||||
"Microsoft.CommandPalette.Extensions.Toolkit.dll",
|
||||
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.dll",
|
||||
"WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
"Microsoft.CmdPal.Ext.PowerToys.dll",
|
||||
"Microsoft.CmdPal.Ext.PowerToys.exe",
|
||||
"*Microsoft.CmdPal.UI_*.msix",
|
||||
|
||||
"PowerToys.DSC.dll",
|
||||
@@ -394,8 +371,6 @@
|
||||
"UnitsNet.dll",
|
||||
"UtfUnknown.dll",
|
||||
"Wpf.Ui.dll",
|
||||
"WmiLight.dll",
|
||||
"WmiLight.Native.dll",
|
||||
"Shmuelie.WinRTServer.dll",
|
||||
"ToolGood.Words.Pinyin.dll"
|
||||
],
|
||||
|
||||
@@ -13,36 +13,9 @@ Param(
|
||||
|
||||
# Root folder Path for processing
|
||||
[Parameter(Mandatory=$False,Position=4)]
|
||||
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json",
|
||||
|
||||
# Use Azure Pipeline artifact as source for metapackage
|
||||
[Parameter(Mandatory=$False,Position=5)]
|
||||
[boolean]$useArtifactSource = $False,
|
||||
|
||||
# Azure DevOps organization URL
|
||||
[Parameter(Mandatory=$False,Position=6)]
|
||||
[string]$azureDevOpsOrg = "https://dev.azure.com/microsoft",
|
||||
|
||||
# Azure DevOps project name
|
||||
[Parameter(Mandatory=$False,Position=7)]
|
||||
[string]$azureDevOpsProject = "ProjectReunion",
|
||||
|
||||
# Pipeline build ID (or "latest" for latest build)
|
||||
[Parameter(Mandatory=$False,Position=8)]
|
||||
[string]$buildId = "",
|
||||
|
||||
# Artifact name containing the NuGet packages
|
||||
[Parameter(Mandatory=$False,Position=9)]
|
||||
[string]$artifactName = "WindowsAppSDK_Nuget_And_MSIX",
|
||||
|
||||
# Metapackage name to look for in artifact
|
||||
[Parameter(Mandatory=$False,Position=10)]
|
||||
[string]$metaPackageName = "Microsoft.WindowsAppSDK"
|
||||
[string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
|
||||
)
|
||||
|
||||
# Script-level constants
|
||||
$script:PackageVersionRegex = '^(.+?)\.(\d+\..*)$'
|
||||
|
||||
|
||||
|
||||
function Read-FileWithEncoding {
|
||||
@@ -84,7 +57,7 @@ function Add-NuGetSourceAndMapping {
|
||||
|
||||
# Ensure packageSources exists
|
||||
if (-not $Xml.configuration.packageSources) {
|
||||
$null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSources"))
|
||||
$Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) | Out-Null
|
||||
}
|
||||
$sources = $Xml.configuration.packageSources
|
||||
|
||||
@@ -93,13 +66,13 @@ function Add-NuGetSourceAndMapping {
|
||||
if (-not $sourceNode) {
|
||||
$sourceNode = $Xml.CreateElement("add")
|
||||
$sourceNode.SetAttribute("key", $Key)
|
||||
$null = $sources.AppendChild($sourceNode)
|
||||
$sources.AppendChild($sourceNode) | Out-Null
|
||||
}
|
||||
$sourceNode.SetAttribute("value", $Value)
|
||||
|
||||
# Ensure packageSourceMapping exists
|
||||
if (-not $Xml.configuration.packageSourceMapping) {
|
||||
$null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping"))
|
||||
$Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) | Out-Null
|
||||
}
|
||||
$mapping = $Xml.configuration.packageSourceMapping
|
||||
|
||||
@@ -107,7 +80,7 @@ function Add-NuGetSourceAndMapping {
|
||||
$invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']")
|
||||
if ($invalidNodes) {
|
||||
foreach ($node in $invalidNodes) {
|
||||
$null = $mapping.RemoveChild($node)
|
||||
$mapping.RemoveChild($node) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +91,9 @@ function Add-NuGetSourceAndMapping {
|
||||
$mappingSource.SetAttribute("key", $Key)
|
||||
# Insert at top for priority
|
||||
if ($mapping.HasChildNodes) {
|
||||
$null = $mapping.InsertBefore($mappingSource, $mapping.FirstChild)
|
||||
$mapping.InsertBefore($mappingSource, $mapping.FirstChild) | Out-Null
|
||||
} else {
|
||||
$null = $mapping.AppendChild($mappingSource)
|
||||
$mapping.AppendChild($mappingSource) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,273 +110,14 @@ function Add-NuGetSourceAndMapping {
|
||||
foreach ($pattern in $Patterns) {
|
||||
$pkg = $Xml.CreateElement("package")
|
||||
$pkg.SetAttribute("pattern", $pattern)
|
||||
$null = $mappingSource.AppendChild($pkg)
|
||||
$mappingSource.AppendChild($pkg) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Download-ArtifactFromPipeline {
|
||||
param (
|
||||
[string]$Organization,
|
||||
[string]$Project,
|
||||
[string]$BuildId,
|
||||
[string]$ArtifactName,
|
||||
[string]$OutputDir
|
||||
)
|
||||
|
||||
Write-Host "Downloading artifact '$ArtifactName' from build $BuildId..."
|
||||
$null = New-Item -ItemType Directory -Path $OutputDir -Force
|
||||
|
||||
try {
|
||||
# Authenticate with Azure DevOps using System Access Token (if available)
|
||||
if ($env:SYSTEM_ACCESSTOKEN) {
|
||||
Write-Host "Authenticating with Azure DevOps using System Access Token..."
|
||||
$env:AZURE_DEVOPS_EXT_PAT = $env:SYSTEM_ACCESSTOKEN
|
||||
} else {
|
||||
Write-Host "No SYSTEM_ACCESSTOKEN found, assuming az CLI is already authenticated..."
|
||||
}
|
||||
|
||||
# Use az CLI to download artifact
|
||||
& az pipelines runs artifact download `
|
||||
--organization $Organization `
|
||||
--project $Project `
|
||||
--run-id $BuildId `
|
||||
--artifact-name $ArtifactName `
|
||||
--path $OutputDir
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "Successfully downloaded artifact to $OutputDir"
|
||||
return $true
|
||||
} else {
|
||||
Write-Warning "Failed to download artifact. Exit code: $LASTEXITCODE"
|
||||
return $false
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Error downloading artifact: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NuspecDependencies {
|
||||
param (
|
||||
[string]$NupkgPath,
|
||||
[string]$TargetFramework = ""
|
||||
)
|
||||
|
||||
$tempDir = Join-Path $env:TEMP "nuspec_parse_$(Get-Random)"
|
||||
|
||||
try {
|
||||
# Extract .nupkg (it's a zip file)
|
||||
# Workaround: Expand-Archive may not recognize .nupkg extension, so copy to .zip first
|
||||
$tempZip = Join-Path $env:TEMP "temp_$(Get-Random).zip"
|
||||
Copy-Item $NupkgPath -Destination $tempZip -Force
|
||||
Expand-Archive -Path $tempZip -DestinationPath $tempDir -Force
|
||||
Remove-Item $tempZip -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Find .nuspec file
|
||||
$nuspecFile = Get-ChildItem -Path $tempDir -Filter "*.nuspec" -Recurse | Select-Object -First 1
|
||||
|
||||
if (-not $nuspecFile) {
|
||||
Write-Warning "No .nuspec file found in $NupkgPath"
|
||||
return @{}
|
||||
}
|
||||
|
||||
[xml]$nuspec = Get-Content $nuspecFile.FullName
|
||||
|
||||
# Extract package info
|
||||
$packageId = $nuspec.package.metadata.id
|
||||
$version = $nuspec.package.metadata.version
|
||||
Write-Host "Parsing $packageId version $version"
|
||||
|
||||
# Parse dependencies
|
||||
$dependencies = @{}
|
||||
$depGroups = $nuspec.package.metadata.dependencies.group
|
||||
|
||||
if ($depGroups) {
|
||||
# Dependencies are grouped by target framework
|
||||
foreach ($group in $depGroups) {
|
||||
$fx = $group.targetFramework
|
||||
Write-Host " Target Framework: $fx"
|
||||
|
||||
foreach ($dep in $group.dependency) {
|
||||
$depId = $dep.id
|
||||
$depVer = $dep.version
|
||||
# Remove version range brackets if present (e.g., "[2.0.0]" -> "2.0.0")
|
||||
$depVer = $depVer -replace '[\[\]]', ''
|
||||
$dependencies[$depId] = $depVer
|
||||
Write-Host " - ${depId} : ${depVer}"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# No grouping, direct dependencies
|
||||
$deps = $nuspec.package.metadata.dependencies.dependency
|
||||
if ($deps) {
|
||||
foreach ($dep in $deps) {
|
||||
$depId = $dep.id
|
||||
$depVer = $dep.version
|
||||
$depVer = $depVer -replace '[\[\]]', ''
|
||||
$dependencies[$depId] = $depVer
|
||||
Write-Host " - ${depId} : ${depVer}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dependencies
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to parse nuspec: $_"
|
||||
return @{}
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-ArtifactBasedDependencies {
|
||||
param (
|
||||
[string]$ArtifactDir,
|
||||
[string]$MetaPackageName,
|
||||
[string]$SourceUrl,
|
||||
[string]$OutputDir
|
||||
)
|
||||
|
||||
Write-Host "Resolving dependencies from artifact-based metapackage..."
|
||||
$null = New-Item -ItemType Directory -Path $OutputDir -Force
|
||||
|
||||
# Find the metapackage in artifact
|
||||
$metaNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.*.nupkg" |
|
||||
Where-Object { $_.Name -notmatch "Runtime" } |
|
||||
Select-Object -First 1
|
||||
|
||||
if (-not $metaNupkg) {
|
||||
Write-Warning "Metapackage $MetaPackageName not found in artifact"
|
||||
return @{}
|
||||
}
|
||||
|
||||
# Extract version from filename
|
||||
if ($metaNupkg.Name -match "$MetaPackageName\.(.+)\.nupkg") {
|
||||
$metaVersion = $Matches[1]
|
||||
Write-Host "Found metapackage: $MetaPackageName version $metaVersion"
|
||||
} else {
|
||||
Write-Warning "Could not extract version from $($metaNupkg.Name)"
|
||||
return @{}
|
||||
}
|
||||
|
||||
# Parse dependencies from metapackage
|
||||
$dependencies = Get-NuspecDependencies -NupkgPath $metaNupkg.FullName
|
||||
|
||||
# Copy metapackage to output directory
|
||||
Copy-Item $metaNupkg.FullName -Destination $OutputDir -Force
|
||||
Write-Host "Copied metapackage to $OutputDir"
|
||||
|
||||
# Prepare package versions hashtable - initialize with metapackage version
|
||||
$packageVersions = @{ $MetaPackageName = $metaVersion }
|
||||
|
||||
# Copy Runtime package from artifact (it's not in feed) and extract its version
|
||||
$runtimeNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.Runtime.*.nupkg" | Select-Object -First 1
|
||||
if ($runtimeNupkg) {
|
||||
Copy-Item $runtimeNupkg.FullName -Destination $OutputDir -Force
|
||||
Write-Host "Copied Runtime package to $OutputDir"
|
||||
|
||||
# Extract version from Runtime package filename
|
||||
if ($runtimeNupkg.Name -match "$MetaPackageName\.Runtime\.(.+)\.nupkg") {
|
||||
$runtimeVersion = $Matches[1]
|
||||
$packageVersions["$MetaPackageName.Runtime"] = $runtimeVersion
|
||||
Write-Host "Extracted Runtime package version: $runtimeVersion"
|
||||
} else {
|
||||
Write-Warning "Could not extract version from Runtime package: $($runtimeNupkg.Name)"
|
||||
}
|
||||
}
|
||||
|
||||
# Download other dependencies from feed (excluding Runtime as it's already copied)
|
||||
# Create temp nuget.config that includes both local packages and remote feed
|
||||
# This allows NuGet to find packages already copied from artifact
|
||||
$tempConfig = Join-Path $env:TEMP "nuget_artifact_$(Get-Random).config"
|
||||
$tempConfigContent = @"
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key='LocalPackages' value='$OutputDir' />
|
||||
<add key='RemoteFeed' value='$SourceUrl' />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
"@
|
||||
Set-Content -Path $tempConfig -Value $tempConfigContent
|
||||
|
||||
try {
|
||||
foreach ($depId in $dependencies.Keys) {
|
||||
# Skip Runtime as it's already copied from artifact
|
||||
if ($depId -like "*Runtime*") {
|
||||
# Don't overwrite the version we extracted from the Runtime package filename
|
||||
if (-not $packageVersions.ContainsKey($depId)) {
|
||||
$packageVersions[$depId] = $dependencies[$depId]
|
||||
}
|
||||
Write-Host "Skipping $depId (already in artifact)"
|
||||
continue
|
||||
}
|
||||
|
||||
$depVersion = $dependencies[$depId]
|
||||
Write-Host "Downloading dependency: $depId version $depVersion from feed..."
|
||||
|
||||
& nuget install $depId `
|
||||
-Version $depVersion `
|
||||
-ConfigFile $tempConfig `
|
||||
-OutputDirectory $OutputDir `
|
||||
-NonInteractive `
|
||||
-NoCache `
|
||||
| Out-Null
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$packageVersions[$depId] = $depVersion
|
||||
Write-Host " Successfully downloaded $depId"
|
||||
} else {
|
||||
Write-Warning " Failed to download $depId version $depVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Parse all downloaded packages to get actual versions
|
||||
$directories = Get-ChildItem -Path $OutputDir -Directory
|
||||
$allLocalPackages = @()
|
||||
|
||||
# Add metapackage and runtime to the list (they are .nupkg files, not directories)
|
||||
$allLocalPackages += $MetaPackageName
|
||||
if ($packageVersions.ContainsKey("$MetaPackageName.Runtime")) {
|
||||
$allLocalPackages += "$MetaPackageName.Runtime"
|
||||
}
|
||||
|
||||
foreach ($dir in $directories) {
|
||||
if ($dir.Name -match $script:PackageVersionRegex) {
|
||||
$pkgId = $Matches[1]
|
||||
$pkgVer = $Matches[2]
|
||||
$allLocalPackages += $pkgId
|
||||
if (-not $packageVersions.ContainsKey($pkgId)) {
|
||||
$packageVersions[$pkgId] = $pkgVer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Update nuget.config dynamically during pipeline execution
|
||||
# This modification is temporary and won't be committed back to the repo
|
||||
$nugetConfig = Join-Path $rootPath "nuget.config"
|
||||
$configData = Read-FileWithEncoding -Path $nugetConfig
|
||||
[xml]$xml = $configData.Content
|
||||
|
||||
Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $OutputDir -Patterns $allLocalPackages
|
||||
|
||||
$xml.Save($nugetConfig)
|
||||
Write-Host "Updated nuget.config with localpackages mapping (temporary, for pipeline execution only)."
|
||||
|
||||
return ,$packageVersions
|
||||
}
|
||||
|
||||
function Resolve-WinAppSdkSplitDependencies {
|
||||
Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..."
|
||||
$installDir = Join-Path $rootPath "localpackages\output"
|
||||
$null = New-Item -ItemType Directory -Path $installDir -Force
|
||||
New-Item -ItemType Directory -Path $installDir -Force | Out-Null
|
||||
|
||||
# Create a temporary nuget.config to avoid interference from the repo's config
|
||||
$tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config"
|
||||
@@ -417,24 +131,14 @@ function Resolve-WinAppSdkSplitDependencies {
|
||||
if ($propsContent -match '<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="([^"]+)"') {
|
||||
$buildToolsVersion = $Matches[1]
|
||||
Write-Host "Downloading Microsoft.Windows.SDK.BuildTools version $buildToolsVersion..."
|
||||
& nuget install Microsoft.Windows.SDK.BuildTools `
|
||||
-Version $buildToolsVersion `
|
||||
-ConfigFile $tempConfig `
|
||||
-OutputDirectory $installDir `
|
||||
-NonInteractive `
|
||||
-NoCache `
|
||||
| Out-Null
|
||||
$nugetArgsBuildTools = "install Microsoft.Windows.SDK.BuildTools -Version $buildToolsVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
|
||||
Invoke-Expression "nuget $nugetArgsBuildTools" | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# Download package to inspect nuspec and keep it for the build
|
||||
& nuget install Microsoft.WindowsAppSDK `
|
||||
-Version $WinAppSDKVersion `
|
||||
-ConfigFile $tempConfig `
|
||||
-OutputDirectory $installDir `
|
||||
-NonInteractive `
|
||||
-NoCache `
|
||||
| Out-Null
|
||||
$nugetArgs = "install Microsoft.WindowsAppSDK -Version $WinAppSDKVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
|
||||
Invoke-Expression "nuget $nugetArgs" | Out-Null
|
||||
|
||||
# Parse dependencies from the installed folders
|
||||
# Folder structure is typically {PackageId}.{Version}
|
||||
@@ -468,101 +172,52 @@ function Resolve-WinAppSdkSplitDependencies {
|
||||
}
|
||||
}
|
||||
|
||||
# Main logic: choose between artifact-based or feed-based approach
|
||||
if ($useArtifactSource) {
|
||||
Write-Host "=== Using Artifact-Based Source ===" -ForegroundColor Cyan
|
||||
Write-Host "Organization: $azureDevOpsOrg"
|
||||
Write-Host "Project: $azureDevOpsProject"
|
||||
Write-Host "Build ID: $buildId"
|
||||
Write-Host "Artifact: $artifactName"
|
||||
|
||||
if ([string]::IsNullOrEmpty($buildId) -or $buildId -eq 'N/A') {
|
||||
Write-Error "buildId parameter is required when using artifact source. Please provide a valid Windows App SDK Build ID."
|
||||
Write-Host "Tip: You can find the build ID from the Windows App SDK pipeline run in Azure DevOps."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Download artifact
|
||||
$artifactDir = Join-Path $rootPath "localpackages\artifact"
|
||||
$downloadSuccess = Download-ArtifactFromPipeline `
|
||||
-Organization $azureDevOpsOrg `
|
||||
-Project $azureDevOpsProject `
|
||||
-BuildId $buildId `
|
||||
-ArtifactName $artifactName `
|
||||
-OutputDir $artifactDir
|
||||
|
||||
if (-not $downloadSuccess) {
|
||||
Write-Host "Failed to download artifact"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve dependencies from artifact
|
||||
$installDir = Join-Path $rootPath "localpackages\output"
|
||||
$packageVersions = Resolve-ArtifactBasedDependencies `
|
||||
-ArtifactDir $artifactDir `
|
||||
-MetaPackageName $metaPackageName `
|
||||
-SourceUrl $sourceLink `
|
||||
-OutputDir $installDir
|
||||
|
||||
if ($packageVersions.Count -eq 0) {
|
||||
Write-Error "Failed to resolve dependencies from artifact"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$WinAppSDKVersion = $packageVersions[$metaPackageName]
|
||||
Write-Host "WinAppSDK Version: $WinAppSDKVersion"
|
||||
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
|
||||
|
||||
# Execute nuget list and capture the output
|
||||
if ($useExperimentalVersion) {
|
||||
# The nuget list for experimental versions will cost more time
|
||||
# So, we will not use -AllVersions to wast time
|
||||
# But it can only get the latest experimental version
|
||||
Write-Host "Fetching WindowsAppSDK with experimental versions"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-Prerelease
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions
|
||||
} else {
|
||||
Write-Host "=== Using Feed-Based Source ===" -ForegroundColor Cyan
|
||||
|
||||
# Execute nuget list and capture the output
|
||||
if ($useExperimentalVersion) {
|
||||
# The nuget list for experimental versions will cost more time
|
||||
# So, we will not use -AllVersions to wast time
|
||||
# But it can only get the latest experimental version
|
||||
Write-Host "Fetching WindowsAppSDK with experimental versions"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-Prerelease
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions
|
||||
} else {
|
||||
Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-AllVersions
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
|
||||
}
|
||||
|
||||
Write-Host "Latest versions found: $latestVersions"
|
||||
# Extract the latest version number from the output
|
||||
$latestVersion = $latestVersions -split "`n" | `
|
||||
Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
|
||||
ForEach-Object { $_.Matches[0].Groups[1].Value } | `
|
||||
Sort-Object -Descending | `
|
||||
Select-Object -First 1
|
||||
|
||||
if ($latestVersion) {
|
||||
$WinAppSDKVersion = $latestVersion
|
||||
Write-Host "Extracted version: $WinAppSDKVersion"
|
||||
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
|
||||
} else {
|
||||
Write-Host "Failed to extract version number from nuget list output"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve dependencies for 1.8+
|
||||
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
|
||||
|
||||
Resolve-WinAppSdkSplitDependencies
|
||||
Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
|
||||
$nugetOutput = nuget list Microsoft.WindowsAppSDK `
|
||||
-Source $sourceLink `
|
||||
-AllVersions
|
||||
# Filter versions based on the specified version prefix
|
||||
$escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
|
||||
$filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
|
||||
$latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
|
||||
}
|
||||
|
||||
Write-Host "Latest versions found: $latestVersions"
|
||||
# Extract the latest version number from the output
|
||||
$latestVersion = $latestVersions -split "`n" | `
|
||||
Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
|
||||
ForEach-Object { $_.Matches[0].Groups[1].Value } | `
|
||||
Sort-Object -Descending | `
|
||||
Select-Object -First 1
|
||||
|
||||
if ($latestVersion) {
|
||||
$WinAppSDKVersion = $latestVersion
|
||||
Write-Host "Extracted version: $WinAppSDKVersion"
|
||||
Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
|
||||
} else {
|
||||
Write-Host "Failed to extract version number from nuget list output"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Resolve dependencies for 1.8+
|
||||
$packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
|
||||
|
||||
Resolve-WinAppSdkSplitDependencies
|
||||
|
||||
# Update Directory.Packages.props file
|
||||
Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object {
|
||||
$file = Read-FileWithEncoding -Path $_.FullName
|
||||
@@ -571,16 +226,9 @@ Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Obje
|
||||
|
||||
foreach ($pkgId in $packageVersions.Keys) {
|
||||
$ver = $packageVersions[$pkgId]
|
||||
|
||||
# Skip packages with empty versions to prevent corruption
|
||||
if ([string]::IsNullOrWhiteSpace($ver)) {
|
||||
Write-Warning "Skipping ${pkgId}: version is empty"
|
||||
continue
|
||||
}
|
||||
|
||||
# Escape dots in package ID for regex
|
||||
$pkgIdRegex = $pkgId -replace '\.', '\.'
|
||||
|
||||
|
||||
$newVersionString = "<PackageVersion Include=""$pkgId"" Version=""$ver"" />"
|
||||
$oldVersionString = "<PackageVersion Include=""$pkgIdRegex"" Version=""[-.0-9a-zA-Z]*"" />"
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ schedules:
|
||||
always: false # only run if there's code changes!
|
||||
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
vmImage: windows-2019
|
||||
|
||||
resources:
|
||||
repositories:
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
# NOTE: When using artifact mode (useArtifactSource: true), the pipeline needs
|
||||
# permission to access System.AccessToken. This is automatically handled by the
|
||||
# script if SYSTEM_ACCESSTOKEN environment variable is available.
|
||||
# If you encounter authentication errors, ensure the job has oauth access enabled.
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
@@ -42,23 +37,6 @@ parameters:
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters (optional)
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
displayName: "Use Artifact Source (instead of feed)"
|
||||
default: false
|
||||
- name: buildId
|
||||
type: string
|
||||
displayName: "Windows App SDK Build ID (required only if using artifact source)"
|
||||
default: 'N/A'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
displayName: "Source Project (for artifact mode, default: ProjectReunion)"
|
||||
default: 'ProjectReunion'
|
||||
- name: artifactName
|
||||
type: string
|
||||
displayName: "Artifact Name (for artifact mode, default: WindowsAppSDK_Nuget_And_MSIX)"
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
|
||||
extends:
|
||||
template: templates/pipeline-ci-build.yml
|
||||
@@ -71,7 +49,3 @@ extends:
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useArtifactSource: ${{ parameters.useArtifactSource }}
|
||||
buildId: ${{ parameters.buildId }}
|
||||
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
|
||||
@@ -16,7 +16,6 @@ pr:
|
||||
include:
|
||||
- main
|
||||
- stable
|
||||
drafts: false
|
||||
# paths:
|
||||
# exclude:
|
||||
# - '**.md'
|
||||
|
||||
@@ -35,9 +35,7 @@ stages:
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Preview
|
||||
${{ else }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ parameters.platform }}
|
||||
buildConfigurations: [Release]
|
||||
|
||||
@@ -51,9 +51,7 @@ extends:
|
||||
pool:
|
||||
name: SHINE-INT-S
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Preview
|
||||
${{ else }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
os: windows
|
||||
sdl:
|
||||
tsa:
|
||||
@@ -76,9 +74,7 @@ extends:
|
||||
demands:
|
||||
# Our INT agents have a large disk mounted at P:\
|
||||
- ${{ if eq(parameters.useVSPreview, true) }}:
|
||||
- ImageOverride -equals SHINE-VS18-Latest-Preview
|
||||
- ${{ else }}:
|
||||
- ImageOverride -equals SHINE-VS18-Latest
|
||||
- ImageOverride -equals SHINE-VS17-Preview
|
||||
os: windows
|
||||
variables:
|
||||
IsPipeline: 1 # The installer uses this to detect whether it should pick up localizations
|
||||
@@ -91,7 +87,6 @@ extends:
|
||||
official: true
|
||||
codeSign: true
|
||||
runTests: false
|
||||
buildTests: false
|
||||
signingIdentity:
|
||||
serviceName: $(SigningServiceName)
|
||||
appId: $(SigningAppId)
|
||||
|
||||
@@ -70,29 +70,10 @@ parameters:
|
||||
default: false
|
||||
- name: winAppSDKVersionNumber
|
||||
type: string
|
||||
default: '2.0'
|
||||
default: 1.6
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
default: false
|
||||
- name: azureDevOpsOrg
|
||||
type: string
|
||||
default: 'https://dev.azure.com/microsoft'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
default: 'ProjectReunion'
|
||||
- name: buildId
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactName
|
||||
type: string
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
- name: metaPackageName
|
||||
type: string
|
||||
default: 'Microsoft.WindowsAppSDK'
|
||||
- name: csProjectsToPublish
|
||||
type: object
|
||||
default:
|
||||
@@ -200,7 +181,7 @@ jobs:
|
||||
- template: steps-ensure-dotnet-version.yml
|
||||
parameters:
|
||||
sdk: true
|
||||
version: '10.0'
|
||||
version: '9.0'
|
||||
|
||||
- ${{ if eq(parameters.runTests, true) }}:
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
@@ -210,9 +191,6 @@ jobs:
|
||||
& '.pipelines/applyXamlStyling.ps1' -Passive
|
||||
displayName: Verify XAML formatting
|
||||
|
||||
- task: NuGetAuthenticate@1
|
||||
displayName: Authenticate NuGet feeds for verification
|
||||
|
||||
- pwsh: |-
|
||||
& '.pipelines/verifyNugetPackages.ps1' -solution '$(build.sourcesdirectory)\PowerToys.slnx'
|
||||
displayName: Verify Nuget package versions for PowerToys.slnx
|
||||
@@ -248,12 +226,6 @@ jobs:
|
||||
parameters:
|
||||
versionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useArtifactSource: ${{ parameters.useArtifactSource }}
|
||||
azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
|
||||
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
|
||||
buildId: ${{ parameters.buildId }}
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
metaPackageName: ${{ parameters.metaPackageName }}
|
||||
|
||||
- ${{ if eq(parameters.useLatestWinAppSDK, false)}}:
|
||||
- template: .\steps-restore-nuget.yml
|
||||
@@ -281,12 +253,11 @@ jobs:
|
||||
displayName: Build PowerToys main project
|
||||
inputs:
|
||||
solution: 'PowerToys.slnx'
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore -graph
|
||||
/p:RestorePackagesConfig=true
|
||||
/p:CIBuild=true
|
||||
/p:BuildTests=${{ parameters.buildTests }}
|
||||
/bl:$(LogOutputDirectory)\build-0-main.binlog
|
||||
${{ parameters.additionalBuildOptions }}
|
||||
$(MSBuildCacheParameters)
|
||||
@@ -305,7 +276,7 @@ jobs:
|
||||
condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
|
||||
inputs:
|
||||
solution: PowerToys.slnx
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
/p:Configuration=$(BuildConfiguration)
|
||||
@@ -367,7 +338,7 @@ jobs:
|
||||
displayName: Build BugReportTool
|
||||
inputs:
|
||||
solution: '**/tools/BugReportTool/BugReportTool.sln'
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore -graph
|
||||
/p:RestorePackagesConfig=true
|
||||
@@ -388,7 +359,7 @@ jobs:
|
||||
displayName: Build StylesReportTool
|
||||
inputs:
|
||||
solution: '**/tools/StylesReportTool/StylesReportTool.sln'
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore -graph
|
||||
/p:RestorePackagesConfig=true
|
||||
@@ -410,7 +381,7 @@ jobs:
|
||||
displayName: Publish ${{ project }} for Packaging
|
||||
inputs:
|
||||
solution: ${{ project }}
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/target:Publish
|
||||
/graph
|
||||
@@ -418,7 +389,7 @@ jobs:
|
||||
/p:VCRTForwarders-IncludeDebugCRT=false
|
||||
/p:PowerToysRoot=$(Build.SourcesDirectory)
|
||||
/p:PublishProfile=InstallationPublishProfile.pubxml
|
||||
/p:TargetFramework=net10.0-windows10.0.26100.0
|
||||
/p:TargetFramework=net9.0-windows10.0.26100.0
|
||||
/bl:$(LogOutputDirectory)\publish-${{ join('_',split(project, '/')) }}.binlog
|
||||
$(RestoreAdditionalProjectSourcesArg)
|
||||
platform: $(BuildPlatform)
|
||||
@@ -533,14 +504,6 @@ jobs:
|
||||
Remove-Item -Force -Recurse "$(JobOutputDirectory)/_appx" -ErrorAction:Ignore
|
||||
displayName: Re-pack the new CmdPal package after signing
|
||||
|
||||
- pwsh: |
|
||||
$testsPath = "$(Build.SourcesDirectory)/$(BuildPlatform)/$(BuildConfiguration)/tests"
|
||||
if (Test-Path $testsPath) {
|
||||
Remove-Item -Path $testsPath -Recurse -Force
|
||||
Write-Host "Removed tests folder to reduce signing workload: $testsPath"
|
||||
}
|
||||
displayName: Remove tests folder before signing
|
||||
|
||||
- template: steps-esrp-signing.yml
|
||||
parameters:
|
||||
displayName: Sign Core PowerToys
|
||||
|
||||
@@ -64,17 +64,18 @@ jobs:
|
||||
- template: steps-ensure-dotnet-version.yml
|
||||
parameters:
|
||||
sdk: true
|
||||
version: '10.0'
|
||||
version: '9.0'
|
||||
|
||||
- template: .\steps-restore-nuget.yml
|
||||
|
||||
- task: MSBuild@1
|
||||
- task: NuGetCommand@2
|
||||
displayName: Restore solution-level NuGet packages
|
||||
inputs:
|
||||
solution: PowerToys.slnx
|
||||
msbuildArguments: '/t:restore /p:RestorePackagesConfig=true'
|
||||
platform: $(BuildPlatform)
|
||||
configuration: $(BuildConfiguration)
|
||||
command: restore
|
||||
feedsToUse: config
|
||||
configPath: nuget.config
|
||||
restoreSolution: PowerToys.slnx
|
||||
restoreDirectory: '$(Build.SourcesDirectory)\packages'
|
||||
|
||||
# Build all UI test projects if no specific modules are specified
|
||||
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
|
||||
@@ -82,7 +83,7 @@ jobs:
|
||||
displayName: Build UI Test Projects
|
||||
inputs:
|
||||
solution: '**/*UITest*.csproj'
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
-graph
|
||||
@@ -103,7 +104,7 @@ jobs:
|
||||
displayName: 'Build UI Test Module: ${{ module }}'
|
||||
inputs:
|
||||
solution: '**/*${{ module }}*.csproj'
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
-graph
|
||||
|
||||
@@ -108,6 +108,9 @@ jobs:
|
||||
sdk: true
|
||||
version: '9.0'
|
||||
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
displayName: Ensure VSTest Platform
|
||||
|
||||
- pwsh: |-
|
||||
& '$(build.sourcesdirectory)\.pipelines\InstallWinAppDriver.ps1'
|
||||
displayName: Download and install WinAppDriver
|
||||
@@ -149,7 +152,46 @@ jobs:
|
||||
inputs:
|
||||
displaySettings: 'optimal'
|
||||
|
||||
- script: |
|
||||
dotnet test $(Build.SourcesDirectory)\src\modules\fancyzones\FancyZones.UITests\FancyZones.UITests.csproj --no-build -c $(BuildConfiguration) -p:Platform=$(BuildPlatform)
|
||||
dotnet test $(Build.SourcesDirectory)\src\modules\fancyzones\FancyZonesEditor.UITests\FancyZonesEditor.UITests.csproj --no-build -c $(BuildConfiguration) -p:Platform=$(BuildPlatform)
|
||||
displayName: "Run UI Tests"
|
||||
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
|
||||
- task: VSTest@3
|
||||
displayName: Run UI Tests
|
||||
inputs:
|
||||
platform: '$(BuildPlatform)'
|
||||
configuration: '$(BuildConfiguration)'
|
||||
testSelector: 'testAssemblies'
|
||||
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
|
||||
vsTestVersion: 'toolsInstaller'
|
||||
uiTests: true
|
||||
rerunFailedTests: true
|
||||
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
|
||||
# Since UITests-FancyZonesEditor.dll is generated in both UITests-FancyZonesEditor and UITests-FancyZones, removed one to avoid duplicate test runs
|
||||
testAssemblyVer2: |
|
||||
**\*UITest*.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
|
||||
|
||||
- ${{ if ne(length(parameters.uiTestModules), 0) }}:
|
||||
- ${{ each module in parameters.uiTestModules }}:
|
||||
- task: VSTest@3
|
||||
displayName: Run UI Test - ${{ module }}
|
||||
inputs:
|
||||
platform: '$(BuildPlatform)'
|
||||
configuration: '$(BuildConfiguration)'
|
||||
testSelector: 'testAssemblies'
|
||||
searchFolder: '$(Pipeline.Workspace)\$(TestArtifactsName)'
|
||||
vsTestVersion: 'toolsInstaller'
|
||||
uiTests: true
|
||||
rerunFailedTests: true
|
||||
testRunTitle: 'UITests_${{ parameters.platform }}_${{ parameters.installMode }}'
|
||||
testAssemblyVer2: |
|
||||
**\*${{ module }}*.dll
|
||||
!**\obj\**
|
||||
!**\ref\**
|
||||
!**\UITests-FancyZones\**\UITests-FancyZonesEditor.dll
|
||||
env:
|
||||
platform: '$(TestPlatform)'
|
||||
useInstallerForTest: ${{ ne(parameters.buildSource, 'buildNow') }}
|
||||
|
||||
@@ -30,29 +30,10 @@ parameters:
|
||||
default: false
|
||||
- name: winAppSDKVersionNumber
|
||||
type: string
|
||||
default: '2.0'
|
||||
default: 1.6
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
default: false
|
||||
- name: azureDevOpsOrg
|
||||
type: string
|
||||
default: 'https://dev.azure.com/microsoft'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
default: 'ProjectReunion'
|
||||
- name: buildId
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactName
|
||||
type: string
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
- name: metaPackageName
|
||||
type: string
|
||||
default: 'Microsoft.WindowsAppSDK'
|
||||
|
||||
stages:
|
||||
- ${{ each platform in parameters.buildPlatforms }}:
|
||||
@@ -68,9 +49,7 @@ stages:
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Preview
|
||||
${{ else }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ platform }}
|
||||
buildConfigurations: [Release]
|
||||
@@ -78,18 +57,11 @@ stages:
|
||||
enableMsBuildCaching: ${{ parameters.enableMsBuildCaching }}
|
||||
msBuildCacheIsReadOnly: ${{ parameters.msBuildCacheIsReadOnly }}
|
||||
runTests: ${{ parameters.runTests }}
|
||||
buildTests: true
|
||||
useVSPreview: ${{ parameters.useVSPreview }}
|
||||
useLatestWinAppSDK: ${{ parameters.useLatestWinAppSDK }}
|
||||
${{ if eq(parameters.useLatestWinAppSDK, true) }}:
|
||||
winAppSDKVersionNumber: ${{ parameters.winAppSDKVersionNumber }}
|
||||
useExperimentalVersion: ${{ parameters.useExperimentalVersion }}
|
||||
useArtifactSource: ${{ parameters.useArtifactSource }}
|
||||
azureDevOpsOrg: ${{ parameters.azureDevOpsOrg }}
|
||||
azureDevOpsProject: ${{ parameters.azureDevOpsProject }}
|
||||
buildId: ${{ parameters.buildId }}
|
||||
artifactName: ${{ parameters.artifactName }}
|
||||
metaPackageName: ${{ parameters.metaPackageName }}
|
||||
timeoutInMinutes: 90
|
||||
|
||||
- stage: Build_SDK
|
||||
@@ -104,9 +76,7 @@ stages:
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Preview
|
||||
${{ else }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildConfigurations: [Release]
|
||||
official: false
|
||||
codeSign: false
|
||||
|
||||
@@ -29,9 +29,7 @@ stages:
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
${{ if eq(parameters.useVSPreview, true) }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Preview
|
||||
${{ else }}:
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||
buildPlatforms:
|
||||
- ${{ parameters.platform }}
|
||||
buildConfigurations: [Release]
|
||||
|
||||
@@ -27,7 +27,6 @@ stages:
|
||||
name: SHINE-INT-L
|
||||
${{ else }}:
|
||||
name: SHINE-OSS-L
|
||||
demands: ImageOverride -equals SHINE-VS18-Latest
|
||||
buildPlatforms:
|
||||
- ${{ parameters.platform }}
|
||||
uiTestModules: ${{ parameters.uiTestModules }}
|
||||
|
||||
@@ -36,7 +36,7 @@ steps:
|
||||
displayName: Build Shared Support DLLs
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:PowerToysSetupCustomActionsVNext;SilentFilesInUseBAFunction
|
||||
/p:RunBuildEvents=true;RestorePackagesConfig=true;CIBuild=true
|
||||
@@ -75,7 +75,7 @@ steps:
|
||||
displayName: 💻 Build VNext MSI
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
/t:PowerToysInstallerVNext
|
||||
@@ -92,7 +92,7 @@ steps:
|
||||
displayName: 👤 Build VNext MSI
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:PowerToysInstallerVNext
|
||||
/p:RunBuildEvents=false;PerUser=true;BuildProjectReferences=false;CIBuild=true
|
||||
@@ -143,7 +143,7 @@ steps:
|
||||
displayName: 💻 Build VNext Bootstrapper
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
-restore
|
||||
/t:PowerToysBootstrapperVNext
|
||||
@@ -160,7 +160,7 @@ steps:
|
||||
displayName: 👤 Build VNext Bootstrapper
|
||||
inputs:
|
||||
solution: "**/installer/PowerToysSetup.slnx"
|
||||
vsVersion: 18.0
|
||||
vsVersion: 17.0
|
||||
msbuildArgs: >-
|
||||
/t:PowerToysBootstrapperVNext
|
||||
/p:PerUser=true;BuildProjectReferences=false;CIBuild=true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
parameters:
|
||||
- name: version
|
||||
type: string
|
||||
default: "10.0"
|
||||
default: "9.0"
|
||||
- name: sdk
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -10,7 +10,7 @@ parameters:
|
||||
default: {}
|
||||
|
||||
steps:
|
||||
- task: EsrpCodeSigning@6
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: 🔏 ${{ parameters.displayName }}
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
parameters:
|
||||
- name: versionNumber
|
||||
type: string
|
||||
default: '2.0'
|
||||
default: 1.6
|
||||
- name: useExperimentalVersion
|
||||
type: boolean
|
||||
default: false
|
||||
# Artifact mode parameters
|
||||
- name: useArtifactSource
|
||||
type: boolean
|
||||
default: false
|
||||
- name: azureDevOpsOrg
|
||||
type: string
|
||||
default: 'https://dev.azure.com/microsoft'
|
||||
- name: azureDevOpsProject
|
||||
type: string
|
||||
default: 'ProjectReunion'
|
||||
- name: buildId
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactName
|
||||
type: string
|
||||
default: 'WindowsAppSDK_Nuget_And_MSIX'
|
||||
- name: metaPackageName
|
||||
type: string
|
||||
default: 'Microsoft.WindowsAppSDK'
|
||||
|
||||
steps:
|
||||
- task: NuGetAuthenticate@1
|
||||
@@ -31,20 +12,12 @@ steps:
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: Update WinAppSDK Versions
|
||||
env:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
inputs:
|
||||
filePath: '$(build.sourcesdirectory)\.pipelines\UpdateVersions.ps1'
|
||||
arguments: >
|
||||
-winAppSdkVersionNumber ${{ parameters.versionNumber }}
|
||||
-useExperimentalVersion $${{ parameters.useExperimentalVersion }}
|
||||
-rootPath "$(build.sourcesdirectory)"
|
||||
-useArtifactSource $${{ parameters.useArtifactSource }}
|
||||
-azureDevOpsOrg "${{ parameters.azureDevOpsOrg }}"
|
||||
-azureDevOpsProject "${{ parameters.azureDevOpsProject }}"
|
||||
-buildId "${{ parameters.buildId }}"
|
||||
-artifactName "${{ parameters.artifactName }}"
|
||||
-metaPackageName "${{ parameters.metaPackageName }}"
|
||||
|
||||
# - task: NuGetCommand@2
|
||||
# displayName: 'Restore NuGet packages (slnx)'
|
||||
@@ -63,4 +36,3 @@ steps:
|
||||
feedsToUse: 'config'
|
||||
nugetConfigPath: '$(build.sourcesdirectory)\nuget.config'
|
||||
workingDirectory: '$(build.sourcesdirectory)'
|
||||
arguments: '/p:NoWarn=NU1602,NU1604'
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
# Build common vswhere base arguments
|
||||
$vsWhereBaseArgs = @('-latest', '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64')
|
||||
if ($env:VCWhereExtraVersionTarget) {
|
||||
# Add version target if specified (e.g., '-version [18.0,19.0)' for VS2026)
|
||||
$vsWhereBaseArgs += $env:VCWhereExtraVersionTarget.Split(' ')
|
||||
}
|
||||
|
||||
$VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vsWhereBaseArgs -include packages -format xml))
|
||||
$VSInstances = ([xml](& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -include packages -format xml))
|
||||
$VSPackages = $VSInstances.instances.instance.packages.package
|
||||
$LatestVCPackage = ($VSPackages | ? { $_.id -eq "Microsoft.VisualCpp.Tools.Core" })
|
||||
$LatestVCToolsVersion = $LatestVCPackage.version;
|
||||
|
||||
$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' @vsWhereBaseArgs -property 'resolvedInstallationPath')
|
||||
$VSRoot = (& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property 'resolvedInstallationPath')
|
||||
$VCToolsRoot = Join-Path $VSRoot "VC\Tools\MSVC"
|
||||
|
||||
# We have observed a few instances where the VC tools package version actually
|
||||
@@ -31,12 +24,5 @@ If ($Null -Eq (Get-Item $PackageVCToolPath -ErrorAction:Ignore)) {
|
||||
}
|
||||
|
||||
Write-Output "Latest VCToolsVersion: $LatestVCToolsVersion"
|
||||
|
||||
# VS2026 (MSVC 14.50+) doesn't need explicit VCToolsVersion - let MSBuild auto-select
|
||||
$MajorMinorVersion = [Version]::Parse($LatestVCToolsVersion)
|
||||
If ($MajorMinorVersion.Major -eq 14 -and $MajorMinorVersion.Minor -ge 50) {
|
||||
Write-Output "VS2026 detected (MSVC 14.50+). Skipping VCToolsVersion override to allow MSBuild auto-selection."
|
||||
} Else {
|
||||
Write-Output "Updating VCToolsVersion environment variable for job"
|
||||
Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion"
|
||||
}
|
||||
Write-Output "Updating VCToolsVersion environment variable for job"
|
||||
Write-Output "##vso[task.setvariable variable=VCToolsVersion]$LatestVCToolsVersion"
|
||||
|
||||
@@ -22,7 +22,7 @@ $totalList = $projFiles | ForEach-Object -Parallel {
|
||||
#Workaround for preventing exit code from dotnet process from reflecting exit code in PowerShell
|
||||
$procInfo = New-Object System.Diagnostics.ProcessStartInfo -Property @{
|
||||
FileName = "dotnet.exe";
|
||||
Arguments = "list $csproj package --no-restore";
|
||||
Arguments = "list $csproj package";
|
||||
RedirectStandardOutput = $true;
|
||||
RedirectStandardError = $true;
|
||||
}
|
||||
@@ -90,16 +90,9 @@ if ($noticeMatch.Success) {
|
||||
$currentNoticePackageList = ""
|
||||
}
|
||||
|
||||
# Test-only packages that are allowed to be in NOTICE.md but not in the build
|
||||
# (e.g., when BuildTests=false, these packages won't appear in the NuGet list)
|
||||
$allowedExtraPackages = @(
|
||||
"- Moq",
|
||||
"- MSTest"
|
||||
)
|
||||
|
||||
if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
{
|
||||
Write-Host -ForegroundColor Yellow "Notice.md does not exactly match NuGet list. Analyzing differences..."
|
||||
Write-Host -ForegroundColor Red "Notice.md does not match NuGet list."
|
||||
|
||||
# Show detailed differences
|
||||
$generatedPackages = $returnList -split "`r`n|`n" | Where-Object { $_.Trim() -ne "" } | Sort-Object
|
||||
@@ -112,7 +105,7 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
# Find packages in proj file list but not in NOTICE.md
|
||||
$missingFromNotice = $generatedPackages | Where-Object { $noticePackages -notcontains $_ }
|
||||
if ($missingFromNotice.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "MissingFromNotice (ERROR - these must be added to NOTICE.md):"
|
||||
Write-Host -ForegroundColor Red "MissingFromNotice:"
|
||||
foreach ($pkg in $missingFromNotice) {
|
||||
Write-Host -ForegroundColor Red " $pkg"
|
||||
}
|
||||
@@ -121,23 +114,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
|
||||
# Find packages in NOTICE.md but not in proj file list
|
||||
$extraInNotice = $noticePackages | Where-Object { $generatedPackages -notcontains $_ }
|
||||
|
||||
# Filter out allowed extra packages (test-only dependencies)
|
||||
$unexpectedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -notcontains $_ }
|
||||
$allowedExtra = $extraInNotice | Where-Object { $allowedExtraPackages -contains $_ }
|
||||
|
||||
if ($allowedExtra.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Green "ExtraInNotice (OK - allowed test-only packages):"
|
||||
foreach ($pkg in $allowedExtra) {
|
||||
Write-Host -ForegroundColor Green " $pkg"
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($unexpectedExtra.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ExtraInNotice (ERROR - unexpected packages in NOTICE.md):"
|
||||
foreach ($pkg in $unexpectedExtra) {
|
||||
Write-Host -ForegroundColor Red " $pkg"
|
||||
if ($extraInNotice.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Yellow "ExtraInNotice:"
|
||||
foreach ($pkg in $extraInNotice) {
|
||||
Write-Host -ForegroundColor Yellow " $pkg"
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
@@ -147,17 +127,10 @@ if (!$noticeFile.Trim().EndsWith($returnList.Trim()))
|
||||
Write-Host " Proj file list has $($generatedPackages.Count) packages"
|
||||
Write-Host " NOTICE.md has $($noticePackages.Count) packages"
|
||||
Write-Host " MissingFromNotice: $($missingFromNotice.Count) packages"
|
||||
Write-Host " ExtraInNotice (allowed): $($allowedExtra.Count) packages"
|
||||
Write-Host " ExtraInNotice (unexpected): $($unexpectedExtra.Count) packages"
|
||||
Write-Host " ExtraInNotice: $($extraInNotice.Count) packages"
|
||||
Write-Host ""
|
||||
|
||||
# Fail if there are missing packages OR unexpected extra packages
|
||||
if ($missingFromNotice.Count -gt 0 -or $unexpectedExtra.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "FAILED: NOTICE.md mismatch detected."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "PASSED: NOTICE.md matches (with allowed test-only packages)."
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -17,10 +17,10 @@ $nonDirectoryAssetsItems = Get-ChildItem $targetAssetsDir -Attributes !Directory
|
||||
$directoryAssetsItems = Get-ChildItem $targetAssetsDir -Attributes Directory
|
||||
|
||||
if ($directoryAssetsItems.Count -le 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: No directories detected in " $nonDirectoryAssetsItems ". Are you sure this is the right path?`r`n"
|
||||
Write-Host -ForegroundColor Red "No directories detected in " $nonDirectoryAssetsItems ". Are you sure this is the right path?`r`n"
|
||||
$totalFailures++;
|
||||
} elseif ($nonDirectoryAssetsItems.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: Detected " $nonDirectoryAssetsItems " files in " $targetAssetsDir ". Each application should use a named subdirectory for assets.`r`n"
|
||||
Write-Host -ForegroundColor Red "Detected " $nonDirectoryAssetsItems " files in " $targetAssetsDir "`r`n"
|
||||
$totalFailures++;
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "Only directories detected in " $targetAssetsDir "`r`n"
|
||||
@@ -29,7 +29,7 @@ if ($directoryAssetsItems.Count -le 0) {
|
||||
# Make sure there's no resources.pri file. Each application should use a different name for their own resources file path.
|
||||
$resourcesPriFiles = Get-ChildItem $targetDir -Filter resources.pri
|
||||
if ($resourcesPriFiles.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: Detected a resources.pri file in " $targetDir ". Each application should use a unique name for its resources file.`r`n"
|
||||
Write-Host -ForegroundColor Red "Detected a resources.pri file in " $targetDir "`r`n"
|
||||
$totalFailures++;
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "No resources.pri file detected in " $targetDir "`r`n"
|
||||
@@ -38,7 +38,7 @@ if ($resourcesPriFiles.Count -gt 0) {
|
||||
# Each application should have their XAML files in their own paths to avoid these conflicts.
|
||||
$resourcesPriFiles = Get-ChildItem $targetDir -Filter *.xbf
|
||||
if ($resourcesPriFiles.Count -gt 0) {
|
||||
Write-Host -ForegroundColor Red "ERROR: Detected a .xbf file in " $targetDir ". Ensure all XAML files are placed in a subdirectory in each application.`r`n"
|
||||
Write-Host -ForegroundColor Red "Detected a .xbf file in " $targetDir "`r`n"
|
||||
$totalFailures++;
|
||||
} else {
|
||||
Write-Host -ForegroundColor Green "No .xbf files detected in " $targetDir "`r`n"
|
||||
|
||||
13
.vscode/mcp.json
vendored
13
.vscode/mcp.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"servers": {
|
||||
"github-artifacts": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"tools/mcp/github-artifacts/launch.js"
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "${env:GITHUB_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
.vscode/settings.json
vendored
18
.vscode/settings.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"github.copilot.chat.reviewSelection.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/review-pr.prompt.md"
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.commitMessageGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/create-commit-title.prompt.md"
|
||||
}
|
||||
],
|
||||
"github.copilot.chat.pullRequestDescriptionGeneration.instructions": [
|
||||
{
|
||||
"file": ".github/prompts/create-pr-summary.prompt.md"
|
||||
}
|
||||
],
|
||||
"sarif-viewer.connectToGithubCodeScanning": "on"
|
||||
}
|
||||
106
.vscode/tasks.json
vendored
106
.vscode/tasks.json
vendored
@@ -1,106 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"windows": {
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "cmd.exe",
|
||||
"args": ["/d", "/c"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"inputs": [
|
||||
{
|
||||
"id": "config",
|
||||
"type": "pickString",
|
||||
"description": "Configuration",
|
||||
"options": ["Debug", "Release"],
|
||||
"default": "Debug"
|
||||
},
|
||||
{
|
||||
"id": "platform",
|
||||
"type": "pickString",
|
||||
"description": "Platform (leave empty to auto-detect host platform)",
|
||||
"options": ["", "X64", "ARM64"],
|
||||
"default": "X64"
|
||||
},
|
||||
{
|
||||
"id": "msbuildExtra",
|
||||
"type": "promptString",
|
||||
"description": "Extra MSBuild args (optional). Example: /p:CIBuild=true /m",
|
||||
"default": ""
|
||||
}
|
||||
],
|
||||
|
||||
"tasks": [
|
||||
{
|
||||
"label": "PT: Build (quick)",
|
||||
"type": "shell",
|
||||
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
|
||||
"args": [
|
||||
"-Path",
|
||||
"${fileDirname}"
|
||||
],
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "PT: Build (with options)",
|
||||
"type": "shell",
|
||||
"command": "\"${workspaceFolder}\\tools\\build\\build.cmd\"",
|
||||
"args": [
|
||||
"-Path",
|
||||
"${fileDirname}",
|
||||
"-Platform",
|
||||
"${input:platform}",
|
||||
"-Configuration",
|
||||
"${input:config}",
|
||||
"${input:msbuildExtra}"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "PT: Build Essentials (quick)",
|
||||
"type": "shell",
|
||||
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
|
||||
"args": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
|
||||
{
|
||||
"label": "PT: Build Essentials (with options)",
|
||||
"type": "shell",
|
||||
"command": "\"${workspaceFolder}\\tools\\build\\build-essentials.cmd\"",
|
||||
"args": [
|
||||
"-Platform",
|
||||
"${input:platform}",
|
||||
"-Configuration",
|
||||
"${input:config}",
|
||||
"${input:msbuildExtra}"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": true
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
172
AGENTS.md
172
AGENTS.md
@@ -1,172 +0,0 @@
|
||||
---
|
||||
description: 'Top-level AI contributor guidance for developing PowerToys - a collection of Windows productivity utilities'
|
||||
applyTo: '**'
|
||||
---
|
||||
|
||||
# PowerToys – AI contributor guide
|
||||
|
||||
This is the top-level guidance for AI contributions to PowerToys. Keep changes atomic, follow existing patterns, and cite exact paths in PRs.
|
||||
|
||||
## Overview
|
||||
|
||||
PowerToys is a set of utilities for power users to tune and streamline their Windows experience.
|
||||
|
||||
| Area | Location | Description |
|
||||
|------|----------|-------------|
|
||||
| Runner | `src/runner/` | Main executable, tray icon, module loader, hotkey management |
|
||||
| Settings UI | `src/settings-ui/` | WinUI/WPF configuration app communicating via named pipes |
|
||||
| Modules | `src/modules/` | Individual PowerToys utilities (each in its own subfolder) |
|
||||
| Common Libraries | `src/common/` | Shared code: logging, IPC, settings, DPI, telemetry, utilities |
|
||||
| Build Tools | `tools/build/` | Build scripts and automation |
|
||||
| Documentation | `doc/devdocs/` | Developer documentation |
|
||||
| Installer | `installer/` | WiX-based installer projects |
|
||||
|
||||
For architecture details and module types, see [Architecture Overview](doc/devdocs/core/architecture.md).
|
||||
|
||||
## Conventions
|
||||
|
||||
For detailed coding conventions, see:
|
||||
|
||||
- [Coding Guidelines](doc/devdocs/development/guidelines.md) – Dependencies, testing, PR management
|
||||
- [Coding Style](doc/devdocs/development/style.md) – Formatting, C++/C#/XAML style rules
|
||||
- [Logging](doc/devdocs/development/logging.md) – C++ spdlog and C# Logger usage
|
||||
|
||||
### Component-specific instructions
|
||||
|
||||
These instruction files are automatically applied when working in their respective areas:
|
||||
|
||||
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md) – IPC contracts, schema migrations
|
||||
- [Common Libraries](.github/instructions/common-libraries.instructions.md) – ABI stability, shared code guidelines
|
||||
|
||||
## Build
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Visual Studio 2022 17.4+ or Visual Studio 2026
|
||||
- Windows 10 1803+ (April 2018 Update or newer)
|
||||
- Initialize submodules once: `git submodule update --init --recursive`
|
||||
|
||||
### Build commands
|
||||
|
||||
| Task | Command |
|
||||
|------|---------|
|
||||
| First build / NuGet restore | `tools\build\build-essentials.cmd` |
|
||||
| Build current folder | `tools\build\build.cmd` |
|
||||
| Build with options | `build.ps1 -Platform x64 -Configuration Release` |
|
||||
|
||||
### Build discipline
|
||||
|
||||
1. One terminal per operation (build → test). Do not switch or open new ones mid-flow
|
||||
2. After making changes, `cd` to the project folder that changed (`.csproj`/`.vcxproj`)
|
||||
3. Use scripts to build: `tools/build/build.ps1` or `tools/build/build.cmd`
|
||||
4. For first build or missing NuGet packages, run `build-essentials.cmd` first
|
||||
5. **Exit code 0 = success; non-zero = failure** – treat this as absolute
|
||||
6. On failure, read the errors log: `build.<config>.<platform>.errors.log`
|
||||
7. Do not start tests or launch Runner until the build succeeds
|
||||
|
||||
### Build logs
|
||||
|
||||
Located next to the solution/project being built:
|
||||
|
||||
- `build.<configuration>.<platform>.errors.log` – errors only (check this first)
|
||||
- `build.<configuration>.<platform>.all.log` – full log
|
||||
- `build.<configuration>.<platform>.trace.binlog` – for MSBuild Structured Log Viewer
|
||||
|
||||
For complete details, see [Build Guidelines](tools/build/BUILD-GUIDELINES.md).
|
||||
|
||||
## Tests
|
||||
|
||||
### Test discovery
|
||||
|
||||
- Find test projects by product code prefix (e.g., `FancyZones`, `AdvancedPaste`)
|
||||
- Look for sibling folders or 1-2 levels up named `<Product>*UnitTests` or `<Product>*UITests`
|
||||
|
||||
### Running tests
|
||||
|
||||
1. **Build the test project first**, wait for exit code 0
|
||||
2. Run via VS Test Explorer (`Ctrl+E, T`) or `vstest.console.exe` with filters
|
||||
3. **Avoid `dotnet test`** in this repo – use VS Test Explorer or vstest.console.exe
|
||||
|
||||
### Test types
|
||||
|
||||
| Type | Requirements | Setup |
|
||||
|------|--------------|-------|
|
||||
| Unit Tests | Standard dev environment | None |
|
||||
| UI Tests | WinAppDriver v1.2.1, Developer Mode | Install from [WinAppDriver releases](https://github.com/microsoft/WinAppDriver/releases/tag/v1.2.1) |
|
||||
| Fuzz Tests | OneFuzz, .NET 8 | See [Fuzzing Tests](doc/devdocs/tools/fuzzingtesting.md) |
|
||||
|
||||
### Test discipline
|
||||
|
||||
1. Add or adjust tests when changing behavior
|
||||
2. If tests skipped, state why (e.g., comment-only change, string rename)
|
||||
3. New modules handling file I/O or user input **must** implement fuzzing tests
|
||||
|
||||
### Special requirements
|
||||
|
||||
- **Mouse Without Borders**: Requires 2+ physical computers (not VMs)
|
||||
- **Multi-monitor utilities**: Test with 2+ monitors, different DPI settings
|
||||
|
||||
For UI test setup details, see [UI Tests](doc/devdocs/development/ui-tests.md).
|
||||
|
||||
## Boundaries
|
||||
|
||||
### Ask for clarification when
|
||||
|
||||
- Ambiguous spec after scanning relevant docs
|
||||
- Cross-module impact (shared enum/struct) is unclear
|
||||
- Security, elevation, or installer changes involved
|
||||
- GPO or policy handling modifications needed
|
||||
|
||||
### Areas requiring extra care
|
||||
|
||||
| Area | Concern | Reference |
|
||||
|------|---------|-----------|
|
||||
| `src/common/` | ABI breaks | [Common Libraries Instructions](.github/instructions/common-libraries.instructions.md) |
|
||||
| `src/runner/`, `src/settings-ui/` | IPC contracts, schema | [Runner & Settings UI Instructions](.github/instructions/runner-settings-ui.instructions.md) |
|
||||
| Installer files | Release impact | Careful review required |
|
||||
| Elevation/GPO logic | Security | Confirm no regression in policy handling |
|
||||
|
||||
### What not to do
|
||||
|
||||
- Don't merge incomplete features into main (use feature branches)
|
||||
- Don't break IPC/JSON contracts without updating both runner and settings-ui
|
||||
- Don't add noisy logs in hot paths
|
||||
- Don't introduce third-party deps without PM approval and `NOTICE.md` update
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Before finishing, verify:
|
||||
|
||||
- [ ] Build clean with exit code 0
|
||||
- [ ] Tests updated and passing locally
|
||||
- [ ] No unintended ABI breaks or schema changes
|
||||
- [ ] IPC contracts consistent between runner and settings-ui
|
||||
- [ ] New dependencies added to `NOTICE.md`
|
||||
- [ ] PR is atomic (one logical change), with issue linked
|
||||
|
||||
## Documentation Index
|
||||
|
||||
### Core architecture
|
||||
|
||||
- [Architecture Overview](doc/devdocs/core/architecture.md)
|
||||
- [Runner](doc/devdocs/core/runner.md)
|
||||
- [Settings System](doc/devdocs/core/settings/readme.md)
|
||||
- [Module Interface](doc/devdocs/modules/interface.md)
|
||||
|
||||
### Development
|
||||
|
||||
- [Coding Guidelines](doc/devdocs/development/guidelines.md)
|
||||
- [Coding Style](doc/devdocs/development/style.md)
|
||||
- [Logging](doc/devdocs/development/logging.md)
|
||||
- [UI Tests](doc/devdocs/development/ui-tests.md)
|
||||
- [Fuzzing Tests](doc/devdocs/tools/fuzzingtesting.md)
|
||||
|
||||
### Build & tools
|
||||
|
||||
- [Build Guidelines](tools/build/BUILD-GUIDELINES.md)
|
||||
- [Tools Overview](doc/devdocs/tools/readme.md)
|
||||
|
||||
### Instructions (auto-applied)
|
||||
|
||||
- [Runner & Settings UI](.github/instructions/runner-settings-ui.instructions.md)
|
||||
- [Common Libraries](.github/instructions/common-libraries.instructions.md)
|
||||
151
COMMUNITY.md
151
COMMUNITY.md
@@ -1,111 +1,83 @@
|
||||
# Community
|
||||
|
||||
The PowerToys team is extremely grateful to have the support of an amazing active community. The work you do is incredibly important. PowerToys wouldn't be near what it is without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thanks and to recognize your work. This is a living document dedicated to highlighting the high impact community members and their contributions.
|
||||
The PowerToys team is extremely grateful to have the support of an amazing active community. The work you do is incredibly important. PowerToys wouldn’t be near what it is without your help filing bugs, updating documentation, guiding the design, or writing features. We want to say thanks and to recognize your work. This is a living document dedicated to highlighting the high impact community members and their contributions.
|
||||
|
||||
Names are in alphabetical order, based on first name.
|
||||
Names are in alphabetical order based on first name.
|
||||
|
||||
## High impact community members
|
||||
|
||||
### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com)
|
||||
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
|
||||
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
|
||||
|
||||
Christian contributed the New+ utility
|
||||
### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com)
|
||||
Christian contributed New+ utility
|
||||
|
||||
### [@CleanCodeDeveloper](https://github.com/CleanCodeDeveloper)
|
||||
|
||||
CleanCodeDeveloper helped do massive amounts of code stability and image resizer work.
|
||||
|
||||
### [@plante-msft](https://github.com/plante-msft) - Connor Plante
|
||||
|
||||
Connor was the creator of Workspaces and helped create Command Palette (PowerToys Run v2)
|
||||
|
||||
### [@damienleroy](https://github.com/damienleroy) - [Damien Leroy](https://www.linkedin.com/in/Damien-Leroy-b2734416a/)
|
||||
|
||||
Damien has helped out by developing and contributing the Quick Accent utility.
|
||||
|
||||
### [@daverayment](https://github.com/daverayment) - [David Rayment](https://www.linkedin.com/in/david-rayment-168b5251/)
|
||||
|
||||
Dave has helped improve the experience inside of Peek by adding in new features and fixing bugs.
|
||||
|
||||
### [@davidegiacometti](https://github.com/davidegiacometti) - [Davide Giacometti](https://www.linkedin.com/in/davidegiacometti/)
|
||||
|
||||
Davide has helped fix multiple bugs, added new utilities, features, as well as help us with the ARM64 effort by porting applications to .NET Core.
|
||||
|
||||
### [@ethanfangg](https://github.com/ethanfangg) - Ethan Fang
|
||||
|
||||
Ethan helped run PowerToys and worked on improving and prototyping out next generation PowerToys
|
||||
|
||||
### [@franky920920](https://github.com/franky920920) - [Franky Chen](https://frankychen.net)
|
||||
|
||||
Franky has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes to PowerToys.
|
||||
|
||||
### [@htcfreek](https://github.com/htcfreek) - Heiko
|
||||
|
||||
Heiko has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes to PowerToys.
|
||||
|
||||
### [@Jay-o-Way](https://github.com/Jay-o-Way) - Jay
|
||||
|
||||
Jay has helped triaging, discussing, creating a substantial number of issues and PRs.
|
||||
|
||||
### [@jefflord](https://github.com/Jjefflord) - Jeff Lord
|
||||
|
||||
Jeff added multiple new features to Keyboard Manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys.
|
||||
|
||||
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
|
||||
|
||||
Jeremy has helped drive substantial ARM64 support within PowerToys.
|
||||
|
||||
### [@jiripolasek](https://github.com/jiripolasek) - [Jiří Polášek](https://github.com/jiripolasek)
|
||||
|
||||
Jiří has contributed a massive number of features and improvements to Command Palette, including drag & drop support, custom themes, Web Search enhancements, Remote Desktop extension fixes, and many UX improvements.
|
||||
Jeff added in multiple new features into Keyboard manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys.
|
||||
|
||||
### [@TheJoeFin](https://github.com/TheJoeFin) - [Joe Finney](https://joefinapps.com)
|
||||
|
||||
Joe has helped with triaging, discussing issues as well as fixing bugs and building features for Text Extractor.
|
||||
Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor.
|
||||
|
||||
### [@joadoumie](https://github.com/joadoumie) - Jordi Adoumie
|
||||
|
||||
Jordi helped innovate amazing new features into Advanced Paste and helped create Command Palette (PowerToys Run v2)
|
||||
|
||||
|
||||
### [@jsoref](https://github.com/jsoref) - [Josh Soref](https://check-spelling.dev/)
|
||||
|
||||
Helping keep our spelling correct :)
|
||||
|
||||
### [@martinchrzan](https://github.com/martinchrzan/) - Martin Chrzan
|
||||
|
||||
Color Picker is from Martin.
|
||||
|
||||
### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com)
|
||||
|
||||
Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility.
|
||||
|
||||
### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
|
||||
|
||||
Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
|
||||
|
||||
### [@pedrolamas](https://github.com/pedrolamas/) - Pedro Lamas
|
||||
|
||||
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
|
||||
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
|
||||
|
||||
### [@PesBandi](https://github.com/PesBandi/) - PesBandi
|
||||
|
||||
PesBandi has helped do massive amounts of Quick Accent and bug fixes.
|
||||
|
||||
### [@riverar](https://github.com/riverar) - [Rafael Rivera](https://withinrafael.com/)
|
||||
|
||||
Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/microsoft/PowerToys/issues/1907). He directly provided feedback to the CppWinRT team for bugs from this migration as well.
|
||||
Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/microsoft/PowerToys/issues/1907). He directly provided feedback to the CppWinRT team for bugs from this migration as well.
|
||||
|
||||
### [@royvou](https://github.com/royvou)
|
||||
|
||||
Roy has helped out contributing multiple features to PowerToys Run
|
||||
|
||||
### [@ThiefZero](https://github.com/ThiefZero)
|
||||
|
||||
ThiefZero has helped contribute features to PowerToys Run, such as the unit converter plugin
|
||||
### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
|
||||
Jeremy has helped drive large sums of the ARM64 support inside PowerToys
|
||||
|
||||
### [@TobiasSekan](https://github.com/TobiasSekan) - Tobias Sekan
|
||||
|
||||
Tobias Sekan has helped out contributing features to PowerToys Run such as Settings plugin, Registry plugin
|
||||
|
||||
### [@ThiefZero](https://github.com/ThiefZero)
|
||||
ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
|
||||
|
||||
## Open source projects
|
||||
|
||||
As PowerToys creates new utilities, some will be based off existing technology. We'll continue to do our best to contribute back to these projects but their efforts were the base of some of our projects. We want to be sure their work is directly recognized.
|
||||
@@ -119,8 +91,7 @@ Their fork of Wox was the base of PowerToys Run.
|
||||
Initial base of jjw24's fork, which makes it the base of PowerToys Run.
|
||||
|
||||
### [Text-Grab](https://github.com/TheJoeFin/Text-Grab) - Joseph Finney
|
||||
|
||||
Joe helped develop and contribute to the Text Extractor utility. It is directly based on his Text Grab application.
|
||||
Joe helped develop and contribute to the Text Extractor utility. It is directly based on his Text Grab application.
|
||||
|
||||
## Microsoft community members
|
||||
|
||||
@@ -128,7 +99,7 @@ We would like to also directly call out some extremely helpful Microsoft employe
|
||||
|
||||
### [@betsegaw](https://github.com/betsegaw/) - [Betsegaw Tadele](http://www.dreamsofameaningfullife.com/)
|
||||
|
||||
Window Walker, inside PowerToys Run, is from Beta.
|
||||
Window Walker, inside PowerToys Run, is from Beta.
|
||||
|
||||
### [@TheMrJukes](https://github.com/TheMrJukes/) - Bret Anderson
|
||||
|
||||
@@ -151,7 +122,6 @@ PowerToys Awake is a tool to keep your computer awake.
|
||||
Randy contributed Registry Preview and some very early conversations about keyboard remapping.
|
||||
|
||||
### [@cinnamon-msft](https://github.com/cinnamon-msft) - Kayla Cinnamon
|
||||
|
||||
Kayla was a former lead for PowerToys and helped create multiple utilities, maintained the GitHub repo, and collaborated with the community to improve the overall product
|
||||
|
||||
### [@oldnewthing](https://github.com/oldnewthing) - Raymond Chen
|
||||
@@ -162,48 +132,46 @@ Find My Mouse is based on Raymond Chen's SuperSonar.
|
||||
|
||||
Crop And Lock is based on the original work of Robert Mikhayelyan, with Program Manager support from [@kevinguo305](https://github.com/kevinguo305) - Kevin Guo.
|
||||
|
||||
ZoomIt's Video Recording Session code is based on Robert Mikhayelyan's <https://github.com/robmikh/capturevideosample> code.
|
||||
ZoomIt's Video Recording Session code is based on Robert Mikhayelyan's https://github.com/robmikh/capturevideosample code.
|
||||
|
||||
### Microsoft InVEST team
|
||||
|
||||
This amazing team helped PowerToys develop PowerToys Run and Keyboard manager as well as update our Settings to v2. @alekhyareddy28, @arjunbalgovind, @jyuwono @laviusmotileng-ms, @ryanbodrug-microsoft, @saahmedm, @somil55, @traies, @udit3333
|
||||
This amazing team helped PowerToys develop PowerToys Run and Keyboard manager as well as update our Settings to v2. @alekhyareddy28, @arjunbalgovind, @jyuwono @laviusmotileng-ms, @ryanbodrug-microsoft, @saahmedm, @somil55, @traies, @udit3333
|
||||
|
||||
## Mouse Without Borders original contributors
|
||||
|
||||
Project creator: Truong Do (Đỗ Đức Trường)
|
||||
*Project creator: Truong Do (Đỗ Đức Trường)*
|
||||
|
||||
Other contributors:
|
||||
|
||||
- Microsoft Garage: Quinn Hawkins, Michael Low, Joe Coplen, Nino Yuniardi, Gwyneth Marshall, David Andrews, Karen Luecking
|
||||
- Peter Hauge - Visual Studio
|
||||
- Bruce Dawson - Windows Fundamentals
|
||||
- Alan Myrvold - Office Security
|
||||
- Adrian Garside - WEX
|
||||
- Scott Bradner - Surface
|
||||
- Aleks Gershaft - Windows Azure
|
||||
- Chinh Huynh - Windows Azure
|
||||
- Long Nguyen - Data Center
|
||||
- Triet Le - Cloud Engineering
|
||||
- Luke Schoen - Excel
|
||||
- Bao Nguyen - Bing
|
||||
- Ross Nichols - Windows
|
||||
- Ryan Baltazar - Windows
|
||||
- Ed Essey - The Garage
|
||||
- Mario Madden - The Garage
|
||||
- Karthick Mahalingam - ACE
|
||||
- Pooja Kamra - ACE
|
||||
- Justin White - SA
|
||||
- Chris Ransom - SA
|
||||
- Mike Ricks - Red Team
|
||||
- Randy Santossio - Surface
|
||||
- Ashish Sen Jaswal - Device Health
|
||||
- Zoltan Harmath - Security Tools
|
||||
- Luciano Krigun - Security Products
|
||||
- Jo Hemmerlein - Red Team
|
||||
- Chris Johnson - Surface Hub
|
||||
- Loren Ponten - Surface Hub
|
||||
- Paul Schmitt - WWL
|
||||
- And many other Users!
|
||||
* Microsoft Garage: Quinn Hawkins, Michael Low, Joe Coplen, Nino Yuniardi, Gwyneth Marshall, David Andrews, Karen Luecking
|
||||
* Peter Hauge - Visual Studio
|
||||
* Bruce Dawson - Windows Fundamentals
|
||||
* Alan Myrvold - Office Security
|
||||
* Adrian Garside - WEX
|
||||
* Scott Bradner - Surface
|
||||
* Aleks Gershaft - Windows Azure
|
||||
* Chinh Huynh - Windows Azure
|
||||
* Long Nguyen - Data Center
|
||||
* Triet Le - Cloud Engineering
|
||||
* Luke Schoen - Excel
|
||||
* Bao Nguyen - Bing
|
||||
* Ross Nichols - Windows
|
||||
* Ryan Baltazar - Windows
|
||||
* Ed Essey - The Garage
|
||||
* Mario Madden - The Garage
|
||||
* Karthick Mahalingam - ACE
|
||||
* Pooja Kamra - ACE
|
||||
* Justin White - SA
|
||||
* Chris Ransom - SA
|
||||
* Mike Ricks - Red Team
|
||||
* Randy Santossio - Surface
|
||||
* Ashish Sen Jaswal - Device Health
|
||||
* Zoltan Harmath - Security Tools
|
||||
* Luciano Krigun - Security Products
|
||||
* Jo Hemmerlein - Red Team
|
||||
* Chris Johnson - Surface Hub
|
||||
* Loren Ponten - Surface Hub
|
||||
* Paul Schmitt - WWL
|
||||
* And many other Users!
|
||||
|
||||
## ZoomIt original contributors
|
||||
|
||||
@@ -219,10 +187,18 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
|
||||
- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager
|
||||
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev Lead
|
||||
- [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev Lead
|
||||
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
|
||||
- [@lei9444](https://github.com/lei9444) - Leilei Zhang - Dev
|
||||
- [@shuaiyuanxx](https://github.com/shuaiyuanxx) - Shawn Yuan - Dev
|
||||
- [@moooyo](https://github.com/moooyo) - Yu Leng - Dev
|
||||
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
|
||||
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
|
||||
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
|
||||
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
|
||||
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
|
||||
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
|
||||
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
|
||||
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
|
||||
- [@vanzue](https://github.com/vanzue) - Kai Tao - Dev
|
||||
- [@zadjii-msft](https://github.com/zadjii-msft) - Mike Griese - Dev
|
||||
- [@khmyznikov](https://github.com/khmyznikov) - Gleb Khmyznikov - Dev
|
||||
@@ -253,12 +229,3 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
|
||||
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
|
||||
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
|
||||
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev Lead
|
||||
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
|
||||
- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
|
||||
- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
|
||||
- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
|
||||
- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
|
||||
- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
|
||||
- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
|
||||
- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
|
||||
- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# PowerToys contributor's guide
|
||||
# PowerToys Contributor's Guide
|
||||
|
||||
Below is our guidance for reporting issues, proposing new features, and submitting contributions via Pull Requests (PRs). Our philosophy is to understand the problem and scenarios first, which is why we follow this pattern before work starts.
|
||||
|
||||
@@ -6,46 +6,46 @@ Below is our guidance for reporting issues, proposing new features, and submitti
|
||||
2. There has been a conversation.
|
||||
3. There is agreement on the problem, the fit for PowerToys, and the solution to the problem (implementation).
|
||||
|
||||
## Filing an issue
|
||||
## Filing an Issue
|
||||
|
||||
**Importance of Filing an Issue First**
|
||||
|
||||
Please follow this rule to help eliminate wasted effort and frustration, and to ensure an efficient and effective use of everyone's time:
|
||||
Please follow this rule to help eliminate wasted effort and frustration, and to ensure an efficient and effective use of everyone’s time:
|
||||
|
||||
> 👉 If you have a question, think you've discovered an issue, or would like to propose a new feature, please find/file an issue **BEFORE** starting work to fix/implement it.
|
||||
|
||||
When requesting new features or enhancements, providing additional evidence, data, tweets, blog posts, or research is extremely helpful. This information gives context to the scenario that may otherwise be lost.
|
||||
|
||||
- Unsure whether it's an issue or feature request? File an issue.
|
||||
- Have a question that isn't answered in the docs, videos, etc.? File an issue.
|
||||
- Want to know if we're planning a particular feature? File an issue.
|
||||
- Got a great idea for a new utility or feature? File an issue/request/idea.
|
||||
- Don't understand how to do something? File an issue/Community Guidance Request.
|
||||
- Found an existing issue that describes yours? Great! Upvote and add additional commentary, info, or repro steps.
|
||||
* Unsure whether it’s an issue or feature request? File an issue.
|
||||
* Have a question that isn't answered in the docs, videos, etc.? File an issue.
|
||||
* Want to know if we’re planning a particular feature? File an issue.
|
||||
* Got a great idea for a new utility or feature? File an issue/request/idea.
|
||||
* Don’t understand how to do something? File an issue/Community Guidance Request.
|
||||
* Found an existing issue that describes yours? Great! Upvote and add additional commentary, info, or repro steps.
|
||||
|
||||
A quick search before filing an issue could be helpful. It's likely someone else has found the same problem, and they may even be working on or have already contributed a fix!
|
||||
A quick search before filing an issue could be helpful. It’s likely someone else has found the same problem, and they may even be working on or have already contributed a fix!
|
||||
|
||||
### Indicating interest in issues
|
||||
### Indicating Interest in Issues
|
||||
|
||||
To let the team know which issues are important, upvote by clicking the [+😊] button and the 👍 icon on the original issue post. Avoid comments like "+1" or "me too" as they clutter the discussion and make it harder to prioritize requests.
|
||||
|
||||
---
|
||||
|
||||
## Contributing fixes or features
|
||||
## Contributing Fixes/Features
|
||||
|
||||
Please comment on our [Would you like to contribute to PowerToys?](https://github.com/microsoft/PowerToys/issues/28769) thread to let us know you're interested in working on something before you start. This helps avoid multiple people unexpectedly working on the same thing and ensures everyone is clear on what should be done. It's less work for everyone to establish this up front.
|
||||
Please comment on our ["Would you like to contribute to PowerToys?"](https://github.com/microsoft/PowerToys/issues/28769) thread to let us know you're interested in working on something before you start. This helps avoid multiple people unexpectedly working on the same thing and ensures everyone is clear on what should be done. It's less work for everyone to establish this up front.
|
||||
|
||||
### Localization issues
|
||||
### Localization Issues
|
||||
|
||||
For localization issues, please file an issue to notify our internal localization team, as community PRs for localization aren't accepted. Localization is handled exclusively by the internal Microsoft team.
|
||||
|
||||
### To spec or not to spec
|
||||
### To Spec or Not to Spec
|
||||
|
||||
A key point is for everyone to understand the approach that will be taken. We want to be sure that any work done will be accepted. Larger-scope items will require a spec to outline the approach and allow for discussion. Specs help collaborators consider different solutions, describe feature behavior, and plan for errors. Achieving agreement in a spec before writing code often results in simpler code and less wasted effort.
|
||||
|
||||
Once a team member has agreed with your approach, proceed to the "Development" section below. Team members are happy to help review specs and guide them to completion.
|
||||
|
||||
### Help wanted
|
||||
### Help Wanted
|
||||
|
||||
Once the team has approved an issue/spec approach, development can proceed. If no developers are immediately available, the spec may be parked and labeled "Help Wanted," ready for a developer to get started. For development opportunities, visit [Issues labeled Help Wanted](https://github.com/microsoft/PowerToys/labels/Help%20Wanted).
|
||||
|
||||
@@ -55,18 +55,18 @@ Once the team has approved an issue/spec approach, development can proceed. If n
|
||||
|
||||
Follow the [development guidelines](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md).
|
||||
|
||||
### Naming features and functionality
|
||||
### Naming Features and Functionality
|
||||
|
||||
Names should be descriptive and straightforward, clearly reflecting functionality and usefulness.
|
||||
|
||||
### Becoming a collaborator on the PowerToys team
|
||||
### Becoming a Collaborator on the PowerToys Team
|
||||
|
||||
Be an active community member! Make helpful contributions by filing bugs, offering suggestions, developing fixes and features, conducting code reviews, and updating documentation.
|
||||
Be an active community member! Make helpful contributions by filing bugs, offering suggestions, developing fixes and features, conducting code reviews, and updating documentation.
|
||||
|
||||
When the time comes, Microsoft will reach out to you about becoming a formal team member. Just make sure they have a way to contact you. 😊
|
||||
|
||||
---
|
||||
|
||||
## Thank you
|
||||
## Thank You
|
||||
|
||||
Thank you in advance for your contribution! We appreciate your help in making PowerToys a better tool for everyone.
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
<Project ToolsVersion="4.0"
|
||||
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<!-- Skip building C++ test projects when BuildTests=false -->
|
||||
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
|
||||
<UsePrecompiledHeaders>false</UsePrecompiledHeaders>
|
||||
<RunCodeAnalysis>false</RunCodeAnalysis>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Project configurations -->
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
@@ -57,13 +51,14 @@
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<DisableSpecificWarnings>4679;4706;4874;5271;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<DisableSpecificWarnings>4679;5271;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<DisableAnalyzeExternal >true</DisableAnalyzeExternal>
|
||||
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||
<BuildStlModules>false</BuildStlModules>
|
||||
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
|
||||
<!-- TODO: _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING for compatibility with VS 17.8. Check if we can remove. -->
|
||||
<PreprocessorDefinitions>_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<!-- CLR + CFG are not compatible >:{ -->
|
||||
@@ -115,7 +110,6 @@
|
||||
<!-- Props that are constant for both Debug and Release configurations -->
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<PlatformToolset Condition="'$(VisualStudioVersion)' == '18.0'">v145</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<DesktopCompatible>true</DesktopCompatible>
|
||||
<SpectreMitigation>Spectre</SpectreMitigation>
|
||||
|
||||
1443
DATA_AND_PRIVACY.md
1443
DATA_AND_PRIVACY.md
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(RepoRoot)src\Version.props" />
|
||||
<Import Project="src\Version.props" />
|
||||
<PropertyGroup>
|
||||
<Copyright>Copyright (C) Microsoft Corporation. All rights reserved.</Copyright>
|
||||
<AssemblyCopyright>Copyright (C) Microsoft Corporation. All rights reserved.</AssemblyCopyright>
|
||||
@@ -20,57 +17,6 @@
|
||||
<NuGetAuditMode>direct</NuGetAuditMode>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
<RestoreEnablePackagePruning Condition=" '$(VisualStudioVersion)' == '17.0'">false </RestoreEnablePackagePruning>
|
||||
|
||||
<!-- Enable Microsoft.Testing.Platform -->
|
||||
<EnableMSTestRunner>true</EnableMSTestRunner>
|
||||
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
|
||||
<TestingPlatformDotNetTestSupport>true</TestingPlatformDotNetTestSupport>
|
||||
<TestingPlatformCommandLineArguments>$(TestingPlatformCommandLineArguments) --report-trx</TestingPlatformCommandLineArguments>
|
||||
<!-- No arm64 agents to run the tests. -->
|
||||
<TestingPlatformDisableCustomTestTarget Condition="'$(Platform)' == 'ARM64'">true</TestingPlatformDisableCustomTestTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
UI tests are run in dedicated UI test jobs/pipelines.
|
||||
In CI, the main build uses `/t:Build;Test` across the full solution, so
|
||||
prevent UI test projects from being executed in that pass.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(TF_BUILD)' != '' and $(MSBuildProjectName.Contains('UITest'))">
|
||||
<TestingPlatformDisableCustomTestTarget>true</TestingPlatformDisableCustomTestTarget>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Completely skip building test projects when BuildTests=false (e.g., Release pipeline).
|
||||
This avoids InternalsVisibleTo/signing issues by not compiling test code at all.
|
||||
Match: projects ending in Test, Tests, UnitTests, UITests, FuzzTests, or in a folder named Tests.
|
||||
Also matches projects starting with UnitTests- (e.g., UnitTests-CommonLib).
|
||||
Also removes all PackageReference/ProjectReference to prevent NuGet restore and dependency builds.
|
||||
Note: Checking both 'false' and 'False' to handle YAML boolean serialization.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(BuildTests)' == 'false' or '$(BuildTests)' == 'False'">
|
||||
<_ProjectName>$(MSBuildProjectName)</_ProjectName>
|
||||
<!-- Match any project ending with "Test" or "Tests" (covers UnitTests, UITests, FuzzTests, etc.) -->
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Test'))">true</_IsSkippedTestProject>
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.EndsWith('Tests'))">true</_IsSkippedTestProject>
|
||||
<!-- Match projects starting with UnitTests- or UITest- prefix -->
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UnitTests-'))">true</_IsSkippedTestProject>
|
||||
<_IsSkippedTestProject Condition="$(_ProjectName.StartsWith('UITest-'))">true</_IsSkippedTestProject>
|
||||
<!-- Match projects in a Tests folder -->
|
||||
<_IsSkippedTestProject Condition="$(MSBuildProjectDirectory.Contains('\Tests\'))">true</_IsSkippedTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
|
||||
<EnableDefaultItems>false</EnableDefaultItems>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateGlobalUsings>false</GenerateGlobalUsings>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<!-- Disable all code analysis for skipped test projects -->
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
<RunAnalyzers>false</RunAnalyzers>
|
||||
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
@@ -82,17 +28,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<_PropertySheetDisplayName>PowerToys.Root.Props</_PropertySheetDisplayName>
|
||||
<ForceImportBeforeCppProps>$(RepoRoot)Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj' and '$(_IsSkippedTestProject)' != 'true'">
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<Compile Include="$(RepoRoot)src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
|
||||
<AdditionalFiles Include="$(RepoRoot)src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -100,15 +44,7 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- In CI, we build and test with `/t:Build;Test` -->
|
||||
<!-- So, for non-test projects, we want the target to be there and it's basically doing nothing -->
|
||||
<!-- For C# test projects, Microsoft.Testing.Platform should inject Test target here: -->
|
||||
<!-- https://github.com/microsoft/testfx/blob/5ad21909704db501f58f27d4a7ec241edd761af5/src/Platform/Microsoft.Testing.Platform.MSBuild/buildMultiTargeting/Microsoft.Testing.Platform.MSBuild.targets#L270-L273 -->
|
||||
<!-- For C++ test projects, the RunVSTest SDK will do its job -->
|
||||
<Target Name="Test" />
|
||||
|
||||
<!-- Add ability to run tests via "msbuild /t:Test" using the RunVSTest SDK -->
|
||||
<!-- This is only needed for C++, as we use Microsoft.Testing.Platform for C# -->
|
||||
<!-- Add ability to run tests via "msbuild /t:Test" -->
|
||||
<!--
|
||||
Work around an MSBuild bug where Microsoft.Common.Test.targets is missing from the Arm64 installation.
|
||||
See: https://github.com/dotnet/msbuild/pull/9984
|
||||
@@ -118,11 +54,11 @@
|
||||
Once the change referenced above is fixed, the ImportGroup below can be replaced with:
|
||||
<Sdk Name="Microsoft.Build.RunVSTest" Version="1.0.319" />
|
||||
-->
|
||||
<ImportGroup Condition="'$(PROCESSOR_ARCHITECTURE)' != 'ARM64' AND ('$(Language)' == 'C++' OR '$(MSBuildProjectExtension)' == '.vcxproj')">
|
||||
<ImportGroup Condition="'$(PROCESSOR_ARCHITECTURE)' != 'ARM64'">
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.Build.RunVSTest" Version="1.0.319" />
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.Build.RunVSTest" Version="1.0.319" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Condition="'$(Language)' == 'C++' OR '$(MSBuildProjectExtension)' == '.vcxproj'">
|
||||
<PropertyGroup>
|
||||
<VSTestLogger>trx</VSTestLogger>
|
||||
<!--
|
||||
RunVSTest by default uses %VSINSTALLDIR%\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe,
|
||||
@@ -171,12 +107,6 @@
|
||||
$(USERPROFILE)\AppData\LocalLow\Microsoft\PowerToys\**;
|
||||
</MSBuildCacheAllowFileAccessAfterProjectFinishFilePatterns>
|
||||
|
||||
<!-- dotnet.exe seems to access files after builds. Temporarily putting in this entry for testing if we get further. This looks to be related to a .NET Roslyn Analyzer in .NET 10-->
|
||||
<MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns>
|
||||
$(MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns);
|
||||
\**\dotnet\dotnet.exe;
|
||||
\**\vbcscompiler.exe;
|
||||
</MSBuildCacheAllowFileAccessAfterProjectFinishProcessPatterns>
|
||||
<!--
|
||||
This repo uses a common output directory with many projects writing duplicate outputs. Allow everything, but note this costs some performance in the form of requiring
|
||||
the cache to use copies instead of hardlinks when pulling from cache.
|
||||
|
||||
@@ -28,57 +28,4 @@
|
||||
<PropertyGroup Condition="'$(IgnoreExperimentalWarnings)' == 'true'">
|
||||
<NoWarn>$(NoWarn);CS8305;SA1500;CA1852</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Skipped test projects when BuildTests=false: no-op build and remove references.
|
||||
This must be in targets (not props) so it runs AFTER the project file adds its items. -->
|
||||
<PropertyGroup Condition="'$(_IsSkippedTestProject)' == 'true'">
|
||||
<BuildDependsOn />
|
||||
<CoreBuildDependsOn />
|
||||
<RebuildDependsOn />
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- For C# projects: remove all items -->
|
||||
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Remove="@(PackageReference)" />
|
||||
<ProjectReference Remove="@(ProjectReference)" />
|
||||
<Reference Remove="@(Reference)" />
|
||||
<Compile Remove="@(Compile)" />
|
||||
<Content Remove="@(Content)" />
|
||||
<EmbeddedResource Remove="@(EmbeddedResource)" />
|
||||
<None Remove="@(None)" />
|
||||
<Using Remove="@(Using)" />
|
||||
<GlobalUsing Remove="@(GlobalUsing)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- For C++ projects (vcxproj): remove all compile/link items to prevent build -->
|
||||
<ItemGroup Condition="'$(_IsSkippedTestProject)' == 'true' and '$(MSBuildProjectExtension)' == '.vcxproj'">
|
||||
<ClCompile Remove="@(ClCompile)" />
|
||||
<ClInclude Remove="@(ClInclude)" />
|
||||
<Link Remove="@(Link)" />
|
||||
<Lib Remove="@(Lib)" />
|
||||
<ProjectReference Remove="@(ProjectReference)" />
|
||||
<None Remove="@(None)" />
|
||||
<ResourceCompile Remove="@(ResourceCompile)" />
|
||||
<Midl Remove="@(Midl)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Note: For C++ skipped test projects, build is effectively skipped by removing all compile items above.
|
||||
We don't define empty Build/Rebuild/Clean targets here because MSBuild Target definitions with Condition
|
||||
on the Target element still override the default targets even when condition is false. -->
|
||||
|
||||
<!-- Clean up unused VC++ runtime DLLs that CopyCppRuntimeToOutputDir copies from the full
|
||||
VCRedist tree (MFC, C++ AMP, OpenMP). No PowerToys binary links against these — verified
|
||||
with dumpbin /dependents across all installed binaries. -->
|
||||
<Target Name="RemoveUnusedVCRuntimeDlls"
|
||||
AfterTargets="Build"
|
||||
Condition="'$(CopyCppRuntimeToOutputDir)' == 'true' and '$(MSBuildProjectExtension)' == '.vcxproj'">
|
||||
<ItemGroup>
|
||||
<_UnusedVCRuntimeDlls Include="$(OutDir)mfc140*.dll" />
|
||||
<_UnusedVCRuntimeDlls Include="$(OutDir)mfcm140*.dll" />
|
||||
<_UnusedVCRuntimeDlls Include="$(OutDir)vcamp140*.dll" />
|
||||
<_UnusedVCRuntimeDlls Include="$(OutDir)vcomp140*.dll" />
|
||||
</ItemGroup>
|
||||
<Delete Files="@(_UnusedVCRuntimeDlls)" Condition="'@(_UnusedVCRuntimeDlls)' != ''" />
|
||||
<Message Importance="normal" Text="Cleaned up unused VC runtime DLLs: @(_UnusedVCRuntimeDlls)" Condition="'@(_UnusedVCRuntimeDlls)' != ''" />
|
||||
</Target>
|
||||
</Project>
|
||||
</Project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user