Path: blob/main/build/azure-pipelines/common/checkCopilotChatCompatibility.ts
5241 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import path from 'path';6import fs from 'fs';7import { retry } from './retry.ts';8import { type IExtensionManifest, parseApiProposalsFromSource, checkExtensionCompatibility, areAllowlistedApiProposalsMatching } from './versionCompatibility.ts';910const root = path.dirname(path.dirname(path.dirname(import.meta.dirname)));1112async function fetchLatestExtensionManifest(extensionId: string): Promise<IExtensionManifest> {13// Use the vscode-unpkg service to get the latest extension package.json14const [publisher, name] = extensionId.split('.');1516// First, get the latest version from the gallery endpoint17const galleryUrl = `https://main.vscode-unpkg.net/_gallery/${publisher}/${name}/latest`;18const galleryResponse = await fetch(galleryUrl, {19headers: { 'User-Agent': 'VSCode Build' }20});2122if (!galleryResponse.ok) {23throw new Error(`Failed to fetch latest version for ${extensionId}: ${galleryResponse.status} ${galleryResponse.statusText}`);24}2526const galleryData = await galleryResponse.json() as { versions: { version: string }[] };27const version = galleryData.versions[0].version;2829// Now fetch the package.json using the actual version30const url = `https://${publisher}.vscode-unpkg.net/${publisher}/${name}/${version}/extension/package.json`;3132const response = await fetch(url, {33headers: { 'User-Agent': 'VSCode Build' }34});3536if (!response.ok) {37throw new Error(`Failed to fetch extension ${extensionId} from unpkg: ${response.status} ${response.statusText}`);38}3940return await response.json() as IExtensionManifest;41}4243export async function checkCopilotChatCompatibility(): Promise<void> {44const extensionId = 'github.copilot-chat';4546console.log(`Checking compatibility of ${extensionId}...`);4748// Get product version from package.json49const packageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));50const productVersion = packageJson.version;5152console.log(`Product version: ${productVersion}`);5354// Get API proposals from the generated file55const apiProposalsPath = path.join(root, 'src/vs/platform/extensions/common/extensionsApiProposals.ts');56const apiProposalsContent = fs.readFileSync(apiProposalsPath, 'utf8');57const allApiProposals = parseApiProposalsFromSource(apiProposalsContent);5859const proposalCount = Object.keys(allApiProposals).length;60if (proposalCount === 0) {61throw new Error('Failed to load API proposals from source');62}6364console.log(`Loaded ${proposalCount} API proposals from source`);6566// Load product.json to check allowlisted API proposals67const productJsonPath = path.join(root, 'product.json');68let productJson;69try {70productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8'));71} catch (error) {72throw new Error(`Failed to load or parse product.json: ${error}`);73}74const extensionEnabledApiProposals = productJson?.extensionEnabledApiProposals;75const extensionIdKey = extensionEnabledApiProposals ? Object.keys(extensionEnabledApiProposals).find(key => key.toLowerCase() === extensionId.toLowerCase()) : undefined;76const productAllowlistedProposals = extensionIdKey ? extensionEnabledApiProposals[extensionIdKey] : undefined;7778if (productAllowlistedProposals) {79console.log(`Product.json allowlisted proposals for ${extensionId}:`);80for (const proposal of productAllowlistedProposals) {81console.log(` ${proposal}`);82}83} else {84console.log(`Product.json allowlisted proposals for ${extensionId}: none`);85}8687// Fetch the latest extension manifest88const manifest = await retry(() => fetchLatestExtensionManifest(extensionId));8990console.log(`Extension ${extensionId}@${manifest.version}:`);91console.log(` engines.vscode: ${manifest.engines.vscode}`);92console.log(` enabledApiProposals:\n ${manifest.enabledApiProposals?.join('\n ') || 'none'}`);9394// Check compatibility95const result = checkExtensionCompatibility(productVersion, allApiProposals, manifest);96if (!result.compatible) {97throw new Error(`Compatibility check failed:\n ${result.errors.join('\n ')}`);98}99100console.log(` ✓ Engine version compatible`);101if (manifest.enabledApiProposals?.length) {102console.log(` ✓ API proposals compatible`);103}104105// Check that product.json allowlist matches package.json declarations106const allowlistResult = areAllowlistedApiProposalsMatching(extensionId, productAllowlistedProposals, manifest.enabledApiProposals);107if (!allowlistResult.compatible) {108throw new Error(`Allowlist check failed:\n ${allowlistResult.errors.join('\n ')}`);109}110111console.log(` ✓ Product.json allowlist matches package.json`);112console.log(`✓ ${extensionId} is compatible with this build`);113}114115if (import.meta.main) {116const warnOnly = process.argv.includes('--warn-only');117118checkCopilotChatCompatibility().then(() => {119console.log('Copilot Chat compatibility check passed');120process.exit(0);121}, err => {122if (warnOnly) {123// Issue a warning using Azure DevOps logging commands but don't fail the build124console.log(`##vso[task.logissue type=warning]Copilot Chat compatibility check failed: ${err.message}`);125console.log(`##vso[task.complete result=SucceededWithIssues;]Copilot Chat compatibility check failed`);126console.log('');127console.log(`⚠️ WARNING: ${err.message}`);128console.log('');129console.log('The build will continue, but the release step will fail if this is not resolved.');130process.exit(0);131} else {132console.error(err);133process.exit(1);134}135});136}137138139