Compare commits

...

3 Commits

4 changed files with 756 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
/**
* generate-monaco-languages.js
*
* Headless replacement for the manual generateLanguagesJson.html workflow.
* Serves the Monaco directory on a local HTTP server, then uses Playwright
* to open generateLanguagesJson.html and capture the generated JSON.
*
* Monaco's AMD loader requires a real browser DOM, so we use Playwright
* instead of trying to load it in Node.js directly.
*
* Usage: node generate-monaco-languages.js <path-to-src/Monaco>
*
* Prerequisites: npx playwright install chromium
*/
"use strict";
const path = require("path");
const fs = require("fs");
const http = require("http");
const monacoDir = process.argv[2];
if (!monacoDir) {
console.error("Usage: node generate-monaco-languages.js <monaco-dir>");
process.exit(1);
}
const absMonacoDir = path.resolve(monacoDir);
const outputPath = path.join(absMonacoDir, "monaco_languages.json");
const htmlPath = path.join(absMonacoDir, "generateLanguagesJson.html");
if (!fs.existsSync(htmlPath)) {
console.error(`generateLanguagesJson.html not found at: ${htmlPath}`);
process.exit(1);
}
// MIME types for serving Monaco files
const MIME_TYPES = {
".html": "text/html",
".js": "application/javascript",
".css": "text/css",
".json": "application/json",
".ttf": "font/ttf",
".woff": "font/woff",
".woff2": "font/woff2",
".svg": "image/svg+xml",
};
/**
* Create a simple static file server rooted at absMonacoDir.
*/
function createServer() {
return http.createServer((req, res) => {
const url = new URL(req.url, "http://localhost");
// Resolve the file path relative to absMonacoDir, preventing path traversal
const requestedPath = decodeURIComponent(url.pathname);
const filePath = path.resolve(absMonacoDir, requestedPath.replace(/^\/+/, ""));
// Ensure the resolved path is within absMonacoDir (case-insensitive on Windows)
const normalizedFile = process.platform === "win32" ? filePath.toLowerCase() : filePath;
const normalizedBase = process.platform === "win32" ? absMonacoDir.toLowerCase() : absMonacoDir;
if (!normalizedFile.startsWith(normalizedBase + path.sep) && normalizedFile !== normalizedBase) {
res.writeHead(403);
res.end("Forbidden");
return;
}
if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
res.writeHead(404);
res.end("Not found");
return;
}
const ext = path.extname(filePath).toLowerCase();
const contentType = MIME_TYPES[ext] || "application/octet-stream";
const content = fs.readFileSync(filePath);
res.writeHead(200, { "Content-Type": contentType });
res.end(content);
});
}
async function main() {
// Start local HTTP server
const server = createServer();
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const port = server.address().port;
const baseUrl = `http://127.0.0.1:${port}`;
console.log(`Local server started at ${baseUrl}`);
let browser;
try {
// Launch Playwright browser
const { chromium } = require("playwright");
browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
// Accept downloads so we can capture the generated file
acceptDownloads: true,
});
const page = await context.newPage();
// The generateLanguagesJson.html auto-triggers a download of the JSON.
// We intercept that download event to capture the content.
const downloadPromise = page.waitForEvent("download", { timeout: 30000 });
console.log("Loading generateLanguagesJson.html in headless browser...");
await page.goto(`${baseUrl}/generateLanguagesJson.html`, {
waitUntil: "networkidle",
timeout: 30000,
});
// Wait for the download to be triggered
const download = await downloadPromise;
console.log(`Download triggered: ${download.suggestedFilename()}`);
// Save the downloaded file
const downloadPath = await download.path();
const content = fs.readFileSync(downloadPath, "utf-8");
// Validate the JSON before writing
const parsed = JSON.parse(content);
if (!parsed.list || !Array.isArray(parsed.list) || parsed.list.length === 0) {
throw new Error(
"Generated JSON is invalid: missing or empty 'list' property"
);
}
// Write to output
fs.writeFileSync(outputPath, content, "utf-8");
console.log(
`monaco_languages.json written with ${parsed.list.length} languages.`
);
} finally {
if (browser) {
await browser.close();
}
server.close();
}
}
main().catch((err) => {
console.error("Error:", err.message || err);
process.exit(1);
});

View File

@@ -0,0 +1,328 @@
<#
.SYNOPSIS
Validates that a Monaco Editor update was performed correctly.
.DESCRIPTION
Runs a series of checks against the Monaco Editor files in the repository
to ensure the update is valid and no regressions were introduced.
Tests:
- loader.js exists and contains version info
- monaco_languages.json is valid JSON with expected structure
- All expected built-in Monaco languages are present
- All PowerToys custom languages are registered
- Custom language extension mappings are present
- Monaco directory structure is intact
- No empty/corrupt core files
.PARAMETER RepoRoot
The root of the PowerToys repository. Defaults to the repo root
relative to this script.
.EXAMPLE
./validate-monaco-update.tests.ps1
./validate-monaco-update.tests.ps1 -RepoRoot "C:\src\PowerToys"
#>
[CmdletBinding()]
param(
[Parameter()]
[string]$RepoRoot
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
if (-not $RepoRoot) {
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot ".." ".." "..")).Path
}
$monacoDir = Join-Path $RepoRoot "src" "Monaco"
$monacoSrcDir = Join-Path $monacoDir "monacoSRC"
$minDir = Join-Path $monacoSrcDir "min"
$loaderJsPath = Join-Path $minDir "vs" "loader.js"
$languagesJsonPath = Join-Path $monacoDir "monaco_languages.json"
$specialLangsPath = Join-Path $monacoDir "monacoSpecialLanguages.js"
$customLangsDir = Join-Path $monacoDir "customLanguages"
$testsPassed = 0
$testsFailed = 0
$testResults = @()
function Assert-Test {
param(
[string]$Name,
[scriptblock]$Test
)
try {
$result = & $Test
if ($result -eq $false) {
throw "Assertion returned false"
}
$script:testsPassed++
$script:testResults += [PSCustomObject]@{ Name = $Name; Status = "PASS"; Error = $null }
Write-Host " [PASS] $Name" -ForegroundColor Green
}
catch {
$script:testsFailed++
$script:testResults += [PSCustomObject]@{ Name = $Name; Status = "FAIL"; Error = $_.Exception.Message }
Write-Host " [FAIL] $Name" -ForegroundColor Red
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
}
}
Write-Host "=== Monaco Editor Update Validation ===" -ForegroundColor Cyan
Write-Host "Repository root: $RepoRoot"
Write-Host ""
# ─── Test Group 1: Directory Structure ────────────────────────────────
Write-Host "--- Directory Structure ---" -ForegroundColor Yellow
Assert-Test "Monaco directory exists" {
Test-Path $monacoDir
}
Assert-Test "monacoSRC directory exists" {
Test-Path $monacoSrcDir
}
Assert-Test "min directory exists" {
Test-Path $minDir
}
Assert-Test "vs subdirectory exists" {
Test-Path (Join-Path $minDir "vs")
}
Assert-Test "editor directory exists" {
Test-Path (Join-Path $minDir "vs" "editor")
}
Assert-Test "basic-languages directory exists" {
Test-Path (Join-Path $minDir "vs" "basic-languages")
}
Assert-Test "base directory exists" {
Test-Path (Join-Path $minDir "vs" "base")
}
Assert-Test "language directory exists" {
Test-Path (Join-Path $minDir "vs" "language")
}
Assert-Test "customLanguages directory exists" {
Test-Path $customLangsDir
}
# ─── Test Group 2: Core Files ─────────────────────────────────────────
Write-Host "`n--- Core Files ---" -ForegroundColor Yellow
Assert-Test "loader.js exists" {
Test-Path $loaderJsPath
}
Assert-Test "loader.js is not empty" {
(Get-Item $loaderJsPath).Length -gt 0
}
Assert-Test "loader.js contains version string" {
$content = Get-Content $loaderJsPath -Raw
$content -match 'Version:\s*\d+\.\d+\.\d+'
}
Assert-Test "editor.main.js exists" {
Test-Path (Join-Path $minDir "vs" "editor" "editor.main.js")
}
Assert-Test "editor.main.js is not empty" {
(Get-Item (Join-Path $minDir "vs" "editor" "editor.main.js")).Length -gt 0
}
Assert-Test "editor.main.css exists" {
Test-Path (Join-Path $minDir "vs" "editor" "editor.main.css")
}
Assert-Test "monacoSpecialLanguages.js exists" {
Test-Path $specialLangsPath
}
Assert-Test "generateLanguagesJson.html exists" {
Test-Path (Join-Path $monacoDir "generateLanguagesJson.html")
}
Assert-Test "index.html exists" {
Test-Path (Join-Path $monacoDir "index.html")
}
Assert-Test "customTokenThemeRules.js exists" {
Test-Path (Join-Path $monacoDir "customTokenThemeRules.js")
}
# ─── Test Group 3: monaco_languages.json ──────────────────────────────
Write-Host "`n--- monaco_languages.json ---" -ForegroundColor Yellow
Assert-Test "monaco_languages.json exists" {
Test-Path $languagesJsonPath
}
Assert-Test "monaco_languages.json is not empty" {
(Get-Item $languagesJsonPath).Length -gt 0
}
$languagesJson = $null
Assert-Test "monaco_languages.json is valid JSON" {
$script:languagesJson = Get-Content $languagesJsonPath -Raw | ConvertFrom-Json
$null -ne $script:languagesJson
}
Assert-Test "JSON has 'list' property" {
$null -ne $languagesJson.list
}
Assert-Test "Language list is a non-empty array" {
$languagesJson.list.Count -gt 0
}
# Minimum expected languages from built-in Monaco
# These are a core subset that should always be present
$expectedBuiltinLanguages = @(
"plaintext", "javascript", "typescript", "html", "css", "json",
"xml", "markdown", "yaml", "python", "java", "csharp", "cpp",
"go", "rust", "ruby", "php", "sql", "shell", "powershell",
"dockerfile", "bat", "fsharp", "lua", "r", "swift", "kotlin",
"scala", "perl", "dart", "ini", "vb"
)
Assert-Test "Minimum language count check (at least 80 languages)" {
$languagesJson.list.Count -ge 80
}
$languageIds = $languagesJson.list | ForEach-Object { $_.id }
foreach ($lang in $expectedBuiltinLanguages) {
Assert-Test "Built-in language '$lang' is present" {
$lang -in $languageIds
}
}
# ─── Test Group 4: PowerToys Custom Languages ─────────────────────────
Write-Host "`n--- PowerToys Custom Languages ---" -ForegroundColor Yellow
# Custom languages defined in monacoSpecialLanguages.js
$expectedCustomLanguages = @(
@{ Id = "reg"; Extensions = @(".reg") },
@{ Id = "gitignore"; Extensions = @(".gitignore") },
@{ Id = "srt"; Extensions = @(".srt") }
)
foreach ($custom in $expectedCustomLanguages) {
Assert-Test "Custom language '$($custom.Id)' is registered" {
$custom.Id -in $languageIds
}
foreach ($ext in $custom.Extensions) {
Assert-Test "Custom language '$($custom.Id)' has extension '$ext'" {
$lang = $languagesJson.list | Where-Object { $_.id -eq $custom.Id }
if ($null -eq $lang) { throw "Language not found" }
$ext -in $lang.extensions
}
}
}
# Custom language definition files exist
$expectedCustomFiles = @("reg.js", "gitignore.js", "srt.js")
foreach ($file in $expectedCustomFiles) {
Assert-Test "Custom language file '$file' exists" {
Test-Path (Join-Path $customLangsDir $file)
}
}
# ─── Test Group 5: Custom Language Extensions ─────────────────────────
Write-Host "`n--- Custom Language Extensions ---" -ForegroundColor Yellow
$expectedExtensions = @(
@{ Id = "cppExt"; Extensions = @(".ino", ".pde") },
@{ Id = "xmlExt"; Extensions = @(".wsdl", ".csproj", ".vcxproj", ".vbproj", ".fsproj", ".resx", ".resw") },
@{ Id = "txtExt"; Extensions = @(".sln", ".log") },
@{ Id = "razorExt"; Extensions = @(".razor") },
@{ Id = "vbExt"; Extensions = @(".vbs") },
@{ Id = "iniExt"; Extensions = @(".inf") },
@{ Id = "shellExt"; Extensions = @(".ksh", ".zsh", ".bsh") }
)
foreach ($ext in $expectedExtensions) {
Assert-Test "Extension mapping '$($ext.Id)' is registered" {
$ext.Id -in $languageIds
}
# Spot-check at least one extension from each mapping
$firstExt = $ext.Extensions[0]
Assert-Test "Extension mapping '$($ext.Id)' has extension '$firstExt'" {
$lang = $languagesJson.list | Where-Object { $_.id -eq $ext.Id }
if ($null -eq $lang) { throw "Language not found" }
$firstExt -in $lang.extensions
}
}
# ─── Test Group 6: Language Entry Structure ───────────────────────────
Write-Host "`n--- Language Entry Structure ---" -ForegroundColor Yellow
Assert-Test "Every language has an 'id' field" {
$missing = @($languagesJson.list | Where-Object { -not $_.id -or $_.id.Trim() -eq "" })
$missing.Count -eq 0
}
Assert-Test "No duplicate language IDs" {
$ids = $languagesJson.list | ForEach-Object { $_.id }
$uniqueIds = $ids | Select-Object -Unique
$ids.Count -eq $uniqueIds.Count
}
Assert-Test "JSON language with extensions has array-type extensions" {
$withExtensions = @($languagesJson.list | Where-Object {
($_.PSObject.Properties.Name -contains "extensions") -and ($null -ne $_.extensions)
})
$invalid = @($withExtensions | Where-Object { $_.extensions -isnot [array] })
$invalid.Count -eq 0
}
# ─── Test Group 7: Version Consistency ────────────────────────────────
Write-Host "`n--- Version Consistency ---" -ForegroundColor Yellow
Assert-Test "All NLS files in editor directory have matching versions" {
$editorDir = Join-Path $minDir "vs" "editor"
$nlsFiles = Get-ChildItem -Path $editorDir -Filter "editor.main.nls*.js" -ErrorAction SilentlyContinue
if ($nlsFiles.Count -eq 0) { throw "No NLS files found" }
$loaderContent = Get-Content $loaderJsPath -Raw
$null = $loaderContent -match 'Version:\s*(\d+\.\d+\.\d+)'
$loaderVersion = $Matches[1]
foreach ($nlsFile in $nlsFiles) {
$content = Get-Content $nlsFile.FullName -Raw -ErrorAction SilentlyContinue
if ($content -and $content -match 'Version:\s*(\d+\.\d+\.\d+)') {
if ($Matches[1] -ne $loaderVersion) {
throw "Version mismatch in $($nlsFile.Name): expected $loaderVersion, found $($Matches[1])"
}
}
}
$true
}
# ─── Summary ──────────────────────────────────────────────────────────
Write-Host ""
Write-Host "=== Test Summary ===" -ForegroundColor Cyan
Write-Host "Passed: $testsPassed" -ForegroundColor Green
Write-Host "Failed: $testsFailed" -ForegroundColor $(if ($testsFailed -gt 0) { "Red" } else { "Green" })
Write-Host "Total: $($testsPassed + $testsFailed)"
if ($testsFailed -gt 0) {
Write-Host "`nFailed tests:" -ForegroundColor Red
$testResults | Where-Object { $_.Status -eq "FAIL" } | ForEach-Object {
Write-Host " - $($_.Name): $($_.Error)" -ForegroundColor Red
}
exit 1
}
Write-Host "`nAll tests passed!" -ForegroundColor Green
exit 0

150
.github/scripts/update-monaco-editor.ps1 vendored Normal file
View File

@@ -0,0 +1,150 @@
<#
.SYNOPSIS
Updates the Monaco Editor in PowerToys to the latest (or specified) version.
.DESCRIPTION
This script automates the Monaco Editor update process described in
doc/devdocs/common/FilePreviewCommon.md:
1. Downloads Monaco editor via npm
2. Copies the min folder into src/Monaco/monacoSRC/
3. Generates the monaco_languages.json file using Node.js (headless)
.PARAMETER Version
The Monaco Editor npm version to install. Defaults to "latest".
.PARAMETER RepoRoot
The root of the PowerToys repository. Defaults to the repo root relative to this script.
.EXAMPLE
./update-monaco-editor.ps1
./update-monaco-editor.ps1 -Version "0.50.0"
#>
[CmdletBinding()]
param(
[Parameter()]
[string]$Version = "latest",
[Parameter()]
[string]$RepoRoot
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
if (-not $RepoRoot) {
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot ".." "..")).Path
}
$monacoDir = Join-Path $RepoRoot "src" "Monaco"
$monacoSrcDir = Join-Path $monacoDir "monacoSRC"
$languagesJsonPath = Join-Path $monacoDir "monaco_languages.json"
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "monaco-update-$([System.Guid]::NewGuid().ToString('N').Substring(0, 8))"
Write-Host "=== Monaco Editor Update Script ==="
Write-Host "Repository root: $RepoRoot"
Write-Host "Target version: $Version"
Write-Host "Temp directory: $tempDir"
# Verify prerequisites
$npmPath = Get-Command npm -ErrorAction SilentlyContinue
if (-not $npmPath) {
throw "npm is required but not found in PATH. Please install Node.js."
}
$nodePath = Get-Command node -ErrorAction SilentlyContinue
if (-not $nodePath) {
throw "node is required but not found in PATH. Please install Node.js."
}
# Verify repo structure
if (-not (Test-Path $monacoDir)) {
throw "Monaco directory not found at: $monacoDir"
}
try {
# Step 1: Download Monaco via npm
Write-Host "`n--- Step 1: Downloading Monaco Editor ($Version) via npm ---"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
Push-Location $tempDir
try {
$versionSpec = if ($Version -eq "latest") { "monaco-editor@latest" } else { "monaco-editor@$Version" }
npm init -y 2>&1 | Out-Null
npm install $versionSpec 2>&1
if ($LASTEXITCODE -ne 0) {
throw "npm install failed with exit code $LASTEXITCODE"
}
}
finally {
Pop-Location
}
$downloadedMinDir = Join-Path $tempDir "node_modules" "monaco-editor" "min"
if (-not (Test-Path $downloadedMinDir)) {
throw "Downloaded Monaco min directory not found at: $downloadedMinDir"
}
# Detect the downloaded version from loader.js
$loaderJsPath = Join-Path $downloadedMinDir "vs" "loader.js"
if (-not (Test-Path $loaderJsPath)) {
throw "loader.js not found in downloaded Monaco package"
}
$loaderContent = Get-Content $loaderJsPath -Raw
if ($loaderContent -match 'Version:\s*(\d+\.\d+\.\d+)') {
$newVersion = $Matches[1]
Write-Host "Downloaded Monaco version: $newVersion"
}
else {
Write-Warning "Could not detect version from loader.js"
$newVersion = $Version
}
# Step 2: Replace monacoSRC/min folder
Write-Host "`n--- Step 2: Replacing monacoSRC with new version ---"
$targetMinDir = Join-Path $monacoSrcDir "min"
if (Test-Path $targetMinDir) {
Write-Host "Removing existing min directory..."
Remove-Item -Recurse -Force $targetMinDir
}
Write-Host "Copying new min directory..."
Copy-Item -Recurse -Force $downloadedMinDir $targetMinDir
# Step 3: Generate monaco_languages.json
Write-Host "`n--- Step 3: Generating monaco_languages.json ---"
$generateScript = Join-Path $PSScriptRoot "generate-monaco-languages.js"
# Ensure playwright is available (installed in workflow, but verify here)
$playwrightCheck = npm list playwright 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "Installing playwright for headless browser..."
npm install playwright@latest 2>&1
npx playwright install chromium --with-deps 2>&1
}
node $generateScript $monacoDir
if ($LASTEXITCODE -ne 0) {
throw "Failed to generate monaco_languages.json (exit code: $LASTEXITCODE)"
}
if (-not (Test-Path $languagesJsonPath)) {
throw "monaco_languages.json was not generated at: $languagesJsonPath"
}
Write-Host "`n=== Monaco Editor update complete ==="
Write-Host "Updated to version: $newVersion"
Write-Host "Languages JSON: $languagesJsonPath"
# Output the new version for the workflow to use
Write-Output "MONACO_VERSION=$newVersion"
}
finally {
# Clean up temp directory
if (Test-Path $tempDir) {
Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue
}
}

View File

@@ -0,0 +1,134 @@
# Update Monaco Editor
#
# Automates the Monaco Editor update process described in
# doc/devdocs/common/FilePreviewCommon.md:
# 1. Downloads the latest (or specified) Monaco Editor from npm
# 2. Replaces src/Monaco/monacoSRC/min with the new version
# 3. Regenerates monaco_languages.json
# 4. Runs validation tests
# 5. Creates a pull request with the changes
#
# Trigger manually via workflow_dispatch.
name: Update Monaco Editor
on:
workflow_dispatch:
inputs:
version:
description: 'Monaco Editor version (e.g. "0.50.0"). Leave empty for latest.'
required: false
default: ''
type: string
permissions:
contents: write
pull-requests: write
jobs:
update-monaco:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Playwright
run: |
npm install playwright@latest
npx playwright install chromium --with-deps
- name: Get current Monaco version
id: current_version
shell: bash
run: |
CURRENT=$(grep -oP 'Version:\s*\K[\d.]+' src/Monaco/monacoSRC/min/vs/loader.js || echo "unknown")
echo "version=$CURRENT" >> "$GITHUB_OUTPUT"
echo "Current Monaco version: $CURRENT"
- name: Run Monaco update script
id: update
shell: pwsh
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
$version = $env:INPUT_VERSION
if ([string]::IsNullOrWhiteSpace($version)) {
$version = 'latest'
}
$output = & ./.github/scripts/update-monaco-editor.ps1 -Version $version -RepoRoot $env:GITHUB_WORKSPACE
# Extract version from output
$versionLine = $output | Select-String -Pattern '^MONACO_VERSION=' | Select-Object -First 1
if ($versionLine) {
$newVersion = $versionLine.ToString().Split('=')[1]
echo "new_version=$newVersion" >> $env:GITHUB_OUTPUT
}
- name: Run validation tests
shell: pwsh
run: |
./.github/scripts/tests/validate-monaco-update.tests.ps1 -RepoRoot $env:GITHUB_WORKSPACE
- name: Check for changes
id: changes
shell: bash
run: |
if git diff --quiet; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No changes detected - Monaco may already be up to date."
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
CHANGED_FILES=$(git diff --stat | tail -1)
echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
fi
- name: Create pull request
if: steps.changes.outputs.has_changes == 'true'
# 3rd-party action pinned to commit hash per Microsoft security guidelines
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update Monaco Editor to ${{ steps.update.outputs.new_version }}"
title: "Update Monaco Editor from ${{ steps.current_version.outputs.version }} to ${{ steps.update.outputs.new_version }}"
body: |
## Summary
Automated update of the Monaco Editor dependency.
**Previous version:** ${{ steps.current_version.outputs.version }}
**New version:** ${{ steps.update.outputs.new_version }}
## Changes
- Updated `src/Monaco/monacoSRC/min/` with the new Monaco Editor release
- Regenerated `src/Monaco/monaco_languages.json`
## Validation
The following automated checks passed:
- ✅ `loader.js` contains valid version header
- ✅ `monaco_languages.json` is valid JSON with expected structure
- ✅ All expected built-in languages are present
- ✅ All PowerToys custom languages (reg, gitignore, srt) are present
- ✅ All custom language extensions are registered
- ✅ Monaco directory structure is intact
## Manual Verification
Before merging, please verify:
- [ ] File Explorer Dev File Preview works correctly
- [ ] Peek module previews code files properly
- [ ] Registry Preview module functions normally
## Reference
- [Monaco Editor update docs](doc/devdocs/common/FilePreviewCommon.md#update-monaco-editor)
- [Monaco Editor releases](https://github.com/microsoft/monaco-editor/releases)
branch: automated/update-monaco-editor
delete-branch: true
labels: |
dependencies