From 65d5f46713be0bef7c54325ee047d3e03d8b58f4 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Fri, 27 Mar 2026 14:24:34 +0200 Subject: [PATCH 01/10] debug Signed-off-by: Roman Nikitenko --- .../api/node/extensionHostProcess.ts | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/code/src/vs/workbench/api/node/extensionHostProcess.ts b/code/src/vs/workbench/api/node/extensionHostProcess.ts index db779d8fd3f..9aff5c8f9fb 100644 --- a/code/src/vs/workbench/api/node/extensionHostProcess.ts +++ b/code/src/vs/workbench/api/node/extensionHostProcess.ts @@ -81,14 +81,31 @@ const args = minimist(process.argv.slice(2), { (function () { const Module = require('module'); const originalLoad = Module._load; + let lastModuleRequest: string | undefined; + let lastModuleParent: string | undefined; + + Module._load = function (request: string, parent: { id?: string; filename?: string } | undefined) { + const prevRequest = lastModuleRequest; + const prevParent = lastModuleParent; + lastModuleRequest = request; + lastModuleParent = parent?.filename ?? parent?.id; - Module._load = function (request: string) { if (request === 'natives') { throw new Error('Either the extension or an NPM dependency is using the [unsupported "natives" node module](https://go.microsoft.com/fwlink/?linkid=871887).'); } - return originalLoad.apply(this, arguments); + try { + return originalLoad.apply(this, arguments); + } finally { + lastModuleRequest = prevRequest; + lastModuleParent = prevParent; + } }; + + (globalThis as unknown as { __vscodePendingMigrationLastModule?: () => { request?: string; parent?: string } }).__vscodePendingMigrationLastModule = () => ({ + request: lastModuleRequest, + parent: lastModuleParent + }); })(); // custom process.exit logic... @@ -140,12 +157,31 @@ function patchProcess(allowExit: boolean) { // NodeJS since v21 defines navigator as a global object. This will likely surprise many extensions and potentially break them // because `navigator` has historically often been used to check if running in a browser (vs running inside NodeJS) if (!args.supportGlobalNavigator) { + let navigatorAccessCount = 0; + let hasSuppressedNavigatorLogs = false; + const maxNavigatorLogs = 25; Object.defineProperty(globalThis, 'navigator', { get: () => { onUnexpectedExternalError(new PendingMigrationError('navigator is now a global in nodejs, please see https://aka.ms/vscode-extensions/navigator for additional info on this error.')); + navigatorAccessCount++; + if (navigatorAccessCount <= maxNavigatorLogs) { + const moduleTracker = (globalThis as unknown as { __vscodePendingMigrationLastModule?: () => { request?: string; parent?: string } }).__vscodePendingMigrationLastModule; + const moduleContext = moduleTracker?.() ?? {}; + const stack = new Error().stack?.split('\n').slice(2, 10).join('\n'); + console.error(`[pending-migration][navigator] access #${navigatorAccessCount}; lastModule="${moduleContext.request ?? 'unknown'}"; parent="${moduleContext.parent ?? 'unknown'}"`); + if (stack) { + console.error(`[pending-migration][navigator] stack:\n${stack}`); + } + } else if (!hasSuppressedNavigatorLogs) { + hasSuppressedNavigatorLogs = true; + console.error(`[pending-migration][navigator] additional accesses suppressed after ${maxNavigatorLogs} logs`); + } return undefined; } }); + console.error('[pending-migration][navigator] migration guard active; detailed accesses will be logged'); +} else { + console.error('[pending-migration][navigator] supportGlobalNavigator=true; PendingMigrationError disabled'); } From 25c28dfe4f3891dfb3b8f8ccebdf8e96e3c96284 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Fri, 27 Mar 2026 15:13:41 +0200 Subject: [PATCH 02/10] test removing axios Signed-off-by: Roman Nikitenko --- code/extensions/che-api/package-lock.json | 33 ------------------- code/extensions/che-api/package.json | 7 ++-- code/extensions/che-api/src/extension.ts | 2 -- .../che-api/src/impl/github-service-impl.ts | 29 +++++++++++----- .../src/impl/k8s-workspace-service-impl.ts | 10 +++--- 5 files changed, 28 insertions(+), 53 deletions(-) diff --git a/code/extensions/che-api/package-lock.json b/code/extensions/che-api/package-lock.json index f302a8b275f..ddea936d55f 100644 --- a/code/extensions/che-api/package-lock.json +++ b/code/extensions/che-api/package-lock.json @@ -12,7 +12,6 @@ "@devfile/api": "^2.3.0-1738854228", "@eclipse-che/workspace-telemetry-client": "^0.0.1-1685523760", "@kubernetes/client-node": "^1.4.0", - "axios": "^1.13.5", "fs-extra": "^11.2.0", "inversify": "^6.0.2", "js-yaml": "^4.1.0", @@ -1304,33 +1303,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/b4a": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", @@ -3932,11 +3904,6 @@ "node": ">= 6" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", diff --git a/code/extensions/che-api/package.json b/code/extensions/che-api/package.json index 9471c8e9773..ee2ae73a0e1 100644 --- a/code/extensions/che-api/package.json +++ b/code/extensions/che-api/package.json @@ -31,14 +31,13 @@ }, "dependencies": { "@devfile/api": "^2.3.0-1738854228", - "axios": "^1.13.5", + "@eclipse-che/workspace-telemetry-client": "^0.0.1-1685523760", "@kubernetes/client-node": "^1.4.0", "fs-extra": "^11.2.0", "inversify": "^6.0.2", "js-yaml": "^4.1.0", "reflect-metadata": "^0.2.2", - "vscode-nls": "^5.0.0", - "@eclipse-che/workspace-telemetry-client": "^0.0.1-1685523760" + "vscode-nls": "^5.0.0" }, "devDependencies": { "@types/fs-extra": "^9.0.13", @@ -46,8 +45,8 @@ "@types/js-yaml": "^4.0.5", "@types/node": "22.x", "jest": "29.7.0", - "typescript": "^5.9.2", "ts-jest": "29.4.5", + "typescript": "^5.9.2", "webpack-node-externals": "^3.0.0" }, "overrides": { diff --git a/code/extensions/che-api/src/extension.ts b/code/extensions/che-api/src/extension.ts index 3969f2f11f5..73dadadde5e 100644 --- a/code/extensions/che-api/src/extension.ts +++ b/code/extensions/che-api/src/extension.ts @@ -30,7 +30,6 @@ import { GithubService } from './api/github-service'; import { GithubServiceImpl } from './impl/github-service-impl'; import { TelemetryService } from './api/telemetry-service'; import { K8sTelemetryServiceImpl } from './impl/k8s-telemetry-service-impl'; -import * as axios from 'axios'; import { Logger } from './logger'; @@ -43,7 +42,6 @@ export async function activate(_extensionContext: vscode.ExtensionContext): Prom container.bind(K8SServiceImpl).toSelf().inSingletonScope(); container.bind(K8SService).to(K8SServiceImpl).inSingletonScope(); container.bind(K8sDevWorkspaceEnvVariables).toSelf().inSingletonScope(); - container.bind(Symbol.for('AxiosInstance')).toConstantValue(axios); container.bind(GithubServiceImpl).toSelf().inSingletonScope(); container.bind(GithubService).to(GithubServiceImpl).inSingletonScope(); container.bind(TelemetryService).to(K8sTelemetryServiceImpl).inSingletonScope(); diff --git a/code/extensions/che-api/src/impl/github-service-impl.ts b/code/extensions/che-api/src/impl/github-service-impl.ts index e0ea3abf9e5..4fad8e27af0 100644 --- a/code/extensions/che-api/src/impl/github-service-impl.ts +++ b/code/extensions/che-api/src/impl/github-service-impl.ts @@ -11,7 +11,6 @@ /* eslint-disable header/header */ import * as k8s from '@kubernetes/client-node'; -import { AxiosInstance } from 'axios'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import * as path from 'path'; @@ -39,8 +38,7 @@ export class GithubServiceImpl implements GithubService { constructor( @inject(Logger) private logger: Logger, - @inject(K8SServiceImpl) private readonly k8sService: K8SServiceImpl, - @inject(Symbol.for('AxiosInstance')) private readonly axiosInstance: AxiosInstance + @inject(K8SServiceImpl) private readonly k8sService: K8SServiceImpl ) { this.initializeToken(); } @@ -58,18 +56,31 @@ export class GithubServiceImpl implements GithubService { async getUser(): Promise { this.checkToken(); - const result = await this.axiosInstance.get('https://api.github.com/user', { - headers: { Authorization: `Bearer ${this.token}` }, - }); - return result.data; + const result = await this.fetchGithubUser(this.token!); + return result.user; } async getTokenScopes(token: string): Promise { this.checkToken(); - const result = await this.axiosInstance.get('https://api.github.com/user', { + const result = await this.fetchGithubUser(token); + return result.scopes; + } + + private async fetchGithubUser(token: string): Promise<{ user: GithubUser; scopes: string[] }> { + const response = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${token}` }, }); - return result.headers['x-oauth-scopes'].split(', '); + if (!response.ok) { + const message = await response.text(); + throw new Error(`GitHub user request failed: ${response.status} ${response.statusText} - ${message}`); + } + const user = await response.json() as GithubUser; + const scopesHeader = response.headers.get('x-oauth-scopes') ?? ''; + const scopes = scopesHeader + .split(', ') + .map(scope => scope.trim()) + .filter(scope => scope.length > 0); + return { user, scopes }; } async persistDeviceAuthToken(token: string): Promise { diff --git a/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts b/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts index 726fac317d2..33c798d6dc5 100644 --- a/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts +++ b/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts @@ -12,7 +12,6 @@ import * as k8s from '@kubernetes/client-node'; import { inject, injectable } from 'inversify'; -import { AxiosInstance } from "axios"; import { K8SServiceImpl } from './k8s-service-impl'; import { K8sDevWorkspaceEnvVariables } from './k8s-devworkspace-env-variables'; import { WorkspaceService } from '../api/workspace-service'; @@ -25,9 +24,6 @@ export class K8sWorkspaceServiceImpl implements WorkspaceService { @inject(K8sDevWorkspaceEnvVariables) private env!: K8sDevWorkspaceEnvVariables; - @inject(Symbol.for('AxiosInstance')) - private axiosInstance!: AxiosInstance; - public async getNamespace(): Promise { return this.env.getWorkspaceNamespace(); } @@ -54,7 +50,11 @@ export class K8sWorkspaceServiceImpl implements WorkspaceService { } const requestUrl = `http://127.0.0.1:${process.env.MACHINE_EXEC_PORT}/activity/tick`; - await this.axiosInstance.post(requestUrl); + const response = await fetch(requestUrl, { method: 'POST' }); + if (!response.ok) { + const message = await response.text(); + throw new Error(`Activity tick request failed: ${response.status} ${response.statusText} - ${message}`); + } } // stopping the workspace is changing the started state to false From 584b4652d446109e8904e4a0819b83df86719660 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sat, 28 Mar 2026 20:27:45 +0200 Subject: [PATCH 03/10] test Signed-off-by: Roman Nikitenko --- code/extensions/che-api/src/extension.ts | 2 -- .../che-api/src/impl/github-service-impl.ts | 4 +-- .../src/impl/k8s-devfile-service-impl.ts | 8 ++--- .../che-api/src/impl/k8s-service-impl.ts | 31 ++++++++++--------- .../src/impl/k8s-workspace-service-impl.ts | 4 +-- .../src/error-handler.ts | 4 +-- .../src/k8s-helper.ts | 20 ++++++------ 7 files changed, 36 insertions(+), 37 deletions(-) diff --git a/code/extensions/che-api/src/extension.ts b/code/extensions/che-api/src/extension.ts index 73dadadde5e..5edee5f215b 100644 --- a/code/extensions/che-api/src/extension.ts +++ b/code/extensions/che-api/src/extension.ts @@ -32,9 +32,7 @@ import { TelemetryService } from './api/telemetry-service'; import { K8sTelemetryServiceImpl } from './impl/k8s-telemetry-service-impl'; import { Logger } from './logger'; - export async function activate(_extensionContext: vscode.ExtensionContext): Promise { - const container = new Container(); container.bind(K8sDevfileServiceImpl).toSelf().inSingletonScope(); container.bind(DevfileService).to(K8sDevfileServiceImpl).inSingletonScope(); diff --git a/code/extensions/che-api/src/impl/github-service-impl.ts b/code/extensions/che-api/src/impl/github-service-impl.ts index 4fad8e27af0..6b60486fc15 100644 --- a/code/extensions/che-api/src/impl/github-service-impl.ts +++ b/code/extensions/che-api/src/impl/github-service-impl.ts @@ -10,7 +10,7 @@ /* eslint-disable header/header */ -import * as k8s from '@kubernetes/client-node'; +import type { V1Secret } from '@kubernetes/client-node'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import * as path from 'path'; @@ -203,7 +203,7 @@ export class GithubServiceImpl implements GithubService { } } -function toDeviceAuthSecret(token: string, namespace: string): k8s.V1Secret { +function toDeviceAuthSecret(token: string, namespace: string): V1Secret { return { apiVersion: 'v1', kind: 'Secret', diff --git a/code/extensions/che-api/src/impl/k8s-devfile-service-impl.ts b/code/extensions/che-api/src/impl/k8s-devfile-service-impl.ts index faabe49a04b..e611ed00f20 100644 --- a/code/extensions/che-api/src/impl/k8s-devfile-service-impl.ts +++ b/code/extensions/che-api/src/impl/k8s-devfile-service-impl.ts @@ -12,7 +12,8 @@ import * as fs from 'fs-extra'; import * as jsYaml from 'js-yaml'; -import * as k8s from '@kubernetes/client-node'; +import { CoreV1Api, CustomObjectsApi } from '@kubernetes/client-node'; +import type { V1Pod } from '@kubernetes/client-node'; import { V1alpha2DevWorkspaceSpecTemplate @@ -23,7 +24,6 @@ import { inject, injectable } from 'inversify'; import { K8SServiceImpl } from './k8s-service-impl'; import { DevfileService } from '../api/devfile-service'; import { K8sDevWorkspaceEnvVariables } from './k8s-devworkspace-env-variables'; -import { V1Pod } from '@kubernetes/client-node'; @injectable() export class K8sDevfileServiceImpl implements DevfileService { @@ -48,7 +48,7 @@ export class K8sDevfileServiceImpl implements DevfileService { async getWorkspacePod(): Promise { // get workspace pod - const k8sCoreV1Api = this.k8SService.makeApiClient(k8s.CoreV1Api); + const k8sCoreV1Api = this.k8SService.makeApiClient(CoreV1Api); const labelSelector = `controller.devfile.io/devworkspace_id=${this.env.getWorkspaceId()}`; const podList = await k8sCoreV1Api.listNamespacedPod({ namespace: this.env.getWorkspaceNamespace(), @@ -68,7 +68,7 @@ export class K8sDevfileServiceImpl implements DevfileService { async updateDevfile(devfile: V1alpha2DevWorkspaceSpecTemplate): Promise { // Grab custom resource object - const customObjectsApi = this.k8SService.makeApiClient(k8s.CustomObjectsApi); + const customObjectsApi = this.k8SService.makeApiClient(CustomObjectsApi); const group = 'workspace.devfile.io'; const version = 'v1alpha2'; diff --git a/code/extensions/che-api/src/impl/k8s-service-impl.ts b/code/extensions/che-api/src/impl/k8s-service-impl.ts index e88fb81e488..3d3ae491cec 100644 --- a/code/extensions/che-api/src/impl/k8s-service-impl.ts +++ b/code/extensions/che-api/src/impl/k8s-service-impl.ts @@ -15,7 +15,8 @@ import { devworkspacePlural, V1alpha2DevWorkspace } from '@devfile/api'; -import * as k8s from '@kubernetes/client-node'; +import { CoreV1Api, CustomObjectsApi, KubeConfig } from '@kubernetes/client-node'; +import type { ApiConstructor, ApiType, V1Secret } from '@kubernetes/client-node'; import * as vscode from 'vscode'; import { injectable } from 'inversify'; @@ -26,14 +27,14 @@ import fetch, { RequestInit } from 'node-fetch'; @injectable() export class K8SServiceImpl implements K8SService { - private coreV1API!: k8s.CoreV1Api; - private customObjectsApi!: k8s.CustomObjectsApi; - private k8sConfig: k8s.KubeConfig; + private coreV1API!: CoreV1Api; + private customObjectsApi!: CustomObjectsApi; + private k8sConfig: KubeConfig; private devWorkspaceName!: string; private devWorkspaceNamespace!: string; constructor() { - this.k8sConfig = new k8s.KubeConfig(); + this.k8sConfig = new KubeConfig(); this.k8sConfig.loadFromCluster(); } @@ -116,31 +117,31 @@ export class K8SServiceImpl implements K8SService { } } - getConfig(): k8s.KubeConfig { + getConfig(): KubeConfig { return this.k8sConfig; } - makeApiClient(apiClientType: k8s.ApiConstructor): T { + makeApiClient(apiClientType: ApiConstructor): T { return this.k8sConfig.makeApiClient(apiClientType); } - getCoreApi(): k8s.CoreV1Api { + getCoreApi(): CoreV1Api { if (!this.coreV1API) { this.k8sConfig.loadFromCluster(); - this.coreV1API = this.k8sConfig.makeApiClient(k8s.CoreV1Api); + this.coreV1API = this.k8sConfig.makeApiClient(CoreV1Api); } return this.coreV1API; } - getCustomObjectsApi(): k8s.CustomObjectsApi { + getCustomObjectsApi(): CustomObjectsApi { if (!this.customObjectsApi) { this.k8sConfig.loadFromCluster(); - this.customObjectsApi = this.k8sConfig.makeApiClient(k8s.CustomObjectsApi); + this.customObjectsApi = this.k8sConfig.makeApiClient(CustomObjectsApi); } return this.customObjectsApi; } - async getSecret(labelSelector?: string): Promise> { + async getSecret(labelSelector?: string): Promise> { const namespace = this.getDevWorkspaceNamespace(); if (!namespace) { throw new Error('Can not get secrets: DEVWORKSPACE_NAMESPACE env variable is not defined'); @@ -159,7 +160,7 @@ export class K8SServiceImpl implements K8SService { } } - async replaceNamespacedSecret(name: string, secret: k8s.V1Secret): Promise { + async replaceNamespacedSecret(name: string, secret: V1Secret): Promise { const namespace = this.getDevWorkspaceNamespace(); if (!namespace) { throw new Error('Can not replace a secret: DEVWORKSPACE_NAMESPACE env variable is not defined'); @@ -173,7 +174,7 @@ export class K8SServiceImpl implements K8SService { }); } - async createNamespacedSecret(secret: k8s.V1Secret): Promise { + async createNamespacedSecret(secret: V1Secret): Promise { const namespace = this.getDevWorkspaceNamespace(); if (!namespace) { throw new Error('Can not create a secret: DEVWORKSPACE_NAMESPACE env variable is not defined'); @@ -186,7 +187,7 @@ export class K8SServiceImpl implements K8SService { }); } - async deleteNamespacedSecret(secret: k8s.V1Secret): Promise { + async deleteNamespacedSecret(secret: V1Secret): Promise { const namespace = this.getDevWorkspaceNamespace(); if (!namespace) { throw new Error('Can not delete a secret: DEVWORKSPACE_NAMESPACE env variable is not defined'); diff --git a/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts b/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts index 33c798d6dc5..78ecfe50a8d 100644 --- a/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts +++ b/code/extensions/che-api/src/impl/k8s-workspace-service-impl.ts @@ -10,7 +10,7 @@ /* eslint-disable header/header */ -import * as k8s from '@kubernetes/client-node'; +import { CustomObjectsApi } from '@kubernetes/client-node'; import { inject, injectable } from 'inversify'; import { K8SServiceImpl } from './k8s-service-impl'; import { K8sDevWorkspaceEnvVariables } from './k8s-devworkspace-env-variables'; @@ -59,7 +59,7 @@ export class K8sWorkspaceServiceImpl implements WorkspaceService { // stopping the workspace is changing the started state to false public async stop(): Promise { - const customObjectsApi = this.k8SService.makeApiClient(k8s.CustomObjectsApi); + const customObjectsApi = this.k8SService.makeApiClient(CustomObjectsApi); const group = 'workspace.devfile.io'; const version = 'v1alpha2'; const namespace = this.env.getWorkspaceNamespace(); diff --git a/code/extensions/che-github-authentication/src/error-handler.ts b/code/extensions/che-github-authentication/src/error-handler.ts index c23ac8b5bfe..822250bdc4f 100644 --- a/code/extensions/che-github-authentication/src/error-handler.ts +++ b/code/extensions/che-github-authentication/src/error-handler.ts @@ -13,7 +13,7 @@ import { V1alpha2DevWorkspaceSpecTemplate } from '@devfile/api'; -import * as k8s from '@kubernetes/client-node'; +import type { V1Secret } from '@kubernetes/client-node'; import { inject, injectable } from 'inversify'; import * as path from 'path'; import * as vscode from 'vscode'; @@ -53,7 +53,7 @@ export class ErrorHandler { } // the token is present, but is expired - private async onTokenExpired(gitCredentialSecrets: Array): Promise { + private async onTokenExpired(gitCredentialSecrets: Array): Promise { const cheSecrets = filterCheSecrets(gitCredentialSecrets); if (cheSecrets.length > 0) { this.onCheControlledTokenExpired(); diff --git a/code/extensions/che-github-authentication/src/k8s-helper.ts b/code/extensions/che-github-authentication/src/k8s-helper.ts index 8b6773d48ef..33303519257 100644 --- a/code/extensions/che-github-authentication/src/k8s-helper.ts +++ b/code/extensions/che-github-authentication/src/k8s-helper.ts @@ -17,6 +17,7 @@ import { V1alpha2DevWorkspace } from '@devfile/api'; import * as k8s from '@kubernetes/client-node'; +import type { CoreV1Api, CustomObjectsApi, KubeConfig, V1Secret } from '@kubernetes/client-node'; import { injectable } from 'inversify'; const PART_OF_LABEL = 'app.kubernetes.io/part-of'; @@ -25,22 +26,21 @@ const CHE_ECLIPSE_ORG_DEVFILE_LABEL = 'che.eclipse.org/devfile'; @injectable() export class K8sHelper { - private coreV1API!: k8s.CoreV1Api; - private customObjectsApi!: k8s.CustomObjectsApi; - private k8sConfig: k8s.KubeConfig; + private coreV1API!: CoreV1Api; + private customObjectsApi!: CustomObjectsApi; + private k8sConfig: KubeConfig; private devWorkspaceName!: string; private devWorkspaceNamespace!: string; - constructor() { this.k8sConfig = new k8s.KubeConfig(); } - getConfig(): k8s.KubeConfig { + getConfig(): KubeConfig { return this.k8sConfig; } - getCoreApi(): k8s.CoreV1Api { + getCoreApi(): CoreV1Api { if (!this.coreV1API) { this.k8sConfig.loadFromDefault(); this.coreV1API = this.k8sConfig.makeApiClient(k8s.CoreV1Api); @@ -48,7 +48,7 @@ export class K8sHelper { return this.coreV1API; } - getCustomObjectsApi(): k8s.CustomObjectsApi { + getCustomObjectsApi(): CustomObjectsApi { if (!this.customObjectsApi) { this.k8sConfig.loadFromCluster(); this.customObjectsApi = this.k8sConfig.makeApiClient(k8s.CustomObjectsApi); @@ -76,7 +76,7 @@ export class K8sHelper { } } - async getSecret(labelSelector?: string): Promise> { + async getSecret(labelSelector?: string): Promise> { const coreV1API = this.getCoreApi(); const namespace = this.getDevWorkspaceNamespace(); if (!namespace) { @@ -126,8 +126,8 @@ export class K8sHelper { } } -export function filterCheSecrets(secrets: k8s.V1Secret[]): k8s.V1Secret[] { - return secrets.filter((secret: k8s.V1Secret) => { +export function filterCheSecrets(secrets: V1Secret[]): V1Secret[] { + return secrets.filter((secret: V1Secret) => { console.log('part of ', secret.metadata!.labels![PART_OF_LABEL]); if (secret.metadata!.labels![PART_OF_LABEL] === CHE_ECLIPSE_ORG_LABEL) { return true; From c6f987ca915581266930cc8b1349d3c44151b8fb Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sat, 28 Mar 2026 20:54:31 +0200 Subject: [PATCH 04/10] test Signed-off-by: Roman Nikitenko --- .../che-api/src/impl/github-service-impl.ts | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/code/extensions/che-api/src/impl/github-service-impl.ts b/code/extensions/che-api/src/impl/github-service-impl.ts index 6b60486fc15..be6a435016f 100644 --- a/code/extensions/che-api/src/impl/github-service-impl.ts +++ b/code/extensions/che-api/src/impl/github-service-impl.ts @@ -35,12 +35,23 @@ const GIT_CREDENTIALS_LABEL_SELECTOR: string = createLabelsSelector(GIT_CREDENTI @injectable() export class GithubServiceImpl implements GithubService { private token: string | undefined; + private tokenInitializationPromise: Promise | undefined; constructor( @inject(Logger) private logger: Logger, @inject(K8SServiceImpl) private readonly k8sService: K8SServiceImpl - ) { - this.initializeToken(); + ) {} + + private async ensureTokenInitialized(): Promise { + if (this.token !== undefined) { + return; + } + if (!this.tokenInitializationPromise) { + this.tokenInitializationPromise = this.initializeToken().finally(() => { + this.tokenInitializationPromise = undefined; + }); + } + await this.tokenInitializationPromise; } private checkToken(): void { @@ -50,17 +61,20 @@ export class GithubServiceImpl implements GithubService { } async getToken(): Promise { + await this.ensureTokenInitialized(); this.checkToken(); return this.token!; } async getUser(): Promise { + await this.ensureTokenInitialized(); this.checkToken(); const result = await this.fetchGithubUser(this.token!); return result.user; } async getTokenScopes(token: string): Promise { + await this.ensureTokenInitialized(); this.checkToken(); const result = await this.fetchGithubUser(token); return result.scopes; @@ -128,7 +142,8 @@ export class GithubServiceImpl implements GithubService { } // another token should be used by the Github Service after removing the Device Authentication token - this.initializeToken(); + this.token = undefined; + await this.ensureTokenInitialized(); } private async initializeToken(): Promise { From 53764109ec6e9d9e78b156bf4c42767f83943897 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sat, 28 Mar 2026 21:21:00 +0200 Subject: [PATCH 05/10] test Signed-off-by: Roman Nikitenko --- code/extensions/che-api/src/impl/k8s-service-impl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/code/extensions/che-api/src/impl/k8s-service-impl.ts b/code/extensions/che-api/src/impl/k8s-service-impl.ts index 3d3ae491cec..751d0284cd9 100644 --- a/code/extensions/che-api/src/impl/k8s-service-impl.ts +++ b/code/extensions/che-api/src/impl/k8s-service-impl.ts @@ -35,7 +35,6 @@ export class K8SServiceImpl implements K8SService { constructor() { this.k8sConfig = new KubeConfig(); - this.k8sConfig.loadFromCluster(); } async ensureKubernetesServiceHostWhitelisted(): Promise { From f1b7cfda75f2079bdc87780ecb7d14f983d9bba9 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sat, 28 Mar 2026 21:49:17 +0200 Subject: [PATCH 06/10] test Signed-off-by: Roman Nikitenko --- .../che-github-authentication/src/k8s-helper.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/extensions/che-github-authentication/src/k8s-helper.ts b/code/extensions/che-github-authentication/src/k8s-helper.ts index 33303519257..f6ed32847c4 100644 --- a/code/extensions/che-github-authentication/src/k8s-helper.ts +++ b/code/extensions/che-github-authentication/src/k8s-helper.ts @@ -16,8 +16,8 @@ import { devworkspacePlural, V1alpha2DevWorkspace } from '@devfile/api'; -import * as k8s from '@kubernetes/client-node'; -import type { CoreV1Api, CustomObjectsApi, KubeConfig, V1Secret } from '@kubernetes/client-node'; +import { CoreV1Api, CustomObjectsApi, KubeConfig } from '@kubernetes/client-node'; +import type { V1Secret } from '@kubernetes/client-node'; import { injectable } from 'inversify'; const PART_OF_LABEL = 'app.kubernetes.io/part-of'; @@ -33,7 +33,7 @@ export class K8sHelper { private devWorkspaceNamespace!: string; constructor() { - this.k8sConfig = new k8s.KubeConfig(); + this.k8sConfig = new KubeConfig(); } getConfig(): KubeConfig { @@ -43,7 +43,7 @@ export class K8sHelper { getCoreApi(): CoreV1Api { if (!this.coreV1API) { this.k8sConfig.loadFromDefault(); - this.coreV1API = this.k8sConfig.makeApiClient(k8s.CoreV1Api); + this.coreV1API = this.k8sConfig.makeApiClient(CoreV1Api); } return this.coreV1API; } @@ -51,7 +51,7 @@ export class K8sHelper { getCustomObjectsApi(): CustomObjectsApi { if (!this.customObjectsApi) { this.k8sConfig.loadFromCluster(); - this.customObjectsApi = this.k8sConfig.makeApiClient(k8s.CustomObjectsApi); + this.customObjectsApi = this.k8sConfig.makeApiClient(CustomObjectsApi); } return this.customObjectsApi; } From a5aa9ef190451c2a98eb95b44568e163b49b2943 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sat, 28 Mar 2026 22:12:32 +0200 Subject: [PATCH 07/10] test Signed-off-by: Roman Nikitenko --- code/extensions/che-api/src/extension.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/code/extensions/che-api/src/extension.ts b/code/extensions/che-api/src/extension.ts index 5edee5f215b..07f23983a4c 100644 --- a/code/extensions/che-api/src/extension.ts +++ b/code/extensions/che-api/src/extension.ts @@ -45,22 +45,18 @@ export async function activate(_extensionContext: vscode.ExtensionContext): Prom container.bind(TelemetryService).to(K8sTelemetryServiceImpl).inSingletonScope(); container.bind(Logger).toSelf().inSingletonScope(); - const devfileService = container.get(DevfileService) as DevfileService; - const workspaceService = container.get(WorkspaceService) as WorkspaceService; - const githubService = container.get(GithubService) as GithubService; - const telemetryService = container.get(TelemetryService) as TelemetryService; const api: Api = { getDevfileService(): DevfileService { - return devfileService; + return container.get(DevfileService) as DevfileService; }, getWorkspaceService(): WorkspaceService { - return workspaceService; + return container.get(WorkspaceService) as WorkspaceService; }, getGithubService(): GithubService { - return githubService; + return container.get(GithubService) as GithubService; }, getTelemetryService(): TelemetryService { - return telemetryService; + return container.get(TelemetryService) as TelemetryService; }, }; From 5694f754f9c8fe95f60f90187860814d94e78170 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sat, 28 Mar 2026 23:02:35 +0200 Subject: [PATCH 08/10] test Signed-off-by: Roman Nikitenko --- code/extensions/che-api/src/extension.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/extensions/che-api/src/extension.ts b/code/extensions/che-api/src/extension.ts index 07f23983a4c..b100e5669ff 100644 --- a/code/extensions/che-api/src/extension.ts +++ b/code/extensions/che-api/src/extension.ts @@ -71,7 +71,5 @@ export async function activate(_extensionContext: vscode.ExtensionContext): Prom _extensionContext.environmentVariableCollection.replace('WORKSPACE_NAMESPACE', workspaceNamespace); _extensionContext.environmentVariableCollection.replace('PROJECTS_ROOT', projectsRoot); - await container.get(K8SServiceImpl).ensureKubernetesServiceHostWhitelisted(); - return api; } From b55747a6acec1d0b31e865dec61164659079faf3 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sun, 29 Mar 2026 17:16:10 +0300 Subject: [PATCH 09/10] test Signed-off-by: Roman Nikitenko --- code/extensions/che-github-authentication/src/extension.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/code/extensions/che-github-authentication/src/extension.ts b/code/extensions/che-github-authentication/src/extension.ts index 8a40d50d4f6..e4729658e38 100644 --- a/code/extensions/che-github-authentication/src/extension.ts +++ b/code/extensions/che-github-authentication/src/extension.ts @@ -45,7 +45,6 @@ export async function activate(context: vscode.ExtensionContext): Promise vscode.authentication.registerAuthenticationProvider('github', 'GitHub', authenticationProvider); container.bind(DeviceAuthentication).toSelf().inSingletonScope(); - container.get(DeviceAuthentication); } export function deactivate(): void { From 4ff8fca302508f50a7172033ad8a38d4bd20344e Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Sun, 29 Mar 2026 20:03:46 +0300 Subject: [PATCH 10/10] test Signed-off-by: Roman Nikitenko --- code/extensions/che-api/src/extension.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/code/extensions/che-api/src/extension.ts b/code/extensions/che-api/src/extension.ts index b100e5669ff..a329fa69931 100644 --- a/code/extensions/che-api/src/extension.ts +++ b/code/extensions/che-api/src/extension.ts @@ -21,18 +21,28 @@ import * as vscode from 'vscode'; import { Api } from './api/api'; import { DevfileService } from './api/devfile-service'; import { K8SService } from './api/k8s-service'; -import { K8sDevfileServiceImpl } from './impl/k8s-devfile-service-impl'; -import { K8SServiceImpl } from './impl/k8s-service-impl'; -import { K8sDevWorkspaceEnvVariables } from './impl/k8s-devworkspace-env-variables'; import { WorkspaceService } from './api/workspace-service'; -import { K8sWorkspaceServiceImpl } from './impl/k8s-workspace-service-impl'; import { GithubService } from './api/github-service'; -import { GithubServiceImpl } from './impl/github-service-impl'; import { TelemetryService } from './api/telemetry-service'; -import { K8sTelemetryServiceImpl } from './impl/k8s-telemetry-service-impl'; import { Logger } from './logger'; export async function activate(_extensionContext: vscode.ExtensionContext): Promise { + const [ + { K8sDevfileServiceImpl }, + { K8SServiceImpl }, + { K8sDevWorkspaceEnvVariables }, + { K8sWorkspaceServiceImpl }, + { GithubServiceImpl }, + { K8sTelemetryServiceImpl }, + ] = await Promise.all([ + import('./impl/k8s-devfile-service-impl'), + import('./impl/k8s-service-impl'), + import('./impl/k8s-devworkspace-env-variables'), + import('./impl/k8s-workspace-service-impl'), + import('./impl/github-service-impl'), + import('./impl/k8s-telemetry-service-impl'), + ]); + const container = new Container(); container.bind(K8sDevfileServiceImpl).toSelf().inSingletonScope(); container.bind(DevfileService).to(K8sDevfileServiceImpl).inSingletonScope();