From 6d9d768e0991168af192bf88fbd6291fb089ca19 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 11 Jun 2026 10:36:05 -0700 Subject: [PATCH 1/5] Initial instructions pass --- ...manifest-schema-versioning.instructions.md | 52 ++++ .../manifests/Branch-LatestManifestSchema.ps1 | 261 ++++++++++++++++++ schemas/JSON/manifests/README.md | 175 ++++++++++++ 3 files changed, 488 insertions(+) create mode 100644 .github/instructions/manifest-schema-versioning.instructions.md create mode 100644 schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 create mode 100644 schemas/JSON/manifests/README.md diff --git a/.github/instructions/manifest-schema-versioning.instructions.md b/.github/instructions/manifest-schema-versioning.instructions.md new file mode 100644 index 0000000000..cac1fd55c5 --- /dev/null +++ b/.github/instructions/manifest-schema-versioning.instructions.md @@ -0,0 +1,52 @@ +--- +applyTo: "schemas/JSON/manifests/**,src/ManifestSchema/**" +--- + +# Manifest Schema Versioning + +The `schemas/JSON/manifests/` directory holds JSON schemas for WinGet package manifests. +`latest/` is the in-development schema; each `v{X.Y.0}/` directory is a frozen snapshot. +See `schemas/JSON/manifests/README.md` for full documentation. + +## Branching `latest/` to a frozen version + +Run `schemas/JSON/manifests/Branch-LatestManifestSchema.ps1` from the repo root to automate +the schema-file and project-file steps. Then verify or complete the manual items below. + +### Files to update + +| File | Change | +|------|--------| +| `schemas/JSON/manifests/v{VERSION}/` | **Create** — copy each `latest/manifest.*.latest.json` to `manifest.*.{VERSION}.json`. No JSON content changes needed; `$id` already contains the version. | +| `src/ManifestSchema/ManifestSchema.h` | **Add** five `#define IDX_MANIFEST_SCHEMA_V{MAJOR}_{MINOR}_*` constants (Singleton/Version/Installer/DefaultLocale/Locale) using the next available IDs (must stay below 300). | +| `src/ManifestSchema/ManifestSchema.rc` | **Update** the five resource entries for this version from `latest/` paths to `v{VERSION}/` paths, **or add** them if they don't exist yet, pointing directly to `v{VERSION}/`. | +| `src/ManifestSchema/ManifestSchema.vcxitems` | **Add** five `` entries alongside the existing `latest/` entries. | +| `src/ManifestSchema/ManifestSchema.vcxitems.filters` | **Add** a `` with a new GUID, and five `` items with `schema\v{VERSION}`. | +| `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` | **Add** `constexpr std::string_view s_ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}"sv;` | +| `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** a new `if (manifestVersion >= ManifestVer{ s_ManifestVersionV{MAJOR}_{MINOR} })` block at the top of the version-check chain with the five `IDX_MANIFEST_SCHEMA_V{MAJOR}_{MINOR}_*` constants. | +| `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** `public const string ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}";` | +| `src/AppInstallerCLITests/TestData/` | **Add** `ManifestV{MAJOR}_{MINOR}-Singleton.yaml` and `ManifestV{MAJOR}_{MINOR}-MultiFile-{Version,Installer,DefaultLocale,Locale}.yaml`. | +| `src/AppInstallerCLITests/AppInstallerCLITests.vcxproj` and `.vcxproj.filters` | **Add** references to the new test manifest files. | +| `src/AppInstallerCLITests/YamlManifest.cpp` | **Add** test cases exercising the new version. | + +### ManifestSchemaValidation.cpp pattern + +```cpp +if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N }) +{ + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_N_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_N_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_N_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_N_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_N_LOCALE }, + }; +} +else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_ }) +``` + +### Notes + +- If C++ infrastructure (ManifestCommon.h, ManifestSchemaValidation.cpp, ManifestSchema.h, ManifestVersion.cs) was already added in a prior PR pointing to `latest/`, only update ManifestSchema.rc to redirect from `latest/` to the versioned path; the constants and logic stay the same. +- Resource IDs in `ManifestSchema.h` are numbered sequentially in groups of 5 (one group per schema version). The comment near the top warns they must stay below 300. +- The `latest/` `` entries in `ManifestSchema.vcxitems` are never removed; versioned entries are added alongside them. diff --git a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 new file mode 100644 index 0000000000..32fbdb129e --- /dev/null +++ b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 @@ -0,0 +1,261 @@ +<# +.SYNOPSIS + Branches the 'latest' manifest schemas into a frozen, versioned folder. + +.DESCRIPTION + Reads the schema version from the $id field in latest/manifest.installer.latest.json, + creates schemas/JSON/manifests/v{VERSION}/ with properly named copies of each latest/ + schema file, and updates ManifestSchema.rc, ManifestSchema.vcxitems, and + ManifestSchema.vcxitems.filters to reference the versioned files. + + C++ source changes (ManifestCommon.h, ManifestSchemaValidation.cpp, ManifestSchema.h, + ManifestVersion.cs) and test manifests require manual updates - see README.md. + +.PARAMETER RepoRoot + Path to the repository root. Defaults to three directories above this script, which is + correct when the script lives at schemas/JSON/manifests/Branch-LatestManifestSchema.ps1. + +.EXAMPLE + .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 + +.EXAMPLE + .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -WhatIf +#> +[CmdletBinding(SupportsShouldProcess)] +param( + [string] $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..') -ErrorAction Stop).Path +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# -- Helper: save XML preserving encoding -------------------------------------- +function Save-XmlFile { + param([xml]$Xml, [string]$Path) + $settings = [System.Xml.XmlWriterSettings]@{ + Indent = $true + IndentChars = ' ' + Encoding = New-Object System.Text.UTF8Encoding $false + } + $writer = [System.Xml.XmlWriter]::Create($Path, $settings) + try { $Xml.Save($writer) } + finally { $writer.Close() } +} + +# -- Resolve paths -------------------------------------------------------------- +$manifestsDir = Join-Path $RepoRoot 'schemas\JSON\manifests' +$latestDir = Join-Path $manifestsDir 'latest' +$manifestSchemaDir = Join-Path $RepoRoot 'src\ManifestSchema' +$rcFile = Join-Path $manifestSchemaDir 'ManifestSchema.rc' +$vcxitemsFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems' +$filtersFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems.filters' + +foreach ($path in $latestDir, $rcFile, $vcxitemsFile, $filtersFile) { + if (-not (Test-Path $path)) { + throw "Required path not found: '$path'. Verify -RepoRoot is correct." + } +} + +# -- Detect version from latest/manifest.installer.latest.json ----------------- +$installerSchema = Join-Path $latestDir 'manifest.installer.latest.json' +$schemaJson = Get-Content $installerSchema -Raw | ConvertFrom-Json +$idField = $schemaJson.'$id' + +if ($idField -notmatch '(\d+\.\d+\.\d+)\.schema\.json$') { + throw "Could not parse version from `$id field: '$idField'" +} +$version = $Matches[1] +$versionedDir = Join-Path $manifestsDir "v$version" + +Write-Host "Schema version detected: $version" +Write-Host "Target directory: $versionedDir" +Write-Host '' + +if (Test-Path $versionedDir) { + Write-Warning "Directory '$versionedDir' already exists - files may be overwritten." +} + +# -- Schema file types ---------------------------------------------------------- +$schemaTypes = @('singleton', 'version', 'installer', 'defaultLocale', 'locale') + +# Relative path prefix used in ManifestSchema.rc (relative to src\ManifestSchema\) +$rcRelBase = '..\..\schemas\JSON\manifests' + +# Include-path prefix used in ManifestSchema.vcxitems / .vcxitems.filters +$vcxRelBase = '$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests' + +$ns = 'http://schemas.microsoft.com/developer/msbuild/2003' + +# -- Step 1: Copy and rename schema files --------------------------------------- +Write-Host '=== Step 1: Schema files ===' +if ($PSCmdlet.ShouldProcess($versionedDir, 'Create directory')) { + New-Item -ItemType Directory -Path $versionedDir -Force | Out-Null +} + +foreach ($type in $schemaTypes) { + $src = Join-Path $latestDir "manifest.$type.latest.json" + $dest = Join-Path $versionedDir "manifest.$type.$version.json" + if ($PSCmdlet.ShouldProcess("manifest.$type.latest.json -> manifest.$type.$version.json", 'Copy')) { + Copy-Item -Path $src -Destination $dest -Force + Write-Host " Copied manifest.$type.$version.json" + } +} + +# -- Step 2: Update ManifestSchema.rc ------------------------------------------ +Write-Host '' +Write-Host '=== Step 2: ManifestSchema.rc ===' +$rcContent = Get-Content $rcFile -Raw +$rcModified = $false + +foreach ($type in $schemaTypes) { + # The RC file uses double-backslash escaping inside quoted strings; use + # String.Replace (literal) rather than -replace (regex) to avoid re-escaping. + $latestPath = ("$rcRelBase\latest\manifest.$type.latest.json").Replace('\', '\\') + $versionedPath = ("$rcRelBase\v$version\manifest.$type.$version.json").Replace('\', '\\') + + if ($rcContent.Contains($latestPath)) { + if ($PSCmdlet.ShouldProcess($rcFile, "Redirect manifest.$type from latest/ to v$version/")) { + $rcContent = $rcContent.Replace($latestPath, $versionedPath) + $rcModified = $true + Write-Host " Updated path for manifest.$type" + } + } elseif ($rcContent.Contains($versionedPath)) { + Write-Host " manifest.$type already points to v$version/ - no change needed." + } else { + Write-Warning " manifest.${type}: neither latest/ nor v${version}/ path found in RC file. Manual edit required." + } +} + +if ($rcModified -and $PSCmdlet.ShouldProcess($rcFile, 'Save')) { + [System.IO.File]::WriteAllText($rcFile, $rcContent, (New-Object System.Text.UTF8Encoding $false)) + Write-Host " Saved $rcFile" +} + +# -- Step 3: Update ManifestSchema.vcxitems ------------------------------------ +Write-Host '' +Write-Host '=== Step 3: ManifestSchema.vcxitems ===' + +[xml]$vcxXml = Get-Content $vcxitemsFile -Raw +$nsm = New-Object System.Xml.XmlNamespaceManager($vcxXml.NameTable) +$nsm.AddNamespace('msb', $ns) + +$noneGroup = $vcxXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $nsm) +if (-not $noneGroup) { + throw 'Could not locate containing elements in ManifestSchema.vcxitems.' +} + +$vcxModified = $false +foreach ($type in $schemaTypes) { + $versionedInclude = "$vcxRelBase\v$version\manifest.$type.$version.json" + + if ($vcxXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $nsm)) { + Write-Host " manifest.$type v$version already present - no change needed." + continue + } + + if ($PSCmdlet.ShouldProcess($vcxitemsFile, "Add for manifest.$type v$version")) { + $el = $vcxXml.CreateElement('None', $ns) + $el.SetAttribute('Include', $versionedInclude) + $noneGroup.AppendChild($el) | Out-Null + $vcxModified = $true + Write-Host " Added for manifest.$type.$version.json" + } +} + +if ($vcxModified -and $PSCmdlet.ShouldProcess($vcxitemsFile, 'Save')) { + Save-XmlFile $vcxXml $vcxitemsFile + Write-Host " Saved $vcxitemsFile" +} + +# -- Step 4: Update ManifestSchema.vcxitems.filters ---------------------------- +Write-Host '' +Write-Host '=== Step 4: ManifestSchema.vcxitems.filters ===' + +[xml]$filtersXml = Get-Content $filtersFile -Raw +$fnsm = New-Object System.Xml.XmlNamespaceManager($filtersXml.NameTable) +$fnsm.AddNamespace('msb', $ns) + +$filterName = "schema\v$version" + +# Add the entry if missing +$filterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:Filter]', $fnsm) +$existingFilter = $filtersXml.SelectSingleNode("//msb:Filter[@Include='$filterName']", $fnsm) +$filtersModified = $false + +if (-not $existingFilter) { + if ($PSCmdlet.ShouldProcess($filtersFile, "Add for $filterName")) { + $newGuid = [System.Guid]::NewGuid().ToString().ToLower() + $filterEl = $filtersXml.CreateElement('Filter', $ns) + $filterEl.SetAttribute('Include', $filterName) + $guidEl = $filtersXml.CreateElement('UniqueIdentifier', $ns) + $guidEl.InnerText = "{$newGuid}" + $filterEl.AppendChild($guidEl) | Out-Null + $filterGroup.AppendChild($filterEl) | Out-Null + $filtersModified = $true + Write-Host " Added for $filterName (GUID: {$newGuid})" + } +} else { + Write-Host " for $filterName already present." +} + +# Add entries with Filter children +$noneFilterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $fnsm) +if (-not $noneFilterGroup) { + $noneFilterGroup = $filtersXml.CreateElement('ItemGroup', $ns) + $filtersXml.DocumentElement.AppendChild($noneFilterGroup) | Out-Null +} + +foreach ($type in $schemaTypes) { + $versionedInclude = "$vcxRelBase\v$version\manifest.$type.$version.json" + if ($filtersXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $fnsm)) { + Write-Host " manifest.$type v$version filter entry already present." + continue + } + + if ($PSCmdlet.ShouldProcess($filtersFile, "Add filter entry for manifest.$type v$version")) { + $noneEl = $filtersXml.CreateElement('None', $ns) + $noneEl.SetAttribute('Include', $versionedInclude) + $filterChild = $filtersXml.CreateElement('Filter', $ns) + $filterChild.InnerText = $filterName + $noneEl.AppendChild($filterChild) | Out-Null + $noneFilterGroup.AppendChild($noneEl) | Out-Null + $filtersModified = $true + Write-Host " Added filter entry for manifest.$type.$version.json" + } +} + +if ($filtersModified -and $PSCmdlet.ShouldProcess($filtersFile, 'Save')) { + Save-XmlFile $filtersXml $filtersFile + Write-Host " Saved $filtersFile" +} + +# -- Summary -------------------------------------------------------------------- +$major, $minor = $version -split '\.' | Select-Object -First 2 +$cppSuffix = "V${major}_${minor}" + +Write-Host '' +Write-Host '=== Done ===' +Write-Host '' +Write-Host 'Manual steps remaining (if not already done in a prior PR):' +Write-Host '' +Write-Host " 1. src\AppInstallerCommonCore\Public\winget\ManifestCommon.h" +Write-Host " Add: constexpr std::string_view s_ManifestVersion${cppSuffix} = `"$version`"sv;" +Write-Host '' +Write-Host " 2. src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp" +Write-Host " Prepend a new 'if (manifestVersion >= ManifestVer{ s_ManifestVersion${cppSuffix} })'" +Write-Host " block at the top of the version-check chain, using IDX_MANIFEST_SCHEMA_${cppSuffix}_* constants." +Write-Host '' +Write-Host " 3. src\ManifestSchema\ManifestSchema.h" +Write-Host " Add five IDX_MANIFEST_SCHEMA_${cppSuffix}_* constants (next available IDs, must stay < 300)." +Write-Host '' +Write-Host " 4. src\WinGetUtilInterop\Manifest\ManifestVersion.cs" +Write-Host " Add: public const string ManifestVersion${cppSuffix} = `"$version`";" +Write-Host '' +Write-Host " 5. src\AppInstallerCLITests\TestData\" +Write-Host " Add ManifestV${major}_${minor}-Singleton.yaml and ManifestV${major}_${minor}-MultiFile-*.yaml." +Write-Host '' +Write-Host " 6. src\AppInstallerCLITests\AppInstallerCLITests.vcxproj and .vcxproj.filters" +Write-Host " Reference the new test manifest files." +Write-Host '' +Write-Host " 7. src\AppInstallerCLITests\YamlManifest.cpp" +Write-Host " Add test cases for manifest version $version." diff --git a/schemas/JSON/manifests/README.md b/schemas/JSON/manifests/README.md new file mode 100644 index 0000000000..0eb2f6e2bb --- /dev/null +++ b/schemas/JSON/manifests/README.md @@ -0,0 +1,175 @@ +# WinGet Manifest Schemas + +This directory contains JSON schemas for WinGet package manifests. + +## Directory Structure + +| Directory | Contents | +|-----------|----------| +| `latest/` | The in-development schema. File names use `latest` as the version token. The `$id` field in each file contains the **current version string** (e.g., `1.28.0`). | +| `v{X.Y.0}/` | Frozen snapshots. File names use the numeric version. Content is identical to `latest/` at the moment of branching. | +| `preview/` | Legacy v0.1.0 preview schema. | + +Each version directory contains five schema files: + +| File (using `latest/` as an example) | Manifest type | +|---------------------------------------|---------------| +| `manifest.singleton.latest.json` | Single-file manifest | +| `manifest.version.latest.json` | Multi-file: version manifest | +| `manifest.installer.latest.json` | Multi-file: installer manifest | +| `manifest.defaultLocale.latest.json` | Multi-file: default locale manifest | +| `manifest.locale.latest.json` | Multi-file: locale manifest | + +--- + +## Branching `latest/` to a Frozen Version + +When the current `latest/` schemas are ready to be frozen (typically as a WinGet release +approaches), branch them into a numbered version directory. The version to use is embedded in +the `$id` field of each `latest/` file—for example: + +``` +"$id": "https://aka.ms/winget-manifest.installer.1.28.0.schema.json" + ^^^^^^ + This is the version. +``` + +> **Helper script**: `Branch-LatestManifestSchema.ps1` in this directory automates +> the schema-file and project-file steps (1–4 below). The C++ source and test steps +> still require manual edits. + +### 1. Create the versioned schema directory + +Create `schemas/JSON/manifests/v{VERSION}/` and copy each `latest/` file with the version +token substituted for `latest`: + +| Source (`latest/`) | Destination (`v1.28.0/` example) | +|---------------------|----------------------------------| +| `manifest.singleton.latest.json` | `manifest.singleton.1.28.0.json` | +| `manifest.version.latest.json` | `manifest.version.1.28.0.json` | +| `manifest.installer.latest.json` | `manifest.installer.1.28.0.json` | +| `manifest.defaultLocale.latest.json` | `manifest.defaultLocale.1.28.0.json` | +| `manifest.locale.latest.json` | `manifest.locale.1.28.0.json` | + +The JSON content does **not** need editing; the `$id` already contains the version string. + +### 2. Update `src/ManifestSchema/ManifestSchema.rc` + +If the resource IDs for this version currently point to `latest/` (as they will if the C++ work +was done in a prior "add new version" PR), redirect them to the versioned files: + +``` +# Before +IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\..\schemas\JSON\manifests\latest\manifest.singleton.latest.json" + +# After +IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\..\schemas\JSON\manifests\v1.28.0\manifest.singleton.1.28.0.json" +``` + +Repeat for all five manifest types. + +### 3. Update `src/ManifestSchema/ManifestSchema.vcxitems` + +Add five `` entries for the new versioned files inside the existing `` that +contains the `` elements. The `latest/` entries should remain unchanged. + +```xml + + + + + +``` + +### 4. Update `src/ManifestSchema/ManifestSchema.vcxitems.filters` + +Add a `` entry for the new version directory, then add five `` items that +reference the versioned files and assign them to that filter: + +```xml + + + {new-guid-here} + + + + + schema\v1.28.0 + + +``` + +--- + +## C++ and Test Changes + +The four steps above cover the schema and project files. Depending on whether the C++ +infrastructure was already added in a prior PR (pointing to `latest/`), some or all of the +following may also be needed in the same PR as the branching step. + +### `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` + +Add a version constant for the new version, following the pattern of existing entries: + +```cpp +// V1.N manifest version +constexpr std::string_view s_ManifestVersionV1_N = "1.N.0"sv; +``` + +### `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` + +Prepend a new `if` block at the top of the version-check chain so that manifests at this +version or higher use the new schema resources: + +```cpp +if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N }) +{ + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_N_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_N_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_N_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_N_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_N_LOCALE }, + }; +} +else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_ }) +// ... +``` + +### `src/ManifestSchema/ManifestSchema.h` + +Add five resource-ID constants, continuing the numeric sequence (IDs must stay below 300, +per the comment in that file): + +```c +#define IDX_MANIFEST_SCHEMA_V1_N_SINGLETON NNN +#define IDX_MANIFEST_SCHEMA_V1_N_VERSION NNN+1 +#define IDX_MANIFEST_SCHEMA_V1_N_INSTALLER NNN+2 +#define IDX_MANIFEST_SCHEMA_V1_N_DEFAULTLOCALE NNN+3 +#define IDX_MANIFEST_SCHEMA_V1_N_LOCALE NNN+4 +``` + +### `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` + +Add a version constant: + +```csharp +/// +/// V1.N manifest version. +/// +public const string ManifestVersionV1_N = "1.N.0"; +``` + +### Test manifests and project files + +Add representative YAML manifests that exercise the new schema version: + +- `src/AppInstallerCLITests/TestData/ManifestV1_N-Singleton.yaml` +- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-Version.yaml` +- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-Installer.yaml` +- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-DefaultLocale.yaml` +- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-Locale.yaml` + +Reference each file in `src/AppInstallerCLITests/AppInstallerCLITests.vcxproj` and +`.vcxproj.filters`, and add corresponding test cases in +`src/AppInstallerCLITests/YamlManifest.cpp`. From dfea521c865bbb570a20e7e71b46ad061c3abb57 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 11 Jun 2026 11:03:03 -0700 Subject: [PATCH 2/5] Add ability to move minor version forward as well --- ...manifest-schema-versioning.instructions.md | 72 ++- .../manifests/Branch-LatestManifestSchema.ps1 | 601 +++++++++++++----- schemas/JSON/manifests/README.md | 107 +++- 3 files changed, 572 insertions(+), 208 deletions(-) diff --git a/.github/instructions/manifest-schema-versioning.instructions.md b/.github/instructions/manifest-schema-versioning.instructions.md index cac1fd55c5..36ac543b6c 100644 --- a/.github/instructions/manifest-schema-versioning.instructions.md +++ b/.github/instructions/manifest-schema-versioning.instructions.md @@ -6,30 +6,58 @@ applyTo: "schemas/JSON/manifests/**,src/ManifestSchema/**" The `schemas/JSON/manifests/` directory holds JSON schemas for WinGet package manifests. `latest/` is the in-development schema; each `v{X.Y.0}/` directory is a frozen snapshot. +The binary version is defined in `src/binver/binver/version.h` as `{VERSION_MAJOR}.{VERSION_MINOR}.0`. See `schemas/JSON/manifests/README.md` for full documentation. -## Branching `latest/` to a frozen version +## Script -Run `schemas/JSON/manifests/Branch-LatestManifestSchema.ps1` from the repo root to automate -the schema-file and project-file steps. Then verify or complete the manual items below. +`schemas/JSON/manifests/Branch-LatestManifestSchema.ps1` automates most steps in both workflows. -### Files to update +```powershell +# Freeze only (no version bump, C++ changes are manual). +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -| File | Change | -|------|--------| -| `schemas/JSON/manifests/v{VERSION}/` | **Create** — copy each `latest/manifest.*.latest.json` to `manifest.*.{VERSION}.json`. No JSON content changes needed; `$id` already contains the version. | -| `src/ManifestSchema/ManifestSchema.h` | **Add** five `#define IDX_MANIFEST_SCHEMA_V{MAJOR}_{MINOR}_*` constants (Singleton/Version/Installer/DefaultLocale/Locale) using the next available IDs (must stay below 300). | -| `src/ManifestSchema/ManifestSchema.rc` | **Update** the five resource entries for this version from `latest/` paths to `v{VERSION}/` paths, **or add** them if they don't exist yet, pointing directly to `v{VERSION}/`. | -| `src/ManifestSchema/ManifestSchema.vcxitems` | **Add** five `` entries alongside the existing `latest/` entries. | -| `src/ManifestSchema/ManifestSchema.vcxitems.filters` | **Add** a `` with a new GUID, and five `` items with `schema\v{VERSION}`. | -| `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` | **Add** `constexpr std::string_view s_ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}"sv;` | -| `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** a new `if (manifestVersion >= ManifestVer{ s_ManifestVersionV{MAJOR}_{MINOR} })` block at the top of the version-check chain with the five `IDX_MANIFEST_SCHEMA_V{MAJOR}_{MINOR}_*` constants. | -| `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** `public const string ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}";` | -| `src/AppInstallerCLITests/TestData/` | **Add** `ManifestV{MAJOR}_{MINOR}-Singleton.yaml` and `ManifestV{MAJOR}_{MINOR}-MultiFile-{Version,Installer,DefaultLocale,Locale}.yaml`. | -| `src/AppInstallerCLITests/AppInstallerCLITests.vcxproj` and `.vcxproj.filters` | **Add** references to the new test manifest files. | -| `src/AppInstallerCLITests/YamlManifest.cpp` | **Add** test cases exercising the new version. | +# Freeze current latest/, then advance schema to match the binary version (automates C++ source edits). +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -### ManifestSchemaValidation.cpp pattern +# Dry run. +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -WhatIf +``` + +## Workflow A: Starting a new schema version (`-BumpVersion`) + +Run when `version.h` has been advanced to a new minor version and the `latest/` schema version +does not yet match. The script automates steps 1–7 below. Step 8 (test manifests) is always manual. + +| Step | File(s) | Change | +|------|---------|--------| +| 1 | `schemas/JSON/manifests/v{OLD}/` | **Create** frozen copy of current `latest/` (only if the folder doesn't exist). | +| 2 | `schemas/JSON/manifests/latest/*.json` | **Replace** every occurrence of the old version string with the new one (`$id` and `description` fields). | +| 3 | `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` | **Add** `constexpr std::string_view s_ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}"sv;` before the "Any new manifest version" comment. | +| 4 | `src/ManifestSchema/ManifestSchema.h` | **Add** five `IDX_MANIFEST_SCHEMA_V{MAJOR}_{MINOR}_*` constants (Singleton/Version/Installer/DefaultLocale/Locale) at the next available IDs (must stay below 300). | +| 5 | `src/ManifestSchema/ManifestSchema.rc` | **Append** five new resource entries pointing to `latest/`. | +| 6 | `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** a new `if (manifestVersion >= ManifestVer{ s_ManifestVersionV{MAJOR}_{MINOR} })` block; old top block becomes `else if`. | +| 7 | `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** `public const string ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}";` | +| 8 | `src/AppInstallerCLITests/TestData/` + vcxproj + YamlManifest.cpp | **Add** test manifests and test cases (**manual**). | + +## Workflow B: Freezing `latest/` at a release + +Run when a WinGet release approaches and the in-flight schema must be locked in. +The script (without `-BumpVersion`) automates steps 1–4 below. Steps 5–8 are only needed if +the C++ infrastructure was not set up in a prior PR. + +| Step | File(s) | Change | +|------|---------|--------| +| 1 | `schemas/JSON/manifests/v{VERSION}/` | **Create** — copy each `latest/manifest.*.latest.json` to `manifest.*.{VERSION}.json`. | +| 2 | `src/ManifestSchema/ManifestSchema.rc` | **Update** five resource entries from `latest/` paths to `v{VERSION}/` paths. | +| 3 | `src/ManifestSchema/ManifestSchema.vcxitems` | **Add** five `` entries. | +| 4 | `src/ManifestSchema/ManifestSchema.vcxitems.filters` | **Add** `` with a new GUID, and five `` items with `schema\v{VERSION}`. | +| 5 | `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` | **Add** version constant (if not already present). | +| 6 | `src/ManifestSchema/ManifestSchema.h` | **Add** five IDX constants (if not already present). | +| 7 | `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** new if-block (if not already present). | +| 8 | `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** version constant (if not already present). | + +## ManifestSchemaValidation.cpp pattern ```cpp if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N }) @@ -45,8 +73,8 @@ if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N }) else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_ }) ``` -### Notes +## Key invariants -- If C++ infrastructure (ManifestCommon.h, ManifestSchemaValidation.cpp, ManifestSchema.h, ManifestVersion.cs) was already added in a prior PR pointing to `latest/`, only update ManifestSchema.rc to redirect from `latest/` to the versioned path; the constants and logic stay the same. -- Resource IDs in `ManifestSchema.h` are numbered sequentially in groups of 5 (one group per schema version). The comment near the top warns they must stay below 300. -- The `latest/` `` entries in `ManifestSchema.vcxitems` are never removed; versioned entries are added alongside them. +- `latest/` `` entries in `ManifestSchema.vcxitems` are **never removed**; versioned entries are added alongside them. +- `ManifestSchema.rc` entries for the **current** version always point to `latest/`; entries for all **prior** versions point to their `v{VERSION}/` directories. +- Resource IDs in `ManifestSchema.h` are sequential groups of 5, must stay below 300. diff --git a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 index 32fbdb129e..8657ce5de4 100644 --- a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 +++ b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 @@ -1,35 +1,61 @@ <# .SYNOPSIS - Branches the 'latest' manifest schemas into a frozen, versioned folder. + Branches the 'latest' manifest schemas and/or bumps the schema version to match the binary. .DESCRIPTION - Reads the schema version from the $id field in latest/manifest.installer.latest.json, - creates schemas/JSON/manifests/v{VERSION}/ with properly named copies of each latest/ - schema file, and updates ManifestSchema.rc, ManifestSchema.vcxitems, and - ManifestSchema.vcxitems.filters to reference the versioned files. - - C++ source changes (ManifestCommon.h, ManifestSchemaValidation.cpp, ManifestSchema.h, - ManifestVersion.cs) and test manifests require manual updates - see README.md. + Without -BumpVersion: + Freezes the current 'latest/' schemas into a versioned folder (e.g. v1.28.0/), and + updates ManifestSchema.rc, ManifestSchema.vcxitems, and ManifestSchema.vcxitems.filters + to reference the now-versioned files. Use this when a WinGet release is approaching and + the in-flight schema needs to be locked in. + + With -BumpVersion: + Reads the target version from src/binver/binver/version.h (MAJOR.MINOR.0) and compares + it against the version embedded in the 'latest/' schema $id fields. When they differ: + 1. Branches the current 'latest/' schema if a versioned folder does not yet exist. + 2. Updates every $id and description string in the 'latest/' JSON files to the new version. + 3. Adds the new version constant to ManifestCommon.h. + 4. Adds five new IDX_MANIFEST_SCHEMA_* constants to ManifestSchema.h. + 5. Appends new resource entries (pointing to 'latest/') to ManifestSchema.rc. + 6. Prepends a new version block to the if-chain in ManifestSchemaValidation.cpp. + 7. Adds the new version constant to ManifestVersion.cs. + When they already match, reports that no bump is needed and exits. + + C++ test manifests (ManifestV*-Singleton.yaml etc.), the test project files, and + YamlManifest.cpp always require manual updates -- see README.md. + +.PARAMETER BumpVersion + Also advance the 'latest/' schema version to match src/binver/binver/version.h and + update all associated source files. .PARAMETER RepoRoot Path to the repository root. Defaults to three directories above this script, which is correct when the script lives at schemas/JSON/manifests/Branch-LatestManifestSchema.ps1. .EXAMPLE + # Freeze the in-flight schema at its current version number. .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 .EXAMPLE - .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -WhatIf + # Freeze then advance the schema version to match the binary. + .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion + +.EXAMPLE + .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -WhatIf #> [CmdletBinding(SupportsShouldProcess)] param( + [switch] $BumpVersion, [string] $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..') -ErrorAction Stop).Path ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' -# -- Helper: save XML preserving encoding -------------------------------------- +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + function Save-XmlFile { param([xml]$Xml, [string]$Path) $settings = [System.Xml.XmlWriterSettings]@{ @@ -42,13 +68,33 @@ function Save-XmlFile { finally { $writer.Close() } } -# -- Resolve paths -------------------------------------------------------------- -$manifestsDir = Join-Path $RepoRoot 'schemas\JSON\manifests' -$latestDir = Join-Path $manifestsDir 'latest' -$manifestSchemaDir = Join-Path $RepoRoot 'src\ManifestSchema' -$rcFile = Join-Path $manifestSchemaDir 'ManifestSchema.rc' -$vcxitemsFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems' -$filtersFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems.filters' +function Save-TextFile { + param([string]$Content, [string]$Path) + [System.IO.File]::WriteAllText($Path, $Content, (New-Object System.Text.UTF8Encoding $false)) +} + +# Derive the C++ identifier suffix from a version string: "1.29.0" -> "V1_29" +function Get-CppSuffix { + param([string]$Version) + $parts = $Version -split '\.' + return "V$($parts[0])_$($parts[1])" +} + +# --------------------------------------------------------------------------- +# Resolve paths +# --------------------------------------------------------------------------- + +$manifestsDir = Join-Path $RepoRoot 'schemas\JSON\manifests' +$latestDir = Join-Path $manifestsDir 'latest' +$manifestSchemaDir = Join-Path $RepoRoot 'src\ManifestSchema' +$rcFile = Join-Path $manifestSchemaDir 'ManifestSchema.rc' +$vcxitemsFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems' +$filtersFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems.filters' +$manifestCommonH = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Public\winget\ManifestCommon.h' +$schemaValidationCpp = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp' +$manifestSchemaH = Join-Path $RepoRoot 'src\ManifestSchema\ManifestSchema.h' +$manifestVersionCs = Join-Path $RepoRoot 'src\WinGetUtilInterop\Manifest\ManifestVersion.cs' +$binverH = Join-Path $RepoRoot 'src\binver\binver\version.h' foreach ($path in $latestDir, $rcFile, $vcxitemsFile, $filtersFile) { if (-not (Test-Path $path)) { @@ -56,7 +102,10 @@ foreach ($path in $latestDir, $rcFile, $vcxitemsFile, $filtersFile) { } } -# -- Detect version from latest/manifest.installer.latest.json ----------------- +# --------------------------------------------------------------------------- +# Read schema version from latest/ +# --------------------------------------------------------------------------- + $installerSchema = Join-Path $latestDir 'manifest.installer.latest.json' $schemaJson = Get-Content $installerSchema -Raw | ConvertFrom-Json $idField = $schemaJson.'$id' @@ -64,198 +113,422 @@ $idField = $schemaJson.'$id' if ($idField -notmatch '(\d+\.\d+\.\d+)\.schema\.json$') { throw "Could not parse version from `$id field: '$idField'" } -$version = $Matches[1] -$versionedDir = Join-Path $manifestsDir "v$version" - -Write-Host "Schema version detected: $version" -Write-Host "Target directory: $versionedDir" -Write-Host '' +$schemaVersion = $Matches[1] +$versionedDir = Join-Path $manifestsDir "v$schemaVersion" -if (Test-Path $versionedDir) { - Write-Warning "Directory '$versionedDir' already exists - files may be overwritten." -} +Write-Host "Current schema version: $schemaVersion" -# -- Schema file types ---------------------------------------------------------- -$schemaTypes = @('singleton', 'version', 'installer', 'defaultLocale', 'locale') +# --------------------------------------------------------------------------- +# If -BumpVersion: read binary version and decide what to do +# --------------------------------------------------------------------------- -# Relative path prefix used in ManifestSchema.rc (relative to src\ManifestSchema\) -$rcRelBase = '..\..\schemas\JSON\manifests' +$newVersion = $null -# Include-path prefix used in ManifestSchema.vcxitems / .vcxitems.filters -$vcxRelBase = '$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests' +if ($BumpVersion) { + foreach ($path in $binverH, $manifestCommonH, $schemaValidationCpp, $manifestSchemaH, $manifestVersionCs) { + if (-not (Test-Path $path)) { + throw "Required path not found for -BumpVersion: '$path'. Verify -RepoRoot is correct." + } + } -$ns = 'http://schemas.microsoft.com/developer/msbuild/2003' + $binverContent = Get-Content $binverH -Raw + if ($binverContent -notmatch '#define VERSION_MAJOR\s+(\d+)') { throw "Could not parse VERSION_MAJOR from version.h" } + $binMajor = $Matches[1] + if ($binverContent -notmatch '#define VERSION_MINOR\s+(\d+)') { throw "Could not parse VERSION_MINOR from version.h" } + $binMinor = $Matches[1] + $newVersion = "$binMajor.$binMinor.0" -# -- Step 1: Copy and rename schema files --------------------------------------- -Write-Host '=== Step 1: Schema files ===' -if ($PSCmdlet.ShouldProcess($versionedDir, 'Create directory')) { - New-Item -ItemType Directory -Path $versionedDir -Force | Out-Null -} + Write-Host "Binary version: $newVersion" -foreach ($type in $schemaTypes) { - $src = Join-Path $latestDir "manifest.$type.latest.json" - $dest = Join-Path $versionedDir "manifest.$type.$version.json" - if ($PSCmdlet.ShouldProcess("manifest.$type.latest.json -> manifest.$type.$version.json", 'Copy')) { - Copy-Item -Path $src -Destination $dest -Force - Write-Host " Copied manifest.$type.$version.json" + if ($schemaVersion -eq $newVersion) { + Write-Host '' + Write-Host "Schema version already matches the binary version ($newVersion). No bump needed." + exit 0 } + + Write-Host "Schema will be bumped: $schemaVersion -> $newVersion" } -# -- Step 2: Update ManifestSchema.rc ------------------------------------------ Write-Host '' -Write-Host '=== Step 2: ManifestSchema.rc ===' -$rcContent = Get-Content $rcFile -Raw -$rcModified = $false -foreach ($type in $schemaTypes) { - # The RC file uses double-backslash escaping inside quoted strings; use - # String.Replace (literal) rather than -replace (regex) to avoid re-escaping. - $latestPath = ("$rcRelBase\latest\manifest.$type.latest.json").Replace('\', '\\') - $versionedPath = ("$rcRelBase\v$version\manifest.$type.$version.json").Replace('\', '\\') - - if ($rcContent.Contains($latestPath)) { - if ($PSCmdlet.ShouldProcess($rcFile, "Redirect manifest.$type from latest/ to v$version/")) { - $rcContent = $rcContent.Replace($latestPath, $versionedPath) - $rcModified = $true - Write-Host " Updated path for manifest.$type" - } - } elseif ($rcContent.Contains($versionedPath)) { - Write-Host " manifest.$type already points to v$version/ - no change needed." +# --------------------------------------------------------------------------- +# Branch function: freeze current latest/ into v{schemaVersion}/ +# --------------------------------------------------------------------------- + +$schemaTypes = @('singleton', 'version', 'installer', 'defaultLocale', 'locale') +$rcRelBase = '..\..\schemas\JSON\manifests' +$vcxRelBase = '$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests' +$ns = 'http://schemas.microsoft.com/developer/msbuild/2003' + +function Invoke-BranchLatest { + param([string]$Version, [string]$VersionedDir) + + $alreadyBranched = (Test-Path $VersionedDir) -and + ((Get-ChildItem $VersionedDir -Filter "*.json").Count -ge $schemaTypes.Count) + + if ($alreadyBranched) { + Write-Host "=== Branch: v$Version already exists -- skipping schema file copy ===" } else { - Write-Warning " manifest.${type}: neither latest/ nor v${version}/ path found in RC file. Manual edit required." + # Step 1: Schema files + Write-Host '=== Branch Step 1: Schema files ===' + if ($PSCmdlet.ShouldProcess($VersionedDir, 'Create directory')) { + New-Item -ItemType Directory -Path $VersionedDir -Force | Out-Null + } + foreach ($type in $schemaTypes) { + $src = Join-Path $latestDir "manifest.$type.latest.json" + $dest = Join-Path $VersionedDir "manifest.$type.$Version.json" + if ($PSCmdlet.ShouldProcess("manifest.$type.latest.json -> manifest.$type.$Version.json", 'Copy')) { + Copy-Item -Path $src -Destination $dest -Force + Write-Host " Copied manifest.$type.$Version.json" + } + } } -} -if ($rcModified -and $PSCmdlet.ShouldProcess($rcFile, 'Save')) { - [System.IO.File]::WriteAllText($rcFile, $rcContent, (New-Object System.Text.UTF8Encoding $false)) - Write-Host " Saved $rcFile" -} + # Step 2: ManifestSchema.rc -- redirect latest/ entries to versioned paths + Write-Host '' + Write-Host '=== Branch Step 2: ManifestSchema.rc ===' + $rcContent = Get-Content $rcFile -Raw + $rcModified = $false + + foreach ($type in $schemaTypes) { + $latestPath = ("$rcRelBase\latest\manifest.$type.latest.json").Replace('\', '\\') + $versionedPath = ("$rcRelBase\v$Version\manifest.$type.$Version.json").Replace('\', '\\') + + if ($rcContent.Contains($latestPath)) { + if ($PSCmdlet.ShouldProcess($rcFile, "Redirect manifest.$type from latest/ to v$Version/")) { + $rcContent = $rcContent.Replace($latestPath, $versionedPath) + $rcModified = $true + Write-Host " Updated path for manifest.$type" + } + } elseif ($rcContent.Contains($versionedPath)) { + Write-Host " manifest.$type already points to v$Version/ - no change needed." + } else { + Write-Warning " manifest.${type}: neither latest/ nor v${Version}/ path found in RC file. Manual edit required." + } + } -# -- Step 3: Update ManifestSchema.vcxitems ------------------------------------ -Write-Host '' -Write-Host '=== Step 3: ManifestSchema.vcxitems ===' + if ($rcModified -and $PSCmdlet.ShouldProcess($rcFile, 'Save')) { + Save-TextFile $rcContent $rcFile + Write-Host " Saved $rcFile" + } -[xml]$vcxXml = Get-Content $vcxitemsFile -Raw -$nsm = New-Object System.Xml.XmlNamespaceManager($vcxXml.NameTable) -$nsm.AddNamespace('msb', $ns) + # Step 3: ManifestSchema.vcxitems -- add versioned entries + Write-Host '' + Write-Host '=== Branch Step 3: ManifestSchema.vcxitems ===' -$noneGroup = $vcxXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $nsm) -if (-not $noneGroup) { - throw 'Could not locate containing elements in ManifestSchema.vcxitems.' -} + [xml]$vcxXml = Get-Content $vcxitemsFile -Raw + $nsm = New-Object System.Xml.XmlNamespaceManager($vcxXml.NameTable) + $nsm.AddNamespace('msb', $ns) -$vcxModified = $false -foreach ($type in $schemaTypes) { - $versionedInclude = "$vcxRelBase\v$version\manifest.$type.$version.json" + $noneGroup = $vcxXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $nsm) + if (-not $noneGroup) { throw 'Could not locate containing in ManifestSchema.vcxitems.' } + + $vcxModified = $false + foreach ($type in $schemaTypes) { + $versionedInclude = "$vcxRelBase\v$Version\manifest.$type.$Version.json" + if ($vcxXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $nsm)) { + Write-Host " manifest.$type v$Version already present - no change needed." + continue + } + if ($PSCmdlet.ShouldProcess($vcxitemsFile, "Add for manifest.$type v$Version")) { + $el = $vcxXml.CreateElement('None', $ns) + $el.SetAttribute('Include', $versionedInclude) + $noneGroup.AppendChild($el) | Out-Null + $vcxModified = $true + Write-Host " Added for manifest.$type.$Version.json" + } + } + + if ($vcxModified -and $PSCmdlet.ShouldProcess($vcxitemsFile, 'Save')) { + Save-XmlFile $vcxXml $vcxitemsFile + Write-Host " Saved $vcxitemsFile" + } - if ($vcxXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $nsm)) { - Write-Host " manifest.$type v$version already present - no change needed." - continue + # Step 4: ManifestSchema.vcxitems.filters -- add filter + entries + Write-Host '' + Write-Host '=== Branch Step 4: ManifestSchema.vcxitems.filters ===' + + [xml]$filtersXml = Get-Content $filtersFile -Raw + $fnsm = New-Object System.Xml.XmlNamespaceManager($filtersXml.NameTable) + $fnsm.AddNamespace('msb', $ns) + + $filterName = "schema\v$Version" + $filterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:Filter]', $fnsm) + $existingFilter = $filtersXml.SelectSingleNode("//msb:Filter[@Include='$filterName']", $fnsm) + $filtersModified = $false + + if (-not $existingFilter) { + if ($PSCmdlet.ShouldProcess($filtersFile, "Add for $filterName")) { + $newGuid = [System.Guid]::NewGuid().ToString().ToLower() + $filterEl = $filtersXml.CreateElement('Filter', $ns) + $filterEl.SetAttribute('Include', $filterName) + $guidEl = $filtersXml.CreateElement('UniqueIdentifier', $ns) + $guidEl.InnerText = "{$newGuid}" + $filterEl.AppendChild($guidEl) | Out-Null + $filterGroup.AppendChild($filterEl) | Out-Null + $filtersModified = $true + Write-Host " Added for $filterName (GUID: {$newGuid})" + } + } else { + Write-Host " for $filterName already present." } - if ($PSCmdlet.ShouldProcess($vcxitemsFile, "Add for manifest.$type v$version")) { - $el = $vcxXml.CreateElement('None', $ns) - $el.SetAttribute('Include', $versionedInclude) - $noneGroup.AppendChild($el) | Out-Null - $vcxModified = $true - Write-Host " Added for manifest.$type.$version.json" + $noneFilterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $fnsm) + if (-not $noneFilterGroup) { + $noneFilterGroup = $filtersXml.CreateElement('ItemGroup', $ns) + $filtersXml.DocumentElement.AppendChild($noneFilterGroup) | Out-Null + } + + foreach ($type in $schemaTypes) { + $versionedInclude = "$vcxRelBase\v$Version\manifest.$type.$Version.json" + if ($filtersXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $fnsm)) { + Write-Host " manifest.$type v$Version filter entry already present." + continue + } + if ($PSCmdlet.ShouldProcess($filtersFile, "Add filter entry for manifest.$type v$Version")) { + $noneEl = $filtersXml.CreateElement('None', $ns) + $noneEl.SetAttribute('Include', $versionedInclude) + $filterChild = $filtersXml.CreateElement('Filter', $ns) + $filterChild.InnerText = $filterName + $noneEl.AppendChild($filterChild) | Out-Null + $noneFilterGroup.AppendChild($noneEl) | Out-Null + $filtersModified = $true + Write-Host " Added filter entry for manifest.$type.$Version.json" + } + } + + if ($filtersModified -and $PSCmdlet.ShouldProcess($filtersFile, 'Save')) { + Save-XmlFile $filtersXml $filtersFile + Write-Host " Saved $filtersFile" } } -if ($vcxModified -and $PSCmdlet.ShouldProcess($vcxitemsFile, 'Save')) { - Save-XmlFile $vcxXml $vcxitemsFile - Write-Host " Saved $vcxitemsFile" +# --------------------------------------------------------------------------- +# Run branch step +# --------------------------------------------------------------------------- + +Invoke-BranchLatest -Version $schemaVersion -VersionedDir $versionedDir + +# --------------------------------------------------------------------------- +# If not bumping, print manual-step reminder and exit +# --------------------------------------------------------------------------- + +if (-not $BumpVersion) { + $oldSuffix = Get-CppSuffix $schemaVersion + Write-Host '' + Write-Host '=== Done ===' + Write-Host '' + Write-Host 'Manual steps remaining (if not already done in a prior PR):' + Write-Host '' + Write-Host " 1. src\AppInstallerCommonCore\Public\winget\ManifestCommon.h" + Write-Host " Add: constexpr std::string_view s_ManifestVersion${oldSuffix} = `"$schemaVersion`"sv;" + Write-Host '' + Write-Host " 2. src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp" + Write-Host " Prepend a new 'if (manifestVersion >= ManifestVer{ s_ManifestVersion${oldSuffix} })'" + Write-Host " block at the top of the version-check chain." + Write-Host '' + Write-Host " 3. src\ManifestSchema\ManifestSchema.h" + Write-Host " Add five IDX_MANIFEST_SCHEMA_${oldSuffix}_* constants (next available IDs, must stay < 300)." + Write-Host '' + Write-Host " 4. src\WinGetUtilInterop\Manifest\ManifestVersion.cs" + Write-Host " Add: public const string ManifestVersion${oldSuffix} = `"$schemaVersion`";" + Write-Host '' + $parts = $schemaVersion -split '\.' + Write-Host " 5. src\AppInstallerCLITests\TestData\" + Write-Host " Add ManifestV$($parts[0])_$($parts[1])-Singleton.yaml and ManifestV$($parts[0])_$($parts[1])-MultiFile-*.yaml." + Write-Host '' + Write-Host " 6. src\AppInstallerCLITests\AppInstallerCLITests.vcxproj and .vcxproj.filters" + Write-Host " Reference the new test manifest files." + Write-Host '' + Write-Host " 7. src\AppInstallerCLITests\YamlManifest.cpp" + Write-Host " Add test cases for manifest version $schemaVersion." + exit 0 } -# -- Step 4: Update ManifestSchema.vcxitems.filters ---------------------------- +# --------------------------------------------------------------------------- +# BumpVersion: update latest/ JSON files and all source files +# --------------------------------------------------------------------------- + +$oldVersion = $schemaVersion +$oldSuffix = Get-CppSuffix $oldVersion +$newSuffix = Get-CppSuffix $newVersion +$parts = $newVersion -split '\.' +$newMajor = $parts[0] +$newMinor = $parts[1] + +# Bump Step 1: Update $id and description in all latest/ JSON files Write-Host '' -Write-Host '=== Step 4: ManifestSchema.vcxitems.filters ===' - -[xml]$filtersXml = Get-Content $filtersFile -Raw -$fnsm = New-Object System.Xml.XmlNamespaceManager($filtersXml.NameTable) -$fnsm.AddNamespace('msb', $ns) - -$filterName = "schema\v$version" - -# Add the entry if missing -$filterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:Filter]', $fnsm) -$existingFilter = $filtersXml.SelectSingleNode("//msb:Filter[@Include='$filterName']", $fnsm) -$filtersModified = $false - -if (-not $existingFilter) { - if ($PSCmdlet.ShouldProcess($filtersFile, "Add for $filterName")) { - $newGuid = [System.Guid]::NewGuid().ToString().ToLower() - $filterEl = $filtersXml.CreateElement('Filter', $ns) - $filterEl.SetAttribute('Include', $filterName) - $guidEl = $filtersXml.CreateElement('UniqueIdentifier', $ns) - $guidEl.InnerText = "{$newGuid}" - $filterEl.AppendChild($guidEl) | Out-Null - $filterGroup.AppendChild($filterEl) | Out-Null - $filtersModified = $true - Write-Host " Added for $filterName (GUID: {$newGuid})" +Write-Host '=== Bump Step 1: Update latest/ schema files ===' +foreach ($type in $schemaTypes) { + $jsonFile = Join-Path $latestDir "manifest.$type.latest.json" + $content = Get-Content $jsonFile -Raw + if ($content.Contains($oldVersion)) { + if ($PSCmdlet.ShouldProcess($jsonFile, "Replace $oldVersion with $newVersion")) { + $updated = $content.Replace($oldVersion, $newVersion) + Save-TextFile $updated $jsonFile + Write-Host " Updated manifest.$type.latest.json ($oldVersion -> $newVersion)" + } + } else { + Write-Warning " manifest.$type.latest.json: '$oldVersion' not found. Inspect the file manually." } +} + +# Bump Step 2: ManifestCommon.h -- add new version constant +Write-Host '' +Write-Host '=== Bump Step 2: ManifestCommon.h ===' +$mcContent = Get-Content $manifestCommonH -Raw + +$newConstant = " // V$newMajor.$newMinor manifest version`n constexpr std::string_view s_ManifestVersion${newSuffix} = `"$newVersion`"sv;`n" +$insertBefore = ' // Any new manifest version must also be added to' + +if ($mcContent.Contains("s_ManifestVersion${newSuffix}")) { + Write-Host " s_ManifestVersion${newSuffix} already present - no change needed." +} elseif (-not $mcContent.Contains($insertBefore)) { + Write-Warning " Could not locate insertion point in ManifestCommon.h. Manual edit required." } else { - Write-Host " for $filterName already present." + if ($PSCmdlet.ShouldProcess($manifestCommonH, "Add s_ManifestVersion${newSuffix}")) { + $mcContent = $mcContent.Replace($insertBefore, "$newConstant`n$insertBefore") + Save-TextFile $mcContent $manifestCommonH + Write-Host " Added s_ManifestVersion${newSuffix} = `"$newVersion`"sv" + } } -# Add entries with Filter children -$noneFilterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $fnsm) -if (-not $noneFilterGroup) { - $noneFilterGroup = $filtersXml.CreateElement('ItemGroup', $ns) - $filtersXml.DocumentElement.AppendChild($noneFilterGroup) | Out-Null +# Bump Step 3: ManifestSchema.h -- add five new IDX constants +Write-Host '' +Write-Host '=== Bump Step 3: ManifestSchema.h ===' +$mshContent = Get-Content $manifestSchemaH -Raw + +if ($mshContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) { + Write-Host " IDX_MANIFEST_SCHEMA_${newSuffix}_* already present - no change needed." +} else { + # Find the last defined IDX constant ID number to determine the next available IDs. + $allIds = [System.Text.RegularExpressions.Regex]::Matches($mshContent, '#define IDX_MANIFEST_SCHEMA_\w+\s+(\d+)') | + ForEach-Object { [int]$_.Groups[1].Value } + if (-not $allIds) { throw 'Could not parse existing IDX constants from ManifestSchema.h.' } + $nextId = ($allIds | Measure-Object -Maximum).Maximum + 1 + + if ($nextId + 4 -ge 300) { + Write-Warning " Next available ID would be $nextId; approaching the 300 limit. Manual review required." + } + + $newBlock = @" + +#define IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON $nextId +#define IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION $($nextId + 1) +#define IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER $($nextId + 2) +#define IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE $($nextId + 3) +#define IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE $($nextId + 4) + +"@ + # Insert before the "Packages schema starts at 300" comment + $insertBefore = '// Packages schema starts at 300' + if (-not $mshContent.Contains($insertBefore)) { + Write-Warning " Could not locate insertion point in ManifestSchema.h. Manual edit required." + } elseif ($PSCmdlet.ShouldProcess($manifestSchemaH, "Add IDX_MANIFEST_SCHEMA_${newSuffix}_* constants")) { + $mshContent = $mshContent.Replace($insertBefore, "$newBlock$insertBefore") + Save-TextFile $mshContent $manifestSchemaH + Write-Host " Added IDX_MANIFEST_SCHEMA_${newSuffix}_* (IDs $nextId-$($nextId + 4))" + } } -foreach ($type in $schemaTypes) { - $versionedInclude = "$vcxRelBase\v$version\manifest.$type.$version.json" - if ($filtersXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $fnsm)) { - Write-Host " manifest.$type v$version filter entry already present." - continue +# Bump Step 4: ManifestSchema.rc -- append new resource entries pointing to latest/ +Write-Host '' +Write-Host '=== Bump Step 4: ManifestSchema.rc ===' +$rcContent = Get-Content $rcFile -Raw + +if ($rcContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON")) { + Write-Host " IDX_MANIFEST_SCHEMA_${newSuffix}_* already present in RC file - no change needed." +} else { + $rcAppend = @" + +IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" +IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" +IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" +IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" +IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" +"@ + if ($PSCmdlet.ShouldProcess($rcFile, "Append IDX_MANIFEST_SCHEMA_${newSuffix}_* entries")) { + $rcContent = $rcContent.TrimEnd() + $rcAppend + "`n" + Save-TextFile $rcContent $rcFile + Write-Host " Appended IDX_MANIFEST_SCHEMA_${newSuffix}_* entries (pointing to latest/)" } +} + +# Bump Step 5: ManifestSchemaValidation.cpp -- prepend new version block at top of if-chain +Write-Host '' +Write-Host '=== Bump Step 5: ManifestSchemaValidation.cpp ===' +$cppContent = Get-Content $schemaValidationCpp -Raw - if ($PSCmdlet.ShouldProcess($filtersFile, "Add filter entry for manifest.$type v$version")) { - $noneEl = $filtersXml.CreateElement('None', $ns) - $noneEl.SetAttribute('Include', $versionedInclude) - $filterChild = $filtersXml.CreateElement('Filter', $ns) - $filterChild.InnerText = $filterName - $noneEl.AppendChild($filterChild) | Out-Null - $noneFilterGroup.AppendChild($noneEl) | Out-Null - $filtersModified = $true - Write-Host " Added filter entry for manifest.$type.$version.json" +if ($cppContent.Contains("s_ManifestVersion${newSuffix}")) { + Write-Host " s_ManifestVersion${newSuffix} already present - no change needed." +} else { + # The current top of the if-chain begins with "if (manifestVersion >= ManifestVer{ s_ManifestVersionOLD })" + $oldIfLine = " if (manifestVersion >= ManifestVer{ s_ManifestVersion${oldSuffix} })" + if (-not $cppContent.Contains($oldIfLine)) { + Write-Warning " Could not locate '$oldIfLine' in ManifestSchemaValidation.cpp. Manual edit required." + } else { + $newBlock = @" + if (manifestVersion >= ManifestVer{ s_ManifestVersion${newSuffix} }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersion${oldSuffix} }) +"@ + if ($PSCmdlet.ShouldProcess($schemaValidationCpp, "Prepend s_ManifestVersion${newSuffix} block")) { + $cppContent = $cppContent.Replace($oldIfLine, $newBlock) + Save-TextFile $cppContent $schemaValidationCpp + Write-Host " Prepended s_ManifestVersion${newSuffix} block; old block demoted to 'else if'" + } } } -if ($filtersModified -and $PSCmdlet.ShouldProcess($filtersFile, 'Save')) { - Save-XmlFile $filtersXml $filtersFile - Write-Host " Saved $filtersFile" +# Bump Step 6: ManifestVersion.cs -- add new version constant +Write-Host '' +Write-Host '=== Bump Step 6: ManifestVersion.cs ===' +$csContent = Get-Content $manifestVersionCs -Raw + +if ($csContent.Contains("ManifestVersion${newSuffix}")) { + Write-Host " ManifestVersion${newSuffix} already present - no change needed." +} else { + # Insert before the closing #pragma restore line + $insertBefore = '#pragma warning restore SA1310' + $newConstant = @" + + /// + /// V$newMajor.$newMinor manifest version. + /// + public const string ManifestVersion${newSuffix} = "$newVersion"; + +"@ + if (-not $csContent.Contains($insertBefore)) { + Write-Warning " Could not locate insertion point in ManifestVersion.cs. Manual edit required." + } elseif ($PSCmdlet.ShouldProcess($manifestVersionCs, "Add ManifestVersion${newSuffix}")) { + $csContent = $csContent.Replace($insertBefore, "$newConstant$insertBefore") + Save-TextFile $csContent $manifestVersionCs + Write-Host " Added ManifestVersion${newSuffix} = `"$newVersion`"" + } } -# -- Summary -------------------------------------------------------------------- -$major, $minor = $version -split '\.' | Select-Object -First 2 -$cppSuffix = "V${major}_${minor}" +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- Write-Host '' Write-Host '=== Done ===' Write-Host '' -Write-Host 'Manual steps remaining (if not already done in a prior PR):' -Write-Host '' -Write-Host " 1. src\AppInstallerCommonCore\Public\winget\ManifestCommon.h" -Write-Host " Add: constexpr std::string_view s_ManifestVersion${cppSuffix} = `"$version`"sv;" -Write-Host '' -Write-Host " 2. src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp" -Write-Host " Prepend a new 'if (manifestVersion >= ManifestVer{ s_ManifestVersion${cppSuffix} })'" -Write-Host " block at the top of the version-check chain, using IDX_MANIFEST_SCHEMA_${cppSuffix}_* constants." -Write-Host '' -Write-Host " 3. src\ManifestSchema\ManifestSchema.h" -Write-Host " Add five IDX_MANIFEST_SCHEMA_${cppSuffix}_* constants (next available IDs, must stay < 300)." -Write-Host '' -Write-Host " 4. src\WinGetUtilInterop\Manifest\ManifestVersion.cs" -Write-Host " Add: public const string ManifestVersion${cppSuffix} = `"$version`";" +Write-Host 'Manual steps remaining:' Write-Host '' -Write-Host " 5. src\AppInstallerCLITests\TestData\" -Write-Host " Add ManifestV${major}_${minor}-Singleton.yaml and ManifestV${major}_${minor}-MultiFile-*.yaml." +Write-Host " 1. src\AppInstallerCLITests\TestData\" +Write-Host " Add ManifestV${newMajor}_${newMinor}-Singleton.yaml and ManifestV${newMajor}_${newMinor}-MultiFile-*.yaml." Write-Host '' -Write-Host " 6. src\AppInstallerCLITests\AppInstallerCLITests.vcxproj and .vcxproj.filters" +Write-Host " 2. src\AppInstallerCLITests\AppInstallerCLITests.vcxproj and .vcxproj.filters" Write-Host " Reference the new test manifest files." Write-Host '' -Write-Host " 7. src\AppInstallerCLITests\YamlManifest.cpp" -Write-Host " Add test cases for manifest version $version." +Write-Host " 3. src\AppInstallerCLITests\YamlManifest.cpp" +Write-Host " Add test cases for manifest version $newVersion." diff --git a/schemas/JSON/manifests/README.md b/schemas/JSON/manifests/README.md index 0eb2f6e2bb..5e9fc61358 100644 --- a/schemas/JSON/manifests/README.md +++ b/schemas/JSON/manifests/README.md @@ -6,7 +6,7 @@ This directory contains JSON schemas for WinGet package manifests. | Directory | Contents | |-----------|----------| -| `latest/` | The in-development schema. File names use `latest` as the version token. The `$id` field in each file contains the **current version string** (e.g., `1.28.0`). | +| `latest/` | The in-development schema. File names use `latest` as the version token. The `$id` field in each file contains the **current version string** (e.g., `1.29.0`). | | `v{X.Y.0}/` | Frozen snapshots. File names use the numeric version. Content is identical to `latest/` at the moment of branching. | | `preview/` | Legacy v0.1.0 preview schema. | @@ -20,13 +20,75 @@ Each version directory contains five schema files: | `manifest.defaultLocale.latest.json` | Multi-file: default locale manifest | | `manifest.locale.latest.json` | Multi-file: locale manifest | +The version string embedded in the `$id` of each `latest/` file is the authoritative schema +version. The binary version is defined in `src/binver/binver/version.h` as +`{VERSION_MAJOR}.{VERSION_MINOR}.0`. + +--- + +## Helper script + +`Branch-LatestManifestSchema.ps1` (in this directory) automates most of both workflows below. +Run from any directory; it locates the repo root automatically. + +```powershell +# Freeze the current latest/ schema at its version (no source-file changes). +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 + +# Freeze latest/, then advance to match the binary version and update all source files. +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion + +# Preview changes without writing anything. +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -WhatIf +``` + +--- + +## Workflow A: Advancing to a new schema version + +Use this when starting work on a new set of schema changes (e.g. after `version.h` has been +bumped to a new minor version). The goal is to ensure `latest/` carries the new version number +before any schema edits are made. + +**Run the script:** + +```powershell +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion +``` + +The script performs the following automatically: + +1. Reads the target version from `src/binver/binver/version.h` (`MAJOR.MINOR.0`). +2. Reads the current schema version from `latest/manifest.installer.latest.json` (`$id` field). +3. If the versions already match, exits with no changes. +4. If they differ, branches the current schema (see Workflow B steps 1–4 below). +5. Replaces every occurrence of the old version string in all five `latest/` JSON files (`$id` + and `description` fields). +6. Adds a new version constant to `ManifestCommon.h`. +7. Adds five new `IDX_MANIFEST_SCHEMA_*` resource-ID constants to `ManifestSchema.h`. +8. Appends new resource entries (pointing to `latest/`) to `ManifestSchema.rc`. +9. Prepends a new `if (manifestVersion >= ...)` block at the top of the version-check chain in + `ManifestSchemaValidation.cpp`, converting the old top block to an `else if`. +10. Adds a new version constant to `ManifestVersion.cs`. + +**Manual steps always required (not automated):** + +- Add representative YAML test manifests: + - `src/AppInstallerCLITests/TestData/ManifestV{MAJOR}_{MINOR}-Singleton.yaml` + - `src/AppInstallerCLITests/TestData/ManifestV{MAJOR}_{MINOR}-MultiFile-{Version,Installer,DefaultLocale,Locale}.yaml` +- Reference each file in `src/AppInstallerCLITests/AppInstallerCLITests.vcxproj` and + `.vcxproj.filters`. +- Add corresponding test cases in `src/AppInstallerCLITests/YamlManifest.cpp`. + --- -## Branching `latest/` to a Frozen Version +## Workflow B: Freezing `latest/` into a numbered version + +Use this when a WinGet release is approaching and the in-flight schema needs to be locked in — +i.e., `latest/` is frozen at its current version number and subsequent changes will target the +next version. -When the current `latest/` schemas are ready to be frozen (typically as a WinGet release -approaches), branch them into a numbered version directory. The version to use is embedded in -the `$id` field of each `latest/` file—for example: +The version to freeze is embedded in the `$id` field of each `latest/` file — for example: ``` "$id": "https://aka.ms/winget-manifest.installer.1.28.0.schema.json" @@ -34,11 +96,13 @@ the `$id` field of each `latest/` file—for example: This is the version. ``` -> **Helper script**: `Branch-LatestManifestSchema.ps1` in this directory automates -> the schema-file and project-file steps (1–4 below). The C++ source and test steps -> still require manual edits. +**Run the script (automates steps 1–4):** + +```powershell +.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 +``` -### 1. Create the versioned schema directory +### Step 1 — Create the versioned schema directory Create `schemas/JSON/manifests/v{VERSION}/` and copy each `latest/` file with the version token substituted for `latest`: @@ -53,10 +117,9 @@ token substituted for `latest`: The JSON content does **not** need editing; the `$id` already contains the version string. -### 2. Update `src/ManifestSchema/ManifestSchema.rc` +### Step 2 — Update `src/ManifestSchema/ManifestSchema.rc` -If the resource IDs for this version currently point to `latest/` (as they will if the C++ work -was done in a prior "add new version" PR), redirect them to the versioned files: +Redirect the five resource entries for this version from `latest/` to the versioned paths: ``` # Before @@ -68,7 +131,7 @@ IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\..\schem Repeat for all five manifest types. -### 3. Update `src/ManifestSchema/ManifestSchema.vcxitems` +### Step 3 — Update `src/ManifestSchema/ManifestSchema.vcxitems` Add five `` entries for the new versioned files inside the existing `` that contains the `` elements. The `latest/` entries should remain unchanged. @@ -81,7 +144,7 @@ contains the `` elements. The `latest/` entries should remain unchanged. ``` -### 4. Update `src/ManifestSchema/ManifestSchema.vcxitems.filters` +### Step 4 — Update `src/ManifestSchema/ManifestSchema.vcxitems.filters` Add a `` entry for the new version directory, then add five `` items that reference the versioned files and assign them to that filter: @@ -101,15 +164,15 @@ reference the versioned files and assign them to that filter: --- -## C++ and Test Changes +## C++ and test changes (both workflows) -The four steps above cover the schema and project files. Depending on whether the C++ -infrastructure was already added in a prior PR (pointing to `latest/`), some or all of the -following may also be needed in the same PR as the branching step. +If the C++ infrastructure for a version was **not** already added in a prior PR (as happens +when branching occurs separately from the initial feature work), these files also need changes. +When using `-BumpVersion`, the script handles all of these except the test manifests. ### `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` -Add a version constant for the new version, following the pattern of existing entries: +Add a version constant before the "Any new manifest version" comment: ```cpp // V1.N manifest version @@ -119,7 +182,7 @@ constexpr std::string_view s_ManifestVersionV1_N = "1.N.0"sv; ### `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` Prepend a new `if` block at the top of the version-check chain so that manifests at this -version or higher use the new schema resources: +version or higher use the new schema resources. The previous top-level `if` becomes `else if`: ```cpp if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N }) @@ -138,8 +201,8 @@ else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_ }) ### `src/ManifestSchema/ManifestSchema.h` -Add five resource-ID constants, continuing the numeric sequence (IDs must stay below 300, -per the comment in that file): +Add five resource-ID constants, continuing the numeric sequence. IDs must stay below 300 +(per the comment in that file): ```c #define IDX_MANIFEST_SCHEMA_V1_N_SINGLETON NNN From 4d7d92bebef73fd9d536b87e85bdfee43be3aa31 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 11 Jun 2026 11:20:02 -0700 Subject: [PATCH 3/5] lines --- schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 index 8657ce5de4..e532c04888 100644 --- a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 +++ b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 @@ -164,7 +164,7 @@ function Invoke-BranchLatest { param([string]$Version, [string]$VersionedDir) $alreadyBranched = (Test-Path $VersionedDir) -and - ((Get-ChildItem $VersionedDir -Filter "*.json").Count -ge $schemaTypes.Count) + (@(Get-ChildItem $VersionedDir -Filter "*.json" -ErrorAction SilentlyContinue).Count -ge $schemaTypes.Count) if ($alreadyBranched) { Write-Host "=== Branch: v$Version already exists -- skipping schema file copy ===" @@ -412,7 +412,6 @@ if ($mshContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) { } $newBlock = @" - #define IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON $nextId #define IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION $($nextId + 1) #define IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER $($nextId + 2) @@ -441,6 +440,7 @@ if ($rcContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON")) { } else { $rcAppend = @" + IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" @@ -499,7 +499,6 @@ if ($csContent.Contains("ManifestVersion${newSuffix}")) { # Insert before the closing #pragma restore line $insertBefore = '#pragma warning restore SA1310' $newConstant = @" - /// /// V$newMajor.$newMinor manifest version. /// From 46bd6346f403f0c2136f30cbbfc69ca31464fe93 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Thu, 11 Jun 2026 11:27:26 -0700 Subject: [PATCH 4/5] more lines --- schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 index e532c04888..673e8d0628 100644 --- a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 +++ b/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 @@ -418,6 +418,7 @@ if ($mshContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) { #define IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE $($nextId + 3) #define IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE $($nextId + 4) + "@ # Insert before the "Packages schema starts at 300" comment $insertBefore = '// Packages schema starts at 300' @@ -504,6 +505,7 @@ if ($csContent.Contains("ManifestVersion${newSuffix}")) { /// public const string ManifestVersion${newSuffix} = "$newVersion"; + "@ if (-not $csContent.Contains($insertBefore)) { Write-Warning " Could not locate insertion point in ManifestVersion.cs. Manual edit required." From afb32c6ed3d65c56317c9f4fd807d2a64e30ac58 Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Fri, 12 Jun 2026 11:04:02 -0700 Subject: [PATCH 5/5] PR feedback --- .github/actions/spelling/allow.txt | 2 + ...manifest-schema-versioning.instructions.md | 11 +- ...s1 => Checkpoint-LatestManifestSchema.ps1} | 129 +++++++----------- schemas/JSON/manifests/README.md | 46 ++----- 4 files changed, 64 insertions(+), 124 deletions(-) rename schemas/JSON/manifests/{Branch-LatestManifestSchema.ps1 => Checkpoint-LatestManifestSchema.ps1} (79%) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 3666150db0..4559c9503f 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -180,6 +180,7 @@ Minimatch Moq motw mrm +msb msdata MSHCTX MSHLFLAGS @@ -197,6 +198,7 @@ ncacn Nelon netstandard newid +NNN NOCASE NOCLOSEPROCESS nodiscard diff --git a/.github/instructions/manifest-schema-versioning.instructions.md b/.github/instructions/manifest-schema-versioning.instructions.md index 36ac543b6c..e89802c57c 100644 --- a/.github/instructions/manifest-schema-versioning.instructions.md +++ b/.github/instructions/manifest-schema-versioning.instructions.md @@ -1,4 +1,4 @@ ---- +--- applyTo: "schemas/JSON/manifests/**,src/ManifestSchema/**" --- @@ -11,17 +11,17 @@ See `schemas/JSON/manifests/README.md` for full documentation. ## Script -`schemas/JSON/manifests/Branch-LatestManifestSchema.ps1` automates most steps in both workflows. +`schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1` automates most steps in both workflows. ```powershell # Freeze only (no version bump, C++ changes are manual). -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 # Freeze current latest/, then advance schema to match the binary version (automates C++ source edits). -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion # Dry run. -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -WhatIf +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion -WhatIf ``` ## Workflow A: Starting a new schema version (`-BumpVersion`) @@ -38,7 +38,6 @@ does not yet match. The script automates steps 1–7 below. Step 8 (test manifes | 5 | `src/ManifestSchema/ManifestSchema.rc` | **Append** five new resource entries pointing to `latest/`. | | 6 | `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** a new `if (manifestVersion >= ManifestVer{ s_ManifestVersionV{MAJOR}_{MINOR} })` block; old top block becomes `else if`. | | 7 | `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** `public const string ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}";` | -| 8 | `src/AppInstallerCLITests/TestData/` + vcxproj + YamlManifest.cpp | **Add** test manifests and test cases (**manual**). | ## Workflow B: Freezing `latest/` at a release diff --git a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 b/schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1 similarity index 79% rename from schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 rename to schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1 index 673e8d0628..8f630f02ed 100644 --- a/schemas/JSON/manifests/Branch-LatestManifestSchema.ps1 +++ b/schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1 @@ -30,18 +30,18 @@ .PARAMETER RepoRoot Path to the repository root. Defaults to three directories above this script, which is - correct when the script lives at schemas/JSON/manifests/Branch-LatestManifestSchema.ps1. + correct when the script lives at schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1. .EXAMPLE # Freeze the in-flight schema at its current version number. - .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 + .\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 .EXAMPLE # Freeze then advance the schema version to match the binary. - .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion + .\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion .EXAMPLE - .\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -WhatIf + .\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion -WhatIf #> [CmdletBinding(SupportsShouldProcess)] param( @@ -61,7 +61,7 @@ function Save-XmlFile { $settings = [System.Xml.XmlWriterSettings]@{ Indent = $true IndentChars = ' ' - Encoding = New-Object System.Text.UTF8Encoding $false + Encoding = New-Object System.Text.UTF8Encoding $true } $writer = [System.Xml.XmlWriter]::Create($Path, $settings) try { $Xml.Save($writer) } @@ -70,7 +70,7 @@ function Save-XmlFile { function Save-TextFile { param([string]$Content, [string]$Path) - [System.IO.File]::WriteAllText($Path, $Content, (New-Object System.Text.UTF8Encoding $false)) + $Content | Out-File -FilePath $Path -Encoding utf8 -NoNewline } # Derive the C++ identifier suffix from a version string: "1.29.0" -> "V1_29" @@ -84,17 +84,17 @@ function Get-CppSuffix { # Resolve paths # --------------------------------------------------------------------------- -$manifestsDir = Join-Path $RepoRoot 'schemas\JSON\manifests' -$latestDir = Join-Path $manifestsDir 'latest' -$manifestSchemaDir = Join-Path $RepoRoot 'src\ManifestSchema' -$rcFile = Join-Path $manifestSchemaDir 'ManifestSchema.rc' -$vcxitemsFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems' -$filtersFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems.filters' -$manifestCommonH = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Public\winget\ManifestCommon.h' +$manifestsDir = Join-Path $RepoRoot 'schemas\JSON\manifests' +$latestDir = Join-Path $manifestsDir 'latest' +$manifestSchemaDir = Join-Path $RepoRoot 'src\ManifestSchema' +$rcFile = Join-Path $manifestSchemaDir 'ManifestSchema.rc' +$vcxitemsFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems' +$filtersFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems.filters' +$manifestCommonH = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Public\winget\ManifestCommon.h' $schemaValidationCpp = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp' -$manifestSchemaH = Join-Path $RepoRoot 'src\ManifestSchema\ManifestSchema.h' -$manifestVersionCs = Join-Path $RepoRoot 'src\WinGetUtilInterop\Manifest\ManifestVersion.cs' -$binverH = Join-Path $RepoRoot 'src\binver\binver\version.h' +$manifestSchemaH = Join-Path $RepoRoot 'src\ManifestSchema\ManifestSchema.h' +$manifestVersionCs = Join-Path $RepoRoot 'src\WinGetUtilInterop\Manifest\ManifestVersion.cs' +$binverH = Join-Path $RepoRoot 'src\binver\binver\version.h' foreach ($path in $latestDir, $rcFile, $vcxitemsFile, $filtersFile) { if (-not (Test-Path $path)) { @@ -140,7 +140,7 @@ if ($BumpVersion) { Write-Host "Binary version: $newVersion" - if ($schemaVersion -eq $newVersion) { + if ($schemaVersion -eq $newVersion) {sXml Write-Host '' Write-Host "Schema version already matches the binary version ($newVersion). No bump needed." exit 0 @@ -157,10 +157,10 @@ Write-Host '' $schemaTypes = @('singleton', 'version', 'installer', 'defaultLocale', 'locale') $rcRelBase = '..\..\schemas\JSON\manifests' -$vcxRelBase = '$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests' +$vcxitemsRelBase = '$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests' $ns = 'http://schemas.microsoft.com/developer/msbuild/2003' -function Invoke-BranchLatest { +function Invoke-CheckpointLatest { param([string]$Version, [string]$VersionedDir) $alreadyBranched = (Test-Path $VersionedDir) -and @@ -170,7 +170,7 @@ function Invoke-BranchLatest { Write-Host "=== Branch: v$Version already exists -- skipping schema file copy ===" } else { # Step 1: Schema files - Write-Host '=== Branch Step 1: Schema files ===' + Write-Host '=== Checkpoint Step 1: Schema files ===' if ($PSCmdlet.ShouldProcess($VersionedDir, 'Create directory')) { New-Item -ItemType Directory -Path $VersionedDir -Force | Out-Null } @@ -186,7 +186,7 @@ function Invoke-BranchLatest { # Step 2: ManifestSchema.rc -- redirect latest/ entries to versioned paths Write-Host '' - Write-Host '=== Branch Step 2: ManifestSchema.rc ===' + Write-Host '=== Checkpoint Step 2: ManifestSchema.rc ===' $rcContent = Get-Content $rcFile -Raw $rcModified = $false @@ -214,24 +214,24 @@ function Invoke-BranchLatest { # Step 3: ManifestSchema.vcxitems -- add versioned entries Write-Host '' - Write-Host '=== Branch Step 3: ManifestSchema.vcxitems ===' + Write-Host '=== Checkpoint Step 3: ManifestSchema.vcxitems ===' - [xml]$vcxXml = Get-Content $vcxitemsFile -Raw - $nsm = New-Object System.Xml.XmlNamespaceManager($vcxXml.NameTable) - $nsm.AddNamespace('msb', $ns) + [xml]$vcxitemsXml = Get-Content $vcxitemsFile -Raw + $namespaceManager = New-Object System.Xml.XmlNamespaceManager($vcxitemsXml.NameTable) + $namespaceManager.AddNamespace('msb', $ns) - $noneGroup = $vcxXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $nsm) + $noneGroup = $vcxitemsXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $namespaceManager) if (-not $noneGroup) { throw 'Could not locate containing in ManifestSchema.vcxitems.' } $vcxModified = $false foreach ($type in $schemaTypes) { - $versionedInclude = "$vcxRelBase\v$Version\manifest.$type.$Version.json" - if ($vcxXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $nsm)) { + $versionedInclude = "$vcxitemsRelBase\v$Version\manifest.$type.$Version.json" + if ($vcxitemsXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $namespaceManager)) { Write-Host " manifest.$type v$Version already present - no change needed." continue } if ($PSCmdlet.ShouldProcess($vcxitemsFile, "Add for manifest.$type v$Version")) { - $el = $vcxXml.CreateElement('None', $ns) + $el = $vcxitemsXml.CreateElement('None', $ns) $el.SetAttribute('Include', $versionedInclude) $noneGroup.AppendChild($el) | Out-Null $vcxModified = $true @@ -240,21 +240,21 @@ function Invoke-BranchLatest { } if ($vcxModified -and $PSCmdlet.ShouldProcess($vcxitemsFile, 'Save')) { - Save-XmlFile $vcxXml $vcxitemsFile + Save-XmlFile $vcxitemsXml $vcxitemsFile Write-Host " Saved $vcxitemsFile" } # Step 4: ManifestSchema.vcxitems.filters -- add filter + entries Write-Host '' - Write-Host '=== Branch Step 4: ManifestSchema.vcxitems.filters ===' + Write-Host '=== Checkpoint Step 4: ManifestSchema.vcxitems.filters ===' [xml]$filtersXml = Get-Content $filtersFile -Raw - $fnsm = New-Object System.Xml.XmlNamespaceManager($filtersXml.NameTable) - $fnsm.AddNamespace('msb', $ns) + $filtersNamespaceManager = New-Object System.Xml.XmlNamespaceManager($filtersXml.NameTable) + $filtersNamespaceManager.AddNamespace('msb', $ns) $filterName = "schema\v$Version" - $filterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:Filter]', $fnsm) - $existingFilter = $filtersXml.SelectSingleNode("//msb:Filter[@Include='$filterName']", $fnsm) + $filterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:Filter]', $filtersNamespaceManager) + $existingFilter = $filtersXml.SelectSingleNode("//msb:Filter[@Include='$filterName']", $filtersNamespaceManager) $filtersModified = $false if (-not $existingFilter) { @@ -273,15 +273,15 @@ function Invoke-BranchLatest { Write-Host " for $filterName already present." } - $noneFilterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $fnsm) + $noneFilterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $filtersNamespaceManager) if (-not $noneFilterGroup) { $noneFilterGroup = $filtersXml.CreateElement('ItemGroup', $ns) $filtersXml.DocumentElement.AppendChild($noneFilterGroup) | Out-Null } foreach ($type in $schemaTypes) { - $versionedInclude = "$vcxRelBase\v$Version\manifest.$type.$Version.json" - if ($filtersXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $fnsm)) { + $versionedInclude = "$vcxitemsRelBase\v$Version\manifest.$type.$Version.json" + if ($filtersXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $filtersNamespaceManager)) { Write-Host " manifest.$type v$Version filter entry already present." continue } @@ -304,44 +304,18 @@ function Invoke-BranchLatest { } # --------------------------------------------------------------------------- -# Run branch step +# Run checkpoint step # --------------------------------------------------------------------------- -Invoke-BranchLatest -Version $schemaVersion -VersionedDir $versionedDir +Invoke-CheckpointLatest -Version $schemaVersion -VersionedDir $versionedDir # --------------------------------------------------------------------------- # If not bumping, print manual-step reminder and exit # --------------------------------------------------------------------------- if (-not $BumpVersion) { - $oldSuffix = Get-CppSuffix $schemaVersion Write-Host '' Write-Host '=== Done ===' - Write-Host '' - Write-Host 'Manual steps remaining (if not already done in a prior PR):' - Write-Host '' - Write-Host " 1. src\AppInstallerCommonCore\Public\winget\ManifestCommon.h" - Write-Host " Add: constexpr std::string_view s_ManifestVersion${oldSuffix} = `"$schemaVersion`"sv;" - Write-Host '' - Write-Host " 2. src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp" - Write-Host " Prepend a new 'if (manifestVersion >= ManifestVer{ s_ManifestVersion${oldSuffix} })'" - Write-Host " block at the top of the version-check chain." - Write-Host '' - Write-Host " 3. src\ManifestSchema\ManifestSchema.h" - Write-Host " Add five IDX_MANIFEST_SCHEMA_${oldSuffix}_* constants (next available IDs, must stay < 300)." - Write-Host '' - Write-Host " 4. src\WinGetUtilInterop\Manifest\ManifestVersion.cs" - Write-Host " Add: public const string ManifestVersion${oldSuffix} = `"$schemaVersion`";" - Write-Host '' - $parts = $schemaVersion -split '\.' - Write-Host " 5. src\AppInstallerCLITests\TestData\" - Write-Host " Add ManifestV$($parts[0])_$($parts[1])-Singleton.yaml and ManifestV$($parts[0])_$($parts[1])-MultiFile-*.yaml." - Write-Host '' - Write-Host " 6. src\AppInstallerCLITests\AppInstallerCLITests.vcxproj and .vcxproj.filters" - Write-Host " Reference the new test manifest files." - Write-Host '' - Write-Host " 7. src\AppInstallerCLITests\YamlManifest.cpp" - Write-Host " Add test cases for manifest version $schemaVersion." exit 0 } @@ -396,13 +370,13 @@ if ($mcContent.Contains("s_ManifestVersion${newSuffix}")) { # Bump Step 3: ManifestSchema.h -- add five new IDX constants Write-Host '' Write-Host '=== Bump Step 3: ManifestSchema.h ===' -$mshContent = Get-Content $manifestSchemaH -Raw +$manifestSchemaHeaderContent = Get-Content $manifestSchemaH -Raw -if ($mshContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) { +if ($manifestSchemaHeaderContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) { Write-Host " IDX_MANIFEST_SCHEMA_${newSuffix}_* already present - no change needed." } else { # Find the last defined IDX constant ID number to determine the next available IDs. - $allIds = [System.Text.RegularExpressions.Regex]::Matches($mshContent, '#define IDX_MANIFEST_SCHEMA_\w+\s+(\d+)') | + $allIds = [System.Text.RegularExpressions.Regex]::Matches($manifestSchemaHeaderContent, '#define IDX_MANIFEST_SCHEMA_\w+\s+(\d+)') | ForEach-Object { [int]$_.Groups[1].Value } if (-not $allIds) { throw 'Could not parse existing IDX constants from ManifestSchema.h.' } $nextId = ($allIds | Measure-Object -Maximum).Maximum + 1 @@ -422,11 +396,11 @@ if ($mshContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) { "@ # Insert before the "Packages schema starts at 300" comment $insertBefore = '// Packages schema starts at 300' - if (-not $mshContent.Contains($insertBefore)) { + if (-not $manifestSchemaHeaderContent.Contains($insertBefore)) { Write-Warning " Could not locate insertion point in ManifestSchema.h. Manual edit required." } elseif ($PSCmdlet.ShouldProcess($manifestSchemaH, "Add IDX_MANIFEST_SCHEMA_${newSuffix}_* constants")) { - $mshContent = $mshContent.Replace($insertBefore, "$newBlock$insertBefore") - Save-TextFile $mshContent $manifestSchemaH + $manifestSchemaHeaderContent = $manifestSchemaHeaderContent.Replace($insertBefore, "$newBlock$insertBefore") + Save-TextFile $manifestSchemaHeaderContent $manifestSchemaH Write-Host " Added IDX_MANIFEST_SCHEMA_${newSuffix}_* (IDs $nextId-$($nextId + 4))" } } @@ -522,14 +496,3 @@ if ($csContent.Contains("ManifestVersion${newSuffix}")) { Write-Host '' Write-Host '=== Done ===' -Write-Host '' -Write-Host 'Manual steps remaining:' -Write-Host '' -Write-Host " 1. src\AppInstallerCLITests\TestData\" -Write-Host " Add ManifestV${newMajor}_${newMinor}-Singleton.yaml and ManifestV${newMajor}_${newMinor}-MultiFile-*.yaml." -Write-Host '' -Write-Host " 2. src\AppInstallerCLITests\AppInstallerCLITests.vcxproj and .vcxproj.filters" -Write-Host " Reference the new test manifest files." -Write-Host '' -Write-Host " 3. src\AppInstallerCLITests\YamlManifest.cpp" -Write-Host " Add test cases for manifest version $newVersion." diff --git a/schemas/JSON/manifests/README.md b/schemas/JSON/manifests/README.md index 5e9fc61358..2e9a4832be 100644 --- a/schemas/JSON/manifests/README.md +++ b/schemas/JSON/manifests/README.md @@ -1,4 +1,4 @@ -# WinGet Manifest Schemas +# WinGet Manifest Schemas This directory contains JSON schemas for WinGet package manifests. @@ -28,18 +28,18 @@ version. The binary version is defined in `src/binver/binver/version.h` as ## Helper script -`Branch-LatestManifestSchema.ps1` (in this directory) automates most of both workflows below. +`Checkpoint-LatestManifestSchema.ps1` (in this directory) automates most of both workflows below. Run from any directory; it locates the repo root automatically. ```powershell # Freeze the current latest/ schema at its version (no source-file changes). -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 # Freeze latest/, then advance to match the binary version and update all source files. -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion # Preview changes without writing anything. -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion -WhatIf +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion -WhatIf ``` --- @@ -53,7 +53,7 @@ before any schema edits are made. **Run the script:** ```powershell -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 -BumpVersion +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion ``` The script performs the following automatically: @@ -61,7 +61,7 @@ The script performs the following automatically: 1. Reads the target version from `src/binver/binver/version.h` (`MAJOR.MINOR.0`). 2. Reads the current schema version from `latest/manifest.installer.latest.json` (`$id` field). 3. If the versions already match, exits with no changes. -4. If they differ, branches the current schema (see Workflow B steps 1–4 below). +4. If they differ, checkpoints the current schema (see Workflow B steps 1–4 below). 5. Replaces every occurrence of the old version string in all five `latest/` JSON files (`$id` and `description` fields). 6. Adds a new version constant to `ManifestCommon.h`. @@ -71,15 +71,6 @@ The script performs the following automatically: `ManifestSchemaValidation.cpp`, converting the old top block to an `else if`. 10. Adds a new version constant to `ManifestVersion.cs`. -**Manual steps always required (not automated):** - -- Add representative YAML test manifests: - - `src/AppInstallerCLITests/TestData/ManifestV{MAJOR}_{MINOR}-Singleton.yaml` - - `src/AppInstallerCLITests/TestData/ManifestV{MAJOR}_{MINOR}-MultiFile-{Version,Installer,DefaultLocale,Locale}.yaml` -- Reference each file in `src/AppInstallerCLITests/AppInstallerCLITests.vcxproj` and - `.vcxproj.filters`. -- Add corresponding test cases in `src/AppInstallerCLITests/YamlManifest.cpp`. - --- ## Workflow B: Freezing `latest/` into a numbered version @@ -99,7 +90,7 @@ The version to freeze is embedded in the `$id` field of each `latest/` file — **Run the script (automates steps 1–4):** ```powershell -.\schemas\JSON\manifests\Branch-LatestManifestSchema.ps1 +.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 ``` ### Step 1 — Create the versioned schema directory @@ -164,11 +155,10 @@ reference the versioned files and assign them to that filter: --- -## C++ and test changes (both workflows) +## C++ source changes -If the C++ infrastructure for a version was **not** already added in a prior PR (as happens -when branching occurs separately from the initial feature work), these files also need changes. -When using `-BumpVersion`, the script handles all of these except the test manifests. +These files are updated automatically by `-BumpVersion`. They are listed here for reference +when doing the work manually or reviewing what the script changes. ### `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` @@ -222,17 +212,3 @@ Add a version constant: /// public const string ManifestVersionV1_N = "1.N.0"; ``` - -### Test manifests and project files - -Add representative YAML manifests that exercise the new schema version: - -- `src/AppInstallerCLITests/TestData/ManifestV1_N-Singleton.yaml` -- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-Version.yaml` -- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-Installer.yaml` -- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-DefaultLocale.yaml` -- `src/AppInstallerCLITests/TestData/ManifestV1_N-MultiFile-Locale.yaml` - -Reference each file in `src/AppInstallerCLITests/AppInstallerCLITests.vcxproj` and -`.vcxproj.filters`, and add corresponding test cases in -`src/AppInstallerCLITests/YamlManifest.cpp`.