diff --git a/esbuild.mjs b/esbuild.mjs index b93b58c19..b323017ea 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -1,5 +1,7 @@ import esbuild from 'esbuild'; import fs from 'node:fs/promises'; +import path from 'node:path'; +import { auditBundle } from './tasks/compilation/bundleAudit.mjs'; const production = process.argv.includes('--production'); const watch = process.argv.includes('--watch'); @@ -42,17 +44,62 @@ const umdEsmLoaderPlugin = { }, }; +/** + * The telemetry package advertises an ESM module but has no exports map, so esbuild's Node + * resolution otherwise selects its CommonJS main entry. + */ +const telemetryEsmLoaderPlugin = { + name: 'telemetryEsmLoaderPlugin', + + setup(build) { + build.onResolve({ filter: /^@vscode\/extension-telemetry$/ }, async () => { + const packageDirectory = path.resolve('node_modules/@vscode/extension-telemetry'); + const packageMetadata = JSON.parse( + await fs.readFile(path.join(packageDirectory, 'package.json'), 'utf8') + ); + if (typeof packageMetadata.module !== 'string') { + throw new Error('@vscode/extension-telemetry no longer declares an ESM module entry.'); + } + + return { path: path.resolve(packageDirectory, packageMetadata.module) }; + }); + }, +}; + +const bundleAuditPlugin = { + name: 'bundleAuditPlugin', + + setup(build) { + build.onEnd(async (result) => { + if (result.errors.length > 0) { + return; + } + + try { + await auditBundle(result.metafile, production); + } catch (error) { + return { + errors: [{ text: error instanceof Error ? error.message : String(error) }], + }; + } + }); + }, +}; + async function main() { const ctx = await esbuild.context({ entryPoints: ['src/main.ts'], bundle: true, format: 'esm', + // Bundled CommonJS dependencies still require Node built-ins at runtime. The exact owners + // are enforced by bundleAuditPlugin so this compatibility bridge cannot grow unnoticed. banner: { js: [ `import { createRequire } from 'node:module';`, `const require = createRequire(import.meta.url);`, ].join('\n'), }, + metafile: true, minify: production, sourcemap: !production, sourcesContent: false, @@ -62,8 +109,10 @@ async function main() { logLevel: 'info', plugins: [ umdEsmLoaderPlugin, + telemetryEsmLoaderPlugin, /* add to the end of plugins array */ esbuildProblemMatcherPlugin, + bundleAuditPlugin, ], }); if (watch) { diff --git a/package-lock.json b/package-lock.json index 0458465aa..481b41c06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@github/copilot-language-server": "1.290.0", "@microsoft/servicehub-framework": "4.2.99-beta", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^1.5.2", "@vscode/js-debug-browsers": "^1.1.0", "archiver": "5.3.0", "execa": "4.0.0", @@ -2143,104 +2143,110 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "4.0.4", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-core-js/-/1ds-core-js-4.0.4.tgz", - "integrity": "sha512-QOCE0fTDOMNptB791chnVlfnRvb7faDQTaUIO3DfPBkvjF3PUAJJCsqJKWitw7nwVn8L82TFx+K22UifIr0zkg==", + "version": "4.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-core-js/-/1ds-core-js-4.4.3.tgz", + "integrity": "sha1-4uVAxnLi3V8G9H9iYTirVg5A1z4=", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "4.0.4", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-post-js/-/1ds-post-js-4.0.4.tgz", - "integrity": "sha512-jlPNL16iRXzmXfriGXv0INzrAl3AeDx+eCORjq8ZjRhIvohB6Q88m5E28nL6Drf5hJWE2ehoW4q8Vh612VoEHw==", + "version": "4.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-post-js/-/1ds-post-js-4.4.3.tgz", + "integrity": "sha1-/3LD505NjEkQ3+q0DamOU0H9SHM=", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "4.0.4", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.5.tgz", - "integrity": "sha512-KfTYY0uZmrQgrz8ErBh1q08eiYfzjUIVzJZHETgEkqv3l2RTndQgpmywDbVNf9wVTB7Mp89ZrFeCciVJFf5geg==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.3.tgz", + "integrity": "sha1-lv6aE/XGMzH2jAP+Yfyo0nP87X8=", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.5.tgz", - "integrity": "sha512-ahph1fMqyLcZ1twzDKMzpHRgR9zEIyqNhMQxDgQ45ieVD641bZiYVwSlbntSXhGCtr5G5HE02zlEzwSxbx95ng==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.3.tgz", + "integrity": "sha1-gg7ByONwX7NQuJdFMc6lO/+KhWs=", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.5.tgz", - "integrity": "sha512-/x+tkxsVALNWSvwGMyaLwFPdD3p156Pef9WHftXrzrKkJ+685nhrwm9MqHIyEHHpSW09ElOdpJ3rfFVqpKRQyQ==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.3.tgz", + "integrity": "sha1-2tJKf5V4gfubq5ZDh2r6Wws54Lo=", + "license": "MIT", "dependencies": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", - "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "integrity": "sha1-OGW3Os6EBbnEYYzFxXHy/jh28G8=", + "license": "MIT", "dependencies": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.5.tgz", - "integrity": "sha512-ps4wjmF9X80hakYxywlzBdSlDjfToZrz/cHKA/9yarrW3mbZGqjjksNoaFxtyU5BK4lhOvrgu+2+QcDHeEEnOA==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.3.tgz", + "integrity": "sha1-Jq1prhzQVMjUMHCQAeJVKTb6dU0=", + "license": "MIT", "dependencies": { - "@microsoft/applicationinsights-channel-js": "3.0.5", - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-channel-js": "3.4.3", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" }, "peerDependencies": { - "tslib": "*" + "tslib": ">= 1.0.0" } }, "node_modules/@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.5.tgz", + "integrity": "sha1-9NCLCwbFcNaerXzSq69BQCUITGc=", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "node_modules/@microsoft/servicehub-framework": { @@ -2270,17 +2276,18 @@ } }, "node_modules/@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-async/-/ts-async-0.5.5.tgz", + "integrity": "sha1-UJZI48PDqpq2E8AMce7k/pvYo8M=", + "license": "MIT", "dependencies": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.12.2 < 2.x" } }, "node_modules/@nevware21/ts-utils": { - "version": "0.14.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", - "integrity": "sha1-nmB0zijwsbq3v9rNGndgsrpzaI0=", + "version": "0.16.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-utils/-/ts-utils-0.16.0.tgz", + "integrity": "sha1-5KOcjrEi54ESMVx5oAkWI4t/PEw=", "funding": [ { "type": "github", @@ -3475,13 +3482,16 @@ "license": "0BSD" }, "node_modules/@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", + "version": "1.5.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@vscode/extension-telemetry/-/extension-telemetry-1.5.2.tgz", + "integrity": "sha1-KsL23kVWWOJgGA/l6r9zvNbaUUY=", + "license": "MIT", "dependencies": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "@microsoft/1ds-core-js": "^4.4.1", + "@microsoft/1ds-post-js": "^4.4.1", + "@microsoft/applicationinsights-common": "^3.4.1", + "@microsoft/applicationinsights-core-js": "^3.4.1", + "@microsoft/applicationinsights-web-basic": "^3.4.1" }, "engines": { "vscode": "^1.75.0" @@ -13336,92 +13346,90 @@ } }, "@microsoft/1ds-core-js": { - "version": "4.0.4", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-core-js/-/1ds-core-js-4.0.4.tgz", - "integrity": "sha512-QOCE0fTDOMNptB791chnVlfnRvb7faDQTaUIO3DfPBkvjF3PUAJJCsqJKWitw7nwVn8L82TFx+K22UifIr0zkg==", + "version": "4.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-core-js/-/1ds-core-js-4.4.3.tgz", + "integrity": "sha1-4uVAxnLi3V8G9H9iYTirVg5A1z4=", "requires": { - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/1ds-post-js": { - "version": "4.0.4", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-post-js/-/1ds-post-js-4.0.4.tgz", - "integrity": "sha512-jlPNL16iRXzmXfriGXv0INzrAl3AeDx+eCORjq8ZjRhIvohB6Q88m5E28nL6Drf5hJWE2ehoW4q8Vh612VoEHw==", + "version": "4.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/1ds-post-js/-/1ds-post-js-4.4.3.tgz", + "integrity": "sha1-/3LD505NjEkQ3+q0DamOU0H9SHM=", "requires": { - "@microsoft/1ds-core-js": "4.0.4", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/applicationinsights-channel-js": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.0.5.tgz", - "integrity": "sha512-KfTYY0uZmrQgrz8ErBh1q08eiYfzjUIVzJZHETgEkqv3l2RTndQgpmywDbVNf9wVTB7Mp89ZrFeCciVJFf5geg==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-3.4.3.tgz", + "integrity": "sha1-lv6aE/XGMzH2jAP+Yfyo0nP87X8=", "requires": { - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/applicationinsights-common": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-common/-/applicationinsights-common-3.0.5.tgz", - "integrity": "sha512-ahph1fMqyLcZ1twzDKMzpHRgR9zEIyqNhMQxDgQ45ieVD641bZiYVwSlbntSXhGCtr5G5HE02zlEzwSxbx95ng==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-common/-/applicationinsights-common-3.4.3.tgz", + "integrity": "sha1-gg7ByONwX7NQuJdFMc6lO/+KhWs=", "requires": { - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/applicationinsights-core-js": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.0.5.tgz", - "integrity": "sha512-/x+tkxsVALNWSvwGMyaLwFPdD3p156Pef9WHftXrzrKkJ+685nhrwm9MqHIyEHHpSW09ElOdpJ3rfFVqpKRQyQ==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-3.4.3.tgz", + "integrity": "sha1-2tJKf5V4gfubq5ZDh2r6Wws54Lo=", "requires": { "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/applicationinsights-shims": { "version": "3.0.1", "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-shims/-/applicationinsights-shims-3.0.1.tgz", - "integrity": "sha512-DKwboF47H1nb33rSUfjqI6ryX29v+2QWcTrRvcQDA32AZr5Ilkr7whOOSsD1aBzwqX0RJEIP1Z81jfE3NBm/Lg==", + "integrity": "sha1-OGW3Os6EBbnEYYzFxXHy/jh28G8=", "requires": { "@nevware21/ts-utils": ">= 0.9.4 < 2.x" } }, "@microsoft/applicationinsights-web-basic": { - "version": "3.0.5", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.0.5.tgz", - "integrity": "sha512-ps4wjmF9X80hakYxywlzBdSlDjfToZrz/cHKA/9yarrW3mbZGqjjksNoaFxtyU5BK4lhOvrgu+2+QcDHeEEnOA==", + "version": "3.4.3", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-3.4.3.tgz", + "integrity": "sha1-Jq1prhzQVMjUMHCQAeJVKTb6dU0=", "requires": { - "@microsoft/applicationinsights-channel-js": "3.0.5", - "@microsoft/applicationinsights-common": "3.0.5", - "@microsoft/applicationinsights-core-js": "3.0.5", + "@microsoft/applicationinsights-channel-js": "3.4.3", + "@microsoft/applicationinsights-core-js": "3.4.3", "@microsoft/applicationinsights-shims": "3.0.1", - "@microsoft/dynamicproto-js": "^2.0.2", - "@nevware21/ts-async": ">= 0.3.0 < 2.x", - "@nevware21/ts-utils": ">= 0.10.1 < 2.x" + "@microsoft/dynamicproto-js": "^2.0.3", + "@nevware21/ts-async": ">= 0.5.5 < 0.6.0", + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/dynamicproto-js": { - "version": "2.0.2", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.2.tgz", - "integrity": "sha512-MB8trWaFREpmb037k/d0bB7T2BP7Ai24w1e1tbz3ASLB0/lwphsq3Nq8S9I5AsI5vs4zAQT+SB5nC5/dLYTiOg==", + "version": "2.0.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@microsoft/dynamicproto-js/-/dynamicproto-js-2.0.5.tgz", + "integrity": "sha1-9NCLCwbFcNaerXzSq69BQCUITGc=", "requires": { - "@nevware21/ts-utils": ">= 0.9.4 < 2.x" + "@nevware21/ts-utils": ">= 0.14.0 < 2.x" } }, "@microsoft/servicehub-framework": { @@ -13449,17 +13457,17 @@ } }, "@nevware21/ts-async": { - "version": "0.3.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-async/-/ts-async-0.3.0.tgz", - "integrity": "sha512-ZUcgUH12LN/F6nzN0cYd0F/rJaMLmXr0EHVTyYfaYmK55bdwE4338uue4UiVoRqHVqNW4KDUrJc49iGogHKeWA==", + "version": "0.5.5", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-async/-/ts-async-0.5.5.tgz", + "integrity": "sha1-UJZI48PDqpq2E8AMce7k/pvYo8M=", "requires": { - "@nevware21/ts-utils": ">= 0.10.0 < 2.x" + "@nevware21/ts-utils": ">= 0.12.2 < 2.x" } }, "@nevware21/ts-utils": { - "version": "0.14.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-utils/-/ts-utils-0.14.0.tgz", - "integrity": "sha1-nmB0zijwsbq3v9rNGndgsrpzaI0=" + "version": "0.16.0", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@nevware21/ts-utils/-/ts-utils-0.16.0.tgz", + "integrity": "sha1-5KOcjrEi54ESMVx5oAkWI4t/PEw=" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -14365,13 +14373,15 @@ } }, "@vscode/extension-telemetry": { - "version": "0.9.0", - "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@vscode/extension-telemetry/-/extension-telemetry-0.9.0.tgz", - "integrity": "sha512-37RxGHXrs3GoXPgCUKQhghEu0gxs8j27RLjQwwtSf4WhPdJKz8UrqMYzpsXlliQ05zURYmtdGZst9C6+hfWXaQ==", - "requires": { - "@microsoft/1ds-core-js": "^4.0.3", - "@microsoft/1ds-post-js": "^4.0.3", - "@microsoft/applicationinsights-web-basic": "^3.0.4" + "version": "1.5.2", + "resolved": "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public-npm/npm/registry/@vscode/extension-telemetry/-/extension-telemetry-1.5.2.tgz", + "integrity": "sha1-KsL23kVWWOJgGA/l6r9zvNbaUUY=", + "requires": { + "@microsoft/1ds-core-js": "^4.4.1", + "@microsoft/1ds-post-js": "^4.4.1", + "@microsoft/applicationinsights-common": "^3.4.1", + "@microsoft/applicationinsights-core-js": "^3.4.1", + "@microsoft/applicationinsights-web-basic": "^3.4.1" } }, "@vscode/js-debug-browsers": { diff --git a/package.json b/package.json index 553b78625..8fd4f7a22 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "dependencies": { "@github/copilot-language-server": "1.290.0", "@microsoft/servicehub-framework": "4.2.99-beta", - "@vscode/extension-telemetry": "^0.9.0", + "@vscode/extension-telemetry": "^1.5.2", "@vscode/js-debug-browsers": "^1.1.0", "archiver": "5.3.0", "execa": "4.0.0", diff --git a/src/main.ts b/src/main.ts index 6ee1d6b9e..00b660b67 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,7 +12,7 @@ import { CsharpChannelObserver } from './shared/observers/csharpChannelObserver. import { CsharpLoggerObserver } from './shared/observers/csharpLoggerObserver.ts'; import { EventStream } from './eventStream.ts'; import { PlatformInformation } from './shared/platform.ts'; -import telemetryReporterModule from '@vscode/extension-telemetry'; +import { TelemetryReporter } from '@vscode/extension-telemetry'; import { vscodeNetworkSettingsProvider } from './networkSettings.ts'; import createOptionStream from './shared/observables/createOptionStream.ts'; import { AbsolutePathPackage } from './packageManager/absolutePathPackage.ts'; @@ -32,9 +32,6 @@ import { checkIsSupportedPlatform } from './checkSupportedPlatform.ts'; import { activateRoslyn } from './activateRoslyn.ts'; import { LimitedActivationStatus } from './shared/limitedActivationStatus.ts'; -// The package's CommonJS entry point exposes its constructor through the imported default object's default export. -const TelemetryReporter = telemetryReporterModule.default; - export async function activate( context: vscode.ExtensionContext ): Promise { diff --git a/src/omnisharp/observers/telemetryObserver.ts b/src/omnisharp/observers/telemetryObserver.ts index e3e3a89f7..2dea25757 100644 --- a/src/omnisharp/observers/telemetryObserver.ts +++ b/src/omnisharp/observers/telemetryObserver.ts @@ -66,11 +66,11 @@ export class TelemetryObserver { } case EventType.TelemetryErrorEvent: { const telemetryErrorEvent = event; + // errorProps has been ignored by @vscode/extension-telemetry since v0.6. this.reporter.sendTelemetryErrorEvent( telemetryErrorEvent.eventName, telemetryErrorEvent.properties, - telemetryErrorEvent.measures, - telemetryErrorEvent.errorProps + telemetryErrorEvent.measures ); break; } diff --git a/src/shared/telemetryReporter.ts b/src/shared/telemetryReporter.ts index e2a4dd7ba..87890d245 100644 --- a/src/shared/telemetryReporter.ts +++ b/src/shared/telemetryReporter.ts @@ -14,8 +14,7 @@ export interface ITelemetryReporter { sendTelemetryErrorEvent( eventName: string, properties?: { [key: string]: string }, - measures?: { [key: string]: number }, - errorProps?: string[] + measures?: { [key: string]: number } ): void; } diff --git a/tasks/compilation/bundleAudit.mjs b/tasks/compilation/bundleAudit.mjs new file mode 100644 index 000000000..da23be17b --- /dev/null +++ b/tasks/compilation/bundleAudit.mjs @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import fs from 'node:fs/promises'; + +const expectedRuntimeRequires = { + '@microsoft/servicehub-framework': ['assert', 'crypto', 'events', 'net', 'os', 'path', 'stream', 'util'], + '@vscode/js-debug-browsers': ['child_process', 'fs', 'os', 'path'], + '@vscode/l10n': ['fs', 'fs/promises'], + 'agent-base': ['http', 'https', 'net'], + archiver: ['buffer', 'events', 'fs', 'path', 'stream', 'util', 'zlib'], + 'archiver-utils': ['path', 'stream', 'util'], + bl: ['buffer', 'events', 'stream', 'util'], + 'buffer-crc32': ['buffer'], + 'compress-commons': ['buffer', 'events', 'stream', 'util'], + 'crc32-stream': ['buffer', 'events', 'stream', 'util', 'zlib'], + 'cross-spawn': ['child_process', 'fs', 'path'], + debug: ['tty', 'util'], + execa: ['child_process', 'os', 'path'], + 'fs-constants': ['constants', 'fs'], + 'fs-extra': ['path'], + 'fs.realpath': ['fs', 'path'], + 'get-stream': ['buffer', 'stream'], + glob: ['assert', 'events', 'fs', 'path', 'util'], + 'graceful-fs': ['assert', 'constants', 'fs', 'stream', 'util'], + 'http-proxy-agent': ['events', 'net', 'tls'], + 'https-proxy-agent': ['assert', 'net', 'tls'], + inherits: ['util'], + isexe: ['fs'], + jsonfile: ['fs'], + lazystream: ['util'], + 'merge-stream': ['stream'], + // Razor is a version-pinned component tarball. Update it through component servicing, not npm. + 'microsoft.aspnetcore.razor.vscode': [ + 'child_process', + 'crypto', + 'events', + 'fs', + 'net', + 'os', + 'path', + 'url', + 'util', + 'vscode', + ], + minimatch: ['path'], + 'msgpack-lite': ['stream', 'util'], + 'nerdbank-streams': ['crypto', 'events', 'stream'], + 'node-machine-id': ['child_process', 'crypto'], + 'npm-run-path': ['path'], + 'ps-list': ['child_process', 'path', 'util'], + pump: ['fs'], + 'readable-stream': ['events', 'stream', 'util'], + 'readdir-glob': ['events', 'fs', 'path'], + 'safe-buffer': ['buffer'], + 'signal-exit': ['assert', 'events'], + 'supports-color': ['os', 'tty'], + 'tar-stream': ['buffer', 'events', 'stream', 'string_decoder', 'util'], + 'util-deprecate': ['util'], + 'vscode-jsonrpc': ['crypto', 'fs', 'net', 'os', 'path', 'util'], + 'vscode-languageclient': ['child_process', 'fs', 'path', 'readline', 'vscode'], + which: ['path'], + yauzl: ['events', 'fs', 'stream', 'util', 'zlib'], + 'zip-stream': ['util'], +}; + +function getPackageOwner(inputPath) { + const normalizedPath = inputPath.replaceAll('\\', '/'); + const marker = 'node_modules/'; + const markerIndex = normalizedPath.indexOf(marker); + if (markerIndex < 0) { + return undefined; + } + + const [firstSegment, secondSegment] = normalizedPath.slice(markerIndex + marker.length).split('/'); + return firstSegment.startsWith('@') ? `${firstSegment}/${secondSegment}` : firstSegment; +} + +function getRuntimeRequires(metafile) { + const packageRequires = new Map(); + const firstPartyRequires = []; + + for (const [inputPath, input] of Object.entries(metafile.inputs)) { + for (const imported of input.imports) { + if (!imported.external || !['require-call', 'require-resolve'].includes(imported.kind)) { + continue; + } + + const packageOwner = getPackageOwner(inputPath); + if (!packageOwner) { + firstPartyRequires.push(`${inputPath} -> ${imported.path}`); + continue; + } + + let record = packageRequires.get(packageOwner); + if (!record) { + record = { imports: 0, inputs: new Set(), targets: new Set() }; + packageRequires.set(packageOwner, record); + } + + record.imports++; + record.inputs.add(inputPath); + record.targets.add(imported.path); + } + } + + return { firstPartyRequires, packageRequires }; +} + +function getDifferences(left, right) { + return [...left].filter((value) => !right.has(value)).sort(); +} + +function validateRuntimeRequires(firstPartyRequires, packageRequires) { + const issues = []; + if (firstPartyRequires.length > 0) { + issues.push(`First-party source emitted CommonJS runtime requires: ${firstPartyRequires.join(', ')}`); + } + + const expectedPackages = new Set(Object.keys(expectedRuntimeRequires)); + const actualPackages = new Set(packageRequires.keys()); + const unexpectedPackages = getDifferences(actualPackages, expectedPackages); + const stalePackages = getDifferences(expectedPackages, actualPackages); + + if (unexpectedPackages.length > 0) { + issues.push(`Review and allowlist new bridge owners: ${unexpectedPackages.join(', ')}`); + } + if (stalePackages.length > 0) { + issues.push(`Remove bridge owners that no longer require it: ${stalePackages.join(', ')}`); + } + + for (const packageName of [...expectedPackages].filter((name) => actualPackages.has(name)).sort()) { + const expectedTargets = new Set(expectedRuntimeRequires[packageName]); + const actualTargets = packageRequires.get(packageName).targets; + const addedTargets = getDifferences(actualTargets, expectedTargets); + const removedTargets = getDifferences(expectedTargets, actualTargets); + + if (addedTargets.length > 0) { + issues.push(`${packageName} added runtime requires: ${addedTargets.join(', ')}`); + } + if (removedTargets.length > 0) { + issues.push(`${packageName} no longer requires: ${removedTargets.join(', ')}`); + } + } + + if (issues.length > 0) { + throw new Error(`Bundle runtime require audit failed:\n- ${issues.join('\n- ')}`); + } +} + +function validateEsmOutput(metafile) { + const output = Object.entries(metafile.outputs).find(([outputPath]) => + outputPath.replaceAll('\\', '/').endsWith('dist/extension.mjs') + )?.[1]; + if (!output) { + throw new Error('Bundle runtime require audit could not find dist/extension.mjs in the esbuild metafile.'); + } + + if (output.exports.length !== 1 || output.exports[0] !== 'activate') { + throw new Error(`Expected the extension bundle to export only activate; found: ${output.exports.join(', ')}`); + } + + if (!output.imports.some((imported) => imported.path === 'vscode' && imported.kind === 'import-statement')) { + throw new Error('Expected the extension bundle to use a native ESM import for vscode.'); + } +} + +export async function auditBundle(metafile, production) { + if (!metafile) { + throw new Error('Bundle runtime require audit requires an esbuild metafile.'); + } + + const { firstPartyRequires, packageRequires } = getRuntimeRequires(metafile); + validateRuntimeRequires(firstPartyRequires, packageRequires); + validateEsmOutput(metafile); + + const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')); + const directDependencies = new Set(Object.keys(packageJson.dependencies)); + const packageNames = [...packageRequires.keys()].sort(); + const directOwners = packageNames.filter((name) => directDependencies.has(name)); + const transitiveOwners = packageNames.filter((name) => !directDependencies.has(name)); + const vscodeOwners = packageNames.filter((name) => packageRequires.get(name).targets.has('vscode')); + const moduleCount = new Set([...packageRequires.values()].flatMap((record) => [...record.inputs])).size; + const importCount = [...packageRequires.values()].reduce((count, record) => count + record.imports, 0); + const mode = production ? 'production' : 'development'; + + console.log( + `[bundle-audit] ${mode}: ${packageNames.length} owners, ${moduleCount} modules, ${importCount} runtime requires` + ); + console.log(`[bundle-audit] direct/component: ${directOwners.join(', ')}`); + console.log(`[bundle-audit] transitive: ${transitiveOwners.join(', ')}`); + console.log(`[bundle-audit] require("vscode"): ${vscodeOwners.join(', ')}`); +} diff --git a/test/fakes.ts b/test/fakes.ts index 0a8f01cb3..35e656012 100644 --- a/test/fakes.ts +++ b/test/fakes.ts @@ -50,8 +50,7 @@ export const getNullTelemetryReporter = (): ITelemetryReporter => { sendTelemetryErrorEvent: ( _eventName: string, _properties?: { [key: string]: string }, - _measures?: { [key: string]: number }, - _errorProps?: string[] + _measures?: { [key: string]: number } ) => { /** empty */ }, diff --git a/test/lsptoolshost/artifactTests/extensionBundle.test.ts b/test/lsptoolshost/artifactTests/extensionBundle.test.ts new file mode 100644 index 000000000..a9fc5fe66 --- /dev/null +++ b/test/lsptoolshost/artifactTests/extensionBundle.test.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, test } from '@jest/globals'; +import esbuild from 'esbuild'; +import fs from 'fs-extra'; +import path from 'node:path'; + +const packageJson = fs.readJsonSync(path.resolve('package.json')); +const extensionEntry = path.resolve(packageJson.main); + +describe('Extension bundle', () => { + test('package main points to an existing .mjs file', async () => { + expect(path.extname(packageJson.main)).toBe('.mjs'); + await expect(fs.pathExists(extensionEntry)).resolves.toBe(true); + }); + + test('entry is native ESM with the activation contract', async () => { + const result = await esbuild.build({ + entryPoints: [extensionEntry], + bundle: false, + format: 'esm', + metafile: true, + outdir: 'artifact-audit', + write: false, + }); + const output = Object.values(result.metafile.outputs)[0]; + + expect(output.exports).toEqual(['activate']); + expect(output.imports).toContainEqual({ + external: true, + kind: 'import-statement', + path: 'vscode', + }); + }); + + test('JavaScript signing includes .mjs output', async () => { + const signingProject = await fs.readFile(path.resolve('msbuild/signing/signJs/signJs.proj'), 'utf8'); + expect(signingProject).toContain(''); + }); +}); diff --git a/test/lsptoolshost/artifactTests/vsix.test.ts b/test/lsptoolshost/artifactTests/vsix.test.ts index f84c1b473..038d9ae0b 100644 --- a/test/lsptoolshost/artifactTests/vsix.test.ts +++ b/test/lsptoolshost/artifactTests/vsix.test.ts @@ -7,8 +7,35 @@ import { describe, test, expect } from '@jest/globals'; import fs from 'fs-extra'; import glob from 'glob'; import * as path from 'path'; +import * as yauzl from 'yauzl'; const vsixFiles = glob.sync(path.join(process.cwd(), '**', '*.vsix')); +const packageJson = fs.readJsonSync(path.resolve('package.json')); +const extensionEntry = path.posix.join('extension', packageJson.main.replace(/^\.\//, '')); + +async function getVsixEntries(vsixPath: string): Promise { + return new Promise((resolve, reject) => { + yauzl.open(vsixPath, { lazyEntries: true }, (error, zipFile) => { + if (error) { + reject(error); + return; + } + if (!zipFile) { + reject(new Error(`Unable to open ${vsixPath}`)); + return; + } + + const entries: string[] = []; + zipFile.on('entry', (entry) => { + entries.push(entry.fileName); + zipFile.readEntry(); + }); + zipFile.on('end', () => resolve(entries)); + zipFile.on('error', reject); + zipFile.readEntry(); + }); + }); +} describe('Vscode VSIX', () => { test('At least one vsix file should be produced', () => { @@ -31,6 +58,10 @@ describe('Vscode VSIX', () => { const stats = await fs.stat(element); expect(stats.size).toBeGreaterThan(0); }); + + test('Then it should contain the declared extension entry', async () => { + await expect(getVsixEntries(element)).resolves.toContain(extensionEntry); + }); }); }); }); diff --git a/test/omnisharp/omnisharpUnitTests/logging/telemetryObserver.test.ts b/test/omnisharp/omnisharpUnitTests/logging/telemetryObserver.test.ts index 61d59482e..885a51e5b 100644 --- a/test/omnisharp/omnisharpUnitTests/logging/telemetryObserver.test.ts +++ b/test/omnisharp/omnisharpUnitTests/logging/telemetryObserver.test.ts @@ -26,7 +26,6 @@ describe('TelemetryReporterObserver', () => { let name = ''; let property: { [key: string]: string } | undefined = undefined; let measure: { [key: string]: number }[] = []; - let errorProp: string[] = []; const useModernNet = true; const observer = new TelemetryObserver( platformInfo, @@ -45,15 +44,11 @@ describe('TelemetryReporterObserver', () => { sendTelemetryErrorEvent: ( eventName: string, properties?: { [key: string]: string }, - measures?: { [key: string]: number }, - errorProps?: string[] + measures?: { [key: string]: number } ) => { name += eventName; property = properties; measure.push(measures!); - errorProps!.forEach((prop) => { - errorProp.push(prop); - }); }, }; }, @@ -64,7 +59,6 @@ describe('TelemetryReporterObserver', () => { name = ''; property = undefined; measure = []; - errorProp = []; }); test('PackageInstallation: AcquisitionStart is reported', async () => { @@ -141,13 +135,12 @@ describe('TelemetryReporterObserver', () => { expect(property).toEqual(event.properties); }); - test(`${TelemetryErrorEvent.name}: SendTelemetry error event is called with the name, properties, measures, and errorProps`, async () => { + test(`${TelemetryErrorEvent.name}: SendTelemetry error event is called with the name, properties, and measures`, async () => { const event = new TelemetryErrorEvent('someName', { key: 'value' }, { someKey: 1 }, ['StackTrace']); await observer.post(event); expect(name).toContain(event.eventName); expect(measure).toMatchObject([event.measures!]); expect(property).toEqual(event.properties); - expect(errorProp).toEqual(event.errorProps!); }); describe('InstallationFailure', () => { diff --git a/test/tasks/bundleAudit.test.js b/test/tasks/bundleAudit.test.js new file mode 100644 index 000000000..5ada00d4b --- /dev/null +++ b/test/tasks/bundleAudit.test.js @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { expect, test } from '@jest/globals'; +import { auditBundle } from '../../tasks/compilation/bundleAudit.mjs'; + +test('bundle audit rejects first-party CommonJS runtime requires', async () => { + const metafile = { + inputs: { + 'src/unexpected.ts': { + imports: [{ external: true, kind: 'require-call', path: 'node:fs' }], + }, + }, + outputs: { + 'dist/extension.mjs': { + exports: ['activate'], + imports: [{ external: true, kind: 'import-statement', path: 'vscode' }], + }, + }, + }; + + await expect(auditBundle(metafile, false)).rejects.toThrow( + 'First-party source emitted CommonJS runtime requires: src/unexpected.ts -> node:fs' + ); +});