From 1d848963562bd73285953be198c734eb3a087132 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Mon, 3 Aug 2026 16:35:05 -0300 Subject: [PATCH 1/7] feat: refactor context management and improve command conditions in package.json --- package.json | 62 +++++++++--------- src/@enum/context.ts | 7 ++ src/@enum/index.ts | 1 + src/core/extension.ts | 27 ++++---- src/events/activate.ts | 20 ++---- src/events/authorized.ts | 6 +- src/events/missingToken.ts | 6 +- src/events/unauthorized.ts | 6 +- src/extension.ts | 3 +- src/language/BaseLanguageProvider.ts | 41 ++++++++---- src/modules/async-queue/AsyncQueue.ts | 2 +- src/modules/async-queue/AsyncQueueEntity.ts | 10 ++- .../async-queue/AsyncQueueRepository.ts | 21 ++---- src/providers/TeamAppTreeDataProvider.ts | 5 +- src/providers/UserAppTreeDataProvider.ts | 6 +- src/services/discloud/REST.ts | 64 ++++++++++--------- src/storage/secrets.ts | 8 +-- 17 files changed, 153 insertions(+), 142 deletions(-) create mode 100644 src/@enum/context.ts diff --git a/package.json b/package.json index 8feaf75e..467802f5 100644 --- a/package.json +++ b/package.json @@ -859,143 +859,143 @@ { "command": "discloud.apps.backup", "group": "1", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.commit", "group": "1", - "when": "view == discloudUserApps && discloudAppLength && workspaceFolderCount" + "when": "view == discloudUserApps && discloudUserAppCount && workspaceFolderCount" }, { "command": "discloud.apps.copy.id", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.delete", "group": "1", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.import", "group": "1", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.import", "group": "inline", - "when": "view == discloudUserApps && discloudAppLength && viewItem =~ /^TreeItem/" + "when": "view == discloudUserApps && discloudUserAppCount && viewItem =~ /^TreeItem/" }, { "command": "discloud.apps.logs", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.logs", "group": "inline", - "when": "view == discloudUserApps && discloudAppLength && viewItem =~ /^TreeItem/" + "when": "view == discloudUserApps && discloudUserAppCount && viewItem =~ /^TreeItem/" }, { "submenu": "discloud.apps.mods", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.profile.avatar", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.profile.name", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.ram", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.restart", "group": "0", - "when": "view == discloudUserApps && discloudAppLength" + "when": "view == discloudUserApps && discloudUserAppCount" }, { "command": "discloud.apps.start", "group": "0", - "when": "view == discloudUserApps && discloudAppLength && viewItem =~ /online\":false/" + "when": "view == discloudUserApps && discloudUserAppCount && viewItem =~ /online\":false/" }, { "command": "discloud.apps.status", "group": "inline", - "when": "view == discloudUserApps && discloudAppLength && viewItem =~ /^TreeItem/" + "when": "view == discloudUserApps && discloudUserAppCount && viewItem =~ /^TreeItem/" }, { "command": "discloud.apps.stop", "group": "0", - "when": "view == discloudUserApps && discloudAppLength && viewItem =~ /online\":true/" + "when": "view == discloudUserApps && discloudUserAppCount && viewItem =~ /online\":true/" }, { "command": "discloud.apps.terminal", "group": "1", - "when": "view == discloudUserApps && discloudAppLength && viewItem =~ /online\":true/" + "when": "view == discloudUserApps && discloudUserAppCount && viewItem =~ /online\":true/" }, { "command": "discloud.upload", "group": "inline", - "when": "view == discloudUserApps && !discloudAppLength && workspaceFolderCount" + "when": "view == discloudUserApps && !discloudUserAppCount && workspaceFolderCount" }, { "command": "discloud.team.backup", "group": "1", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /backup_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /backup_app/" }, { "command": "discloud.team.commit", "group": "1", - "when": "view == discloudTeamApps && discloudTeamAppLength && workspaceFolderCount && viewItem =~ /commit_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && workspaceFolderCount && viewItem =~ /commit_app/" }, { "command": "discloud.team.copy.id", - "when": "view == discloudTeamApps && discloudTeamAppLength" + "when": "view == discloudTeamApps && discloudTeamAppCount" }, { "command": "discloud.team.import", "group": "1", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /backup_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /backup_app/" }, { "command": "discloud.team.import", "group": "inline", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /^TreeItem/ && viewItem =~ /backup_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /^TreeItem/ && viewItem =~ /backup_app/" }, { "command": "discloud.team.logs", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /logs_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /logs_app/" }, { "command": "discloud.team.logs", "group": "inline", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /^TreeItem/ && viewItem =~ /logs_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /^TreeItem/ && viewItem =~ /logs_app/" }, { "command": "discloud.team.ram", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /edit_ram/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /edit_ram/" }, { "command": "discloud.team.restart", "group": "0", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /restart_app/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /restart_app/" }, { "command": "discloud.team.start", "group": "0", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /start_app/ && viewItem =~ /online\":false/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /start_app/ && viewItem =~ /online\":false/" }, { "command": "discloud.team.status", "group": "inline", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /^TreeItem/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /^TreeItem/" }, { "command": "discloud.team.stop", "group": "0", - "when": "view == discloudTeamApps && discloudTeamAppLength && viewItem =~ /stop_app/ && viewItem =~ /online\":true/" + "when": "view == discloudTeamApps && discloudTeamAppCount && viewItem =~ /stop_app/ && viewItem =~ /online\":true/" }, { "command": "discloud.user.copy.id", @@ -1346,4 +1346,4 @@ "npm-run-all": "^4.1.5", "typescript": "^6.0.3" } -} +} \ No newline at end of file diff --git a/src/@enum/context.ts b/src/@enum/context.ts new file mode 100644 index 00000000..d9719e84 --- /dev/null +++ b/src/@enum/context.ts @@ -0,0 +1,7 @@ +export enum ExtensionContextId { + discloudAuthorized = "discloudAuthorized", + discloudInitialized = "discloudInitialized", + discloudTeamAppCount = "discloudTeamAppCount", + discloudUserAppCount = "discloudUserAppCount", + discloudUnauthorized = "discloudUnauthorized", +} diff --git a/src/@enum/index.ts b/src/@enum/index.ts index d158c576..ac897510 100644 --- a/src/@enum/index.ts +++ b/src/@enum/index.ts @@ -1 +1,2 @@ export * from "./api"; +export * from "./context"; diff --git a/src/core/extension.ts b/src/core/extension.ts index 7ff85222..6642b669 100644 --- a/src/core/extension.ts +++ b/src/core/extension.ts @@ -1,7 +1,8 @@ import { t } from "@vscode/l10n"; import { EventEmitter } from "events"; import { normalize } from "path"; -import { type Disposable, type ExtensionContext, type LogOutputChannel, type OutputChannel, type SecretStorage, Uri, window, workspace } from "vscode"; +import { commands, type Disposable, type ExtensionContext, type LogOutputChannel, type OutputChannel, type SecretStorage, Uri, window, workspace } from "vscode"; +import { type ExtensionContextId } from "../@enum"; import type { Events, GetWorkspaceFolderOptions, IGlobalStateStorage, TaskData } from "../@types"; import AuthenticationProviderContainer from "../authentication/providers"; import { commandsRegister } from "../commands"; @@ -23,6 +24,13 @@ import VSUser from "../structures/VSUser"; import { ConfigKeys } from "../utils/constants"; import FileSystem from "../utils/FileSystem"; +const _workspaceIgnoreConfigKeys = Object.freeze([ + ConfigKeys.appBackupDir, + ConfigKeys.appImportDir, + ConfigKeys.teamBackupDir, + ConfigKeys.teamImportDir, +]); + export default class ExtensionCore extends EventEmitter implements Disposable { constructor() { super({ captureRejections: true }); @@ -70,12 +78,7 @@ export default class ExtensionCore extends EventEmitter implements Dispo } get workspaceIgnoreList() { - return [ - ConfigKeys.appBackupDir, - ConfigKeys.appImportDir, - ConfigKeys.teamBackupDir, - ConfigKeys.teamImportDir, - ] + return _workspaceIgnoreConfigKeys .reduce((acc, config) => { const data = this.config.get(config); if (data) return acc.concat(normalize(data)); @@ -84,6 +87,10 @@ export default class ExtensionCore extends EventEmitter implements Dispo .concat("discloud", `${workspace.name}.zip`); } + async setContext(contextId: ExtensionContextId, ...values: any[]) { + await commands.executeCommand("setContext", contextId, ...values); + } + debug(...args: Parameters) { this.emit("debug", this, ...args); } @@ -151,12 +158,8 @@ export default class ExtensionCore extends EventEmitter implements Dispo } } - setContext(context: ExtensionContext) { - Object.defineProperties(this, { context: { value: context } }); - } - async activate(context: ExtensionContext = this.context) { - if (!this.context) this.setContext(context); + Object.defineProperties(this, { context: { value: context } }); this.logger.debug("Activate: begin"); diff --git a/src/events/activate.ts b/src/events/activate.ts index 0eb84629..c79d5148 100644 --- a/src/events/activate.ts +++ b/src/events/activate.ts @@ -1,24 +1,12 @@ import { type AuthenticationSessionAccountInformation, commands, type ExtensionContext, workspace } from "vscode"; +import { ExtensionContextId } from "../@enum"; import { AuthenticationProviderId } from "../authentication/enum/providers"; import type ExtensionCore from "../core/extension"; import BaseLanguageProvider from "../language/BaseLanguageProvider"; -import CompletionItemProvider from "../language/CompletionItemProvider"; -import LanguageConfigurationProvider from "../language/LanguageConfigurationProvider"; -import { DISCLOUD_CONFIG_SCHEMA_FILE_NAME, GlobalStorageKeys } from "../utils/constants"; +import { GlobalStorageKeys } from "../utils/constants"; export default async function (core: ExtensionCore, context: ExtensionContext) { - queueMicrotask(async function () { - try { - const path = context.asAbsolutePath(DISCLOUD_CONFIG_SCHEMA_FILE_NAME); - - const schema = await BaseLanguageProvider.getSchemaFromPath(path); - - new CompletionItemProvider(context, schema); - new LanguageConfigurationProvider(context, schema); - } catch (error: any) { - core.logger.error(error); - } - }); + queueMicrotask(() => BaseLanguageProvider.startProviders(context).catch(core.logger.error)); const disposableChangeConfiguration = workspace.onDidChangeConfiguration(event => { if (event.affectsConfiguration("discloud.app.sort")) return core.userAppTree.refresh(); @@ -61,7 +49,7 @@ export default async function (core: ExtensionCore, context: ExtensionContext) { core.statusBar.reset(); } - await commands.executeCommand("setContext", "discloudInitialized", true); + await core.setContext(ExtensionContextId.discloudInitialized, true); } async function migrateAuthenticationProvider(core: ExtensionCore) { diff --git a/src/events/authorized.ts b/src/events/authorized.ts index 28f420ad..03596036 100644 --- a/src/events/authorized.ts +++ b/src/events/authorized.ts @@ -1,10 +1,10 @@ -import { commands } from "vscode"; +import { ExtensionContextId } from "../@enum"; import type ExtensionCore from "../core/extension"; export default async function (core: ExtensionCore) { await Promise.all([ - commands.executeCommand("setContext", "discloudAuthorized", true), - commands.executeCommand("setContext", "discloudUnauthorized", false), + core.setContext(ExtensionContextId.discloudAuthorized, true), + core.setContext(ExtensionContextId.discloudUnauthorized, false), ]); core.api.authorized = true; diff --git a/src/events/missingToken.ts b/src/events/missingToken.ts index f3f65171..f8875e38 100644 --- a/src/events/missingToken.ts +++ b/src/events/missingToken.ts @@ -1,11 +1,11 @@ import { t } from "@vscode/l10n"; -import { commands } from "vscode"; +import { ExtensionContextId } from "../@enum"; import type ExtensionCore from "../core/extension"; export default async function (core: ExtensionCore) { await Promise.all([ - commands.executeCommand("setContext", "discloudAuthorized", false), - commands.executeCommand("setContext", "discloudUnauthorized", false), + core.setContext(ExtensionContextId.discloudAuthorized, false), + core.setContext(ExtensionContextId.discloudUnauthorized, false), ]); core.api.authorized = false; diff --git a/src/events/unauthorized.ts b/src/events/unauthorized.ts index 59a0b17d..cfc2c999 100644 --- a/src/events/unauthorized.ts +++ b/src/events/unauthorized.ts @@ -1,10 +1,10 @@ -import { commands } from "vscode"; +import { ExtensionContextId } from "../@enum"; import type ExtensionCore from "../core/extension"; export default async function (core: ExtensionCore) { await Promise.all([ - commands.executeCommand("setContext", "discloudAuthorized", false), - commands.executeCommand("setContext", "discloudUnauthorized", true), + core.setContext(ExtensionContextId.discloudAuthorized, false), + core.setContext(ExtensionContextId.discloudUnauthorized, true), ]); core.api.authorized = false; diff --git a/src/extension.ts b/src/extension.ts index 76c8d68a..271c2783 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,8 +7,7 @@ export default core; export async function activate(context: ExtensionContext) { await localize(context); - core.setContext(context); - await core.activate(); + await core.activate(context); } // This method is called when your extension is deactivated diff --git a/src/language/BaseLanguageProvider.ts b/src/language/BaseLanguageProvider.ts index 8288db94..76261718 100644 --- a/src/language/BaseLanguageProvider.ts +++ b/src/language/BaseLanguageProvider.ts @@ -4,33 +4,46 @@ import type { JSONSchema7 } from "json-schema"; import { compileSchema, type SchemaNode } from "json-schema-library"; import { parseEnv } from "util"; import { type ExtensionContext, Position, Range, type TextDocument } from "vscode"; -import { MAX_LANGUAGE_PROVIDER_READ_LINES } from "../utils/constants"; +import { DISCLOUD_CONFIG_SCHEMA_FILE_NAME, MAX_LANGUAGE_PROVIDER_READ_LINES } from "../utils/constants"; -const STRING_BOOLEAN = new Set(["false", "true"]); -const start = new Position(0, 0); -const end = new Position(MAX_LANGUAGE_PROVIDER_READ_LINES, 0); -const range = new Range(start, end); +const _booleanAsString = new Set(["false", "true"]); +const _start = new Position(0, 0); +const _end = new Position(MAX_LANGUAGE_PROVIDER_READ_LINES, 0); +const _range = new Range(_start, _end); +const _noSchemaId: symbol = Symbol("No Schema"); export default class BaseLanguageProvider { static readonly #schemas: Record = {}; - static readonly #drafts: Record = {}; - declare readonly draft: SchemaNode; - declare readonly scopes: string[]; + static readonly #drafts: Map = new Map(); static async getSchemaFromPath(path: string) { return BaseLanguageProvider.#schemas[path] ??= JSON.parse(await readFile(path, "utf8")); } + static async startProviders(context: ExtensionContext) { + const path = context.asAbsolutePath(DISCLOUD_CONFIG_SCHEMA_FILE_NAME); + + const schema = await BaseLanguageProvider.getSchemaFromPath(path); + + const { default: CompletionItemProvider } = await import("./CompletionItemProvider"); + const { default: LanguageConfigurationProvider } = await import("./LanguageConfigurationProvider"); + + new CompletionItemProvider(context, schema); + new LanguageConfigurationProvider(context, schema); + } + constructor(readonly context: ExtensionContext, readonly schema: JSONSchema7) { this.scopes = Object.keys(this.schema.properties ?? {}); - this.draft = schema.$id - ? BaseLanguageProvider.#drafts[schema.$id] ??= compileSchema(schema) - : compileSchema(schema); + this.draft = BaseLanguageProvider.#drafts + .getOrInsertComputed(schema.$id ?? _noSchemaId, () => compileSchema(schema)); } + declare readonly draft: SchemaNode; + declare readonly scopes: string[]; + transformConfigToJSON(document: TextDocument) { - return this.#parseValues(parseEnv(document.getText(range))); + return this.#parseValues(parseEnv(document.getText(_range))); } validateJsonSchema(data: Record) { @@ -47,13 +60,13 @@ export default class BaseLanguageProvider { if (key in obj) obj[key] = obj[key].split(/\s*,\s*/g).filter(Boolean); key = DiscloudConfigScopes.AUTORESTART; - if (key in obj && STRING_BOOLEAN.has(obj[key])) obj[key] = obj[key] == "true"; + if (key in obj && _booleanAsString.has(obj[key])) obj[key] = obj[key] == "true"; key = DiscloudConfigScopes.RAM; if (key in obj && obj[key]) obj[key] = Number(obj[key]); key = DiscloudConfigScopes.VLAN; - if (key in obj && STRING_BOOLEAN.has(obj[key])) obj[key] = obj[key] == "true"; + if (key in obj && _booleanAsString.has(obj[key])) obj[key] = obj[key] == "true"; return obj; } diff --git a/src/modules/async-queue/AsyncQueue.ts b/src/modules/async-queue/AsyncQueue.ts index eaa29b1f..7211383a 100644 --- a/src/modules/async-queue/AsyncQueue.ts +++ b/src/modules/async-queue/AsyncQueue.ts @@ -11,7 +11,7 @@ export default class AsyncQueue { wait(key?: AsyncQueueKey) { const entity = this.#repository.push(key); - if (entity.index) return entity.promise; + if (entity.isNotFirst) return entity.promise; return Promise.resolve(); } diff --git a/src/modules/async-queue/AsyncQueueEntity.ts b/src/modules/async-queue/AsyncQueueEntity.ts index e61b3db7..5bd1168b 100644 --- a/src/modules/async-queue/AsyncQueueEntity.ts +++ b/src/modules/async-queue/AsyncQueueEntity.ts @@ -1,8 +1,16 @@ -export default class AsyncQueueEntity { +const _firstIndex = 0; + +export default class AsyncQueueEntity implements PromiseWithResolvers { constructor( readonly index: number, ) { } + /** @readonly */ + get isFirst() { return this.index === _firstIndex; } + + /** @readonly */ + get isNotFirst() { return this.index !== _firstIndex; }; + readonly #promiseWithResolvers: PromiseWithResolvers = Promise.withResolvers(); /** @readonly */ diff --git a/src/modules/async-queue/AsyncQueueRepository.ts b/src/modules/async-queue/AsyncQueueRepository.ts index 1434f556..783d678f 100644 --- a/src/modules/async-queue/AsyncQueueRepository.ts +++ b/src/modules/async-queue/AsyncQueueRepository.ts @@ -2,35 +2,26 @@ import Queue from "yocto-queue"; import AsyncQueueEntity from "./AsyncQueueEntity"; import type { AsyncQueueKey } from "./types"; +// eslint-disable-next-line func-style +const _queueFactory = () => new Queue(); + export default class AsyncQueueRepository { readonly #cache: Map> = new Map(); readonly #internalKey: symbol = Symbol("internal"); - #getCached(key?: AsyncQueueKey) { - key ??= this.#internalKey; - - let cached = this.#cache.get(key); - if (cached) return cached; - - cached = new Queue(); - this.#cache.set(key, cached); - - return cached; - } - getSize(key?: AsyncQueueKey) { - return this.#getCached(key).size; + return this.#cache.getOrInsertComputed(key ?? this.#internalKey, _queueFactory).size; } push(key?: AsyncQueueKey) { - const cached = this.#getCached(key); + const cached = this.#cache.getOrInsertComputed(key ?? this.#internalKey, _queueFactory); const entity = new AsyncQueueEntity(cached.size); cached.enqueue(entity); return entity; } shift(key?: AsyncQueueKey) { - const cached = this.#getCached(key); + const cached = this.#cache.getOrInsertComputed(key ?? this.#internalKey, _queueFactory); cached.dequeue(); cached.peek()?.resolve(); } diff --git a/src/providers/TeamAppTreeDataProvider.ts b/src/providers/TeamAppTreeDataProvider.ts index 4dd5f810..bc397d51 100644 --- a/src/providers/TeamAppTreeDataProvider.ts +++ b/src/providers/TeamAppTreeDataProvider.ts @@ -1,6 +1,7 @@ import { Routes, type ApiStatusApp, type ApiTeamApp, type BaseApiApp, type RESTGetApiAppStatusResult, type RESTGetApiTeamResult } from "@discloudapp/api-types/v2"; import { t } from "@vscode/l10n"; -import { commands, window, type ProviderResult, type TreeItem } from "vscode"; +import { window, type ProviderResult, type TreeItem } from "vscode"; +import { ExtensionContextId } from "../@enum"; import type ExtensionCore from "../core/extension"; import EmptyAppListTreeItem from "../structures/EmptyAppListTreeItem"; import TeamAppTreeItem from "../structures/TeamAppTreeItem"; @@ -110,7 +111,7 @@ export default class TeamAppTreeDataProvider extends BaseTreeDataProvider } refresh(data?: Item | Item[] | null) { - commands.executeCommand("setContext", "discloudTeamAppLength", this.size); + this.core.setContext(ExtensionContextId.discloudTeamAppCount, this.size); super.refresh(data); } diff --git a/src/providers/UserAppTreeDataProvider.ts b/src/providers/UserAppTreeDataProvider.ts index 2b8cd084..9e857588 100644 --- a/src/providers/UserAppTreeDataProvider.ts +++ b/src/providers/UserAppTreeDataProvider.ts @@ -1,7 +1,7 @@ import { Routes, type ApiStatusApp, type BaseApiApp, type RESTGetApiAppStatusResult } from "@discloudapp/api-types/v2"; import { t } from "@vscode/l10n"; -import { commands, window, type ProviderResult, type TreeItem } from "vscode"; -import { type AppType } from "../@enum"; +import { window, type ProviderResult, type TreeItem } from "vscode"; +import { ExtensionContextId, type AppType } from "../@enum"; import type { ApiVscodeApp } from "../@types"; import type ExtensionCore from "../core/extension"; import DisposableMap from "../structures/DisposableMap"; @@ -158,7 +158,7 @@ export default class UserAppTreeDataProvider extends BaseTreeDataProvider } refresh(data?: Item | Item[] | null) { - commands.executeCommand("setContext", "discloudAppLength", this.size); + this.core.setContext(ExtensionContextId.discloudUserAppCount, this.size); super.refresh(data); } diff --git a/src/services/discloud/REST.ts b/src/services/discloud/REST.ts index 36c1e2dc..6705b157 100644 --- a/src/services/discloud/REST.ts +++ b/src/services/discloud/REST.ts @@ -1,6 +1,7 @@ import { RouteBases } from "@discloudapp/api-types/v2"; import { t } from "@vscode/l10n"; import { EventEmitter } from "events"; +import { constants } from "http2"; import { window } from "vscode"; import type ExtensionCore from "../../core/extension"; import AsyncQueue from "../../modules/async-queue"; @@ -8,26 +9,34 @@ import { RequestMethod } from "./enum"; import DiscloudAPIError from "./errors/api"; import type { InternalRequestData, RequestData, RESTOptions, RouteLike } from "./types"; +const _defaultRateLimitLimit = 60; +const _minimumRateRemaining = 1; +const _sInMs = 1_000; + export default class REST extends EventEmitter { - limit = 60; - remaining = 60; - reset = 60; - declare time: number; - authorized: boolean = true; + constructor(readonly core: ExtensionCore, options?: Partial) { + super({ captureRejections: true }); + + this.options = options ?? {}; + } + declare readonly options: Partial; readonly #queue = new AsyncQueue(); + authorized: boolean = true; - get baseURL() { - return RouteBases.api; - } + #limit = _defaultRateLimitLimit; + #remaining = _defaultRateLimitLimit; + #reset = _defaultRateLimitLimit; + #time!: number; - get limited() { - return this.remaining < 1; - } + get baseURL() { return RouteBases.api; } - get timeToReset(): number { - return this.reset * 1000 + this.time - Date.now(); - } + get limit(): number { return this.#limit; } + get remaining(): number { return this.#remaining; } + get reset(): number { return this.#reset; } + + get limited(): boolean { return this.#remaining < _minimumRateRemaining; } + get timeToReset(): number { return this.#reset * _sInMs + this.#time - Date.now(); } getSession() { return this.core.auth.getSession(); @@ -38,12 +47,6 @@ export default class REST extends EventEmitter { return session?.accessToken; } - constructor(readonly core: ExtensionCore, options?: Partial) { - super(); - - this.options = options ?? {}; - } - delete(fullRoute: RouteLike, options: RequestData = {}): Promise { return this.#raw(Object.assign({}, options, { fullRoute, method: RequestMethod.Delete })); } @@ -81,7 +84,7 @@ export default class REST extends EventEmitter { if (!this.authorized) return null; if (this.limited) { - this.core.emit("rateLimited", this.core, { reset: this.reset, time: this.time }); + this.core.emit("rateLimited", this.core, { reset: this.reset, time: this.#time }); return null; } @@ -107,13 +110,12 @@ export default class REST extends EventEmitter { } } - queueMicrotask(() => this.core.emit("debug", - this.core, + queueMicrotask(() => this.core.debug( "Request:", pathname, "Headers:", Object.entries(config.headers!).map(([k, v]) => `${k}:${typeof v}(${`${v}`.length})`).join(" "), )); - this.remaining--; + this.#remaining--; let response: Response; try { response = await fetch(url, config); @@ -134,7 +136,7 @@ export default class REST extends EventEmitter { if (!response.ok) { switch (response.status) { - case 401: + case constants.HTTP_STATUS_UNAUTHORIZED: this.core.emit("unauthorized", this.core); break; } @@ -159,7 +161,7 @@ export default class REST extends EventEmitter { const url = new URL(this.baseURL + request.fullRoute); const formData = new FormData(); - const headers = new Headers(Object.assign({}, { + const headers = new Headers(Object.assign({ "api-token": await this.getToken(), "User-Agent": this.options.userAgent, }, request.headers)); @@ -217,15 +219,15 @@ export default class REST extends EventEmitter { } #resolveResponseHeaders(headers: Headers) { - this.time = Date.now(); + this.#time = Date.now(); const Limit = parseInt(headers.get("ratelimit-limit")!); const Remaining = parseInt(headers.get("ratelimit-remaining")!); const Reset = parseInt(headers.get("ratelimit-reset")!); - if (!isNaN(Limit)) this.limit = Math.max(Limit, 0); - if (!isNaN(Remaining)) this.remaining = Math.max(Remaining, 0); + if (!isNaN(Limit)) this.#limit = Math.max(Limit, 0); + if (!isNaN(Remaining)) this.#remaining = Math.max(Remaining, 0); if (!isNaN(Reset)) { - this.reset = Math.max(Reset, 0); + this.#reset = Math.max(Reset, 0); this.#initRateLimitResetTimer(); } } @@ -235,7 +237,7 @@ export default class REST extends EventEmitter { if (this.#timer) clearTimeout(this.#timer); this.#timer = setTimeout(() => { this.#timer = null; - this.remaining = this.limit; + this.#remaining = this.#limit; }, this.timeToReset).unref(); } diff --git a/src/storage/secrets.ts b/src/storage/secrets.ts index 45b2d75e..c76bc9b7 100644 --- a/src/storage/secrets.ts +++ b/src/storage/secrets.ts @@ -3,9 +3,9 @@ import type vscode from "vscode"; export default class SecretStorage implements vscode.SecretStorage { constructor( protected readonly secrets: vscode.SecretStorage, - ) { - this.onDidChange = secrets.onDidChange.bind(secrets); - } + ) { } + + get onDidChange() { return this.secrets.onDidChange; } delete(key: string) { return this.secrets.delete(key); @@ -20,8 +20,6 @@ export default class SecretStorage implements vscode.SecretStorage { return this.secrets.keys(); } - declare onDidChange: vscode.Event; - store(key: string, value: string): Thenable { return this.secrets.store(key, value); } From 91e27fa3d2ed1afaf2d6e04582f2bc1c84d31a37 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Mon, 3 Aug 2026 16:56:02 -0300 Subject: [PATCH 2/7] feat: refactor error handling and rate limiting logic --- src/events/error.ts | 5 ++++- src/events/rateLimited.ts | 9 +++++---- src/services/discloud/REST.ts | 5 ++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/events/error.ts b/src/events/error.ts index 2baf5067..cbc52062 100644 --- a/src/events/error.ts +++ b/src/events/error.ts @@ -1,9 +1,12 @@ import { t } from "@vscode/l10n"; +import { constants } from "http2"; import { CancellationError, commands, version, window } from "vscode"; import type ExtensionCore from "../core/extension"; import WarningError from "../errors/warning"; import DiscloudAPIError from "../services/discloud/errors/api"; +const _belowInternalServerError = constants.HTTP_STATUS_INTERNAL_SERVER_ERROR - 1; + export default async function (core: ExtensionCore, error: any) { if (!error) return; @@ -20,7 +23,7 @@ export default async function (core: ExtensionCore, error: any) { ].join("\n"); if (error instanceof DiscloudAPIError) { - if (error.code > 499) { + if (error.code > _belowInternalServerError) { core.logger.error(`Server error ${error.code}`, metadata); return void window.showErrorMessage(`Server error ${error.code}`); } diff --git a/src/events/rateLimited.ts b/src/events/rateLimited.ts index fe1cba7a..99a24041 100644 --- a/src/events/rateLimited.ts +++ b/src/events/rateLimited.ts @@ -4,17 +4,18 @@ import type { RateLimitData } from "../@types"; import type ExtensionCore from "../core/extension"; const eventName = "rateLimited"; +const _sInMs = 1_000; export default async function (core: ExtensionCore, rateLimitData: RateLimitData) { if (core.timers.has(eventName) || isNaN(rateLimitData.reset) || isNaN(rateLimitData.time)) return; - const reset = rateLimitData.reset * 1000 + rateLimitData.time - Date.now(); + const resetTime = rateLimitData.reset * _sInMs + rateLimitData.time - Date.now(); - const time = Math.round(reset / 1000); + const time = Math.round(resetTime / _sInMs); - core.logger.warn("Rate limited by " + time + " seconds"); + core.logger.warn(`Rate limited by ${time} seconds`); - core.timers.setTimeout(eventName, () => core.statusBar.setRateLimited(false), reset); + core.timers.setTimeout(eventName, () => core.statusBar.setRateLimited(false), resetTime); core.statusBar.setRateLimited(true); diff --git a/src/services/discloud/REST.ts b/src/services/discloud/REST.ts index 6705b157..0dd9ee51 100644 --- a/src/services/discloud/REST.ts +++ b/src/services/discloud/REST.ts @@ -84,7 +84,7 @@ export default class REST extends EventEmitter { if (!this.authorized) return null; if (this.limited) { - this.core.emit("rateLimited", this.core, { reset: this.reset, time: this.#time }); + this.core.emit("rateLimited", this.core, { reset: this.#reset, time: this.#time }); return null; } @@ -139,6 +139,9 @@ export default class REST extends EventEmitter { case constants.HTTP_STATUS_UNAUTHORIZED: this.core.emit("unauthorized", this.core); break; + case constants.HTTP_STATUS_TOO_MANY_REQUESTS: + this.core.emit("rateLimited", this.core, { reset: this.#reset, time: this.#time }); + break; } throw new DiscloudAPIError(responseBody, response.status, config.method, pathname, config.body); From 9f12e0d400a0933441917e83a4efb7a2332d3462 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Mon, 3 Aug 2026 20:26:38 -0300 Subject: [PATCH 3/7] feat: refactor constructor argument handling and add rate limit control methods --- src/structures/DiscloudStatusBarItem.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/structures/DiscloudStatusBarItem.ts b/src/structures/DiscloudStatusBarItem.ts index c686d3ba..1f08a42d 100644 --- a/src/structures/DiscloudStatusBarItem.ts +++ b/src/structures/DiscloudStatusBarItem.ts @@ -44,7 +44,7 @@ function defaultOptions(): Partial { export default class DiscloudStatusBarItem extends BaseStatusBarItem { constructor(readonly core: ExtensionCore, data?: Partial) { - super(core.context, Object.assign({}, defaultOptions(), data)); + super(core.context, Object.assign(defaultOptions(), data)); if (workspace.workspaceFolders?.length) { this.show(); @@ -225,6 +225,14 @@ export default class DiscloudStatusBarItem extends BaseStatusBarItem { this.tooltip = t(tooltipLoginKey); } + disableRateLimited() { + this.setRateLimited(false); + } + + enableRateLimited() { + this.setRateLimited(true); + } + setRateLimited(limited?: boolean) { if (typeof limited === "boolean") { if (limited) { From 1b32d85ae3f2ae7a83b65b0f6e6bc86df52dfde4 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Tue, 4 Aug 2026 11:32:56 -0300 Subject: [PATCH 4/7] feat: refactor authentication provider and error handling logic --- src/authentication/pat/provider.ts | 38 ++++++++++++++++++------------ src/events/activate.ts | 25 ++++++++++---------- src/events/error.ts | 3 +-- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/authentication/pat/provider.ts b/src/authentication/pat/provider.ts index 9e39063b..778d28fe 100644 --- a/src/authentication/pat/provider.ts +++ b/src/authentication/pat/provider.ts @@ -71,34 +71,42 @@ export default class DiscloudPatAuthenticationProvider implements IPatAuthentica if (!response.ok) throw await BaseAuthenticationError.fromStatusCode(response.status); - const body = await response.json() as RESTGetApiUserResult; + const body = await response.json().catch(() => null) as RESTGetApiUserResult | null; const account: AuthenticationSessionAccountInformation = { - id: body.user.userID ?? defaultSessionAccount.id, + id: body?.user.userID ?? defaultSessionAccount.id, label: - body.user.username ?? body.user.userID ?? defaultSessionAccount.label, + body?.user.username ?? body?.user.userID ?? defaultSessionAccount.label, }; + const promises = []; + const newSessionId = `${providerId}.${account.id}`; const oldSessionId = this.storage.get(GlobalStorageKeys.currentSessionId); - if (oldSessionId) { - sessionIdSet.delete(oldSessionId); - await Promise.all([ - this.storage.update(oldSessionId, undefined), - this.secrets.delete(oldSessionId), - ]); - } - sessionIdSet.add(newSessionId); + if (oldSessionId !== newSessionId) { + sessionIdSet.add(newSessionId); - await Promise.all([ - this.storage.update(GlobalStorageKeys.sessionIdList, Array.from(sessionIdSet)), + if (oldSessionId) { + sessionIdSet.delete(oldSessionId); + promises.push( + this.storage.update(oldSessionId, undefined), + this.secrets.delete(oldSessionId), + ); + } + + promises.push( + this.storage.update(GlobalStorageKeys.sessionIdList, Array.from(sessionIdSet)), + this.storage.update(GlobalStorageKeys.currentSessionId, newSessionId), + ); + } + + await Promise.all(promises.concat( this.storage.update(GlobalStorageKeys.currentAutenticationProviderId, providerId), - this.storage.update(GlobalStorageKeys.currentSessionId, newSessionId), this.storage.update(newSessionId, account), this.secrets.store(newSessionId, input), - ]); + )); const newSession = new DiscloudAuthenticationSession(newSessionId, input, account); diff --git a/src/events/activate.ts b/src/events/activate.ts index c79d5148..6fcc6544 100644 --- a/src/events/activate.ts +++ b/src/events/activate.ts @@ -5,17 +5,23 @@ import type ExtensionCore from "../core/extension"; import BaseLanguageProvider from "../language/BaseLanguageProvider"; import { GlobalStorageKeys } from "../utils/constants"; +const _discloudAppSort = "discloud.app.sort"; +const _discloudTeamSort = "discloud.team.sort"; +const _discloudAppSeparateByType = "discloud.app.separate.by.type"; +const _discloudAppShowAvatarInsteadStatus = "discloud.app.show.avatar.instead.status"; +const _discloudStatusBarBehavior = "discloud.status.bar.behavior"; + export default async function (core: ExtensionCore, context: ExtensionContext) { queueMicrotask(() => BaseLanguageProvider.startProviders(context).catch(core.logger.error)); const disposableChangeConfiguration = workspace.onDidChangeConfiguration(event => { - if (event.affectsConfiguration("discloud.app.sort")) return core.userAppTree.refresh(); + if (event.affectsConfiguration(_discloudAppSort)) return core.userAppTree.refresh(); - if (event.affectsConfiguration("discloud.team.sort")) return core.teamAppTree.refresh(); + if (event.affectsConfiguration(_discloudTeamSort)) return core.teamAppTree.refresh(); - if (event.affectsConfiguration("discloud.app.separate.by.type")) return core.userAppTree.refresh(); + if (event.affectsConfiguration(_discloudAppSeparateByType)) return core.userAppTree.refresh(); - if (event.affectsConfiguration("discloud.app.show.avatar.instead.status")) { + if (event.affectsConfiguration(_discloudAppShowAvatarInsteadStatus)) { for (const app of core.userAppTree.children.values()) { app._patch({}); } @@ -23,7 +29,7 @@ export default async function (core: ExtensionCore, context: ExtensionContext) { return core.userAppTree.refresh(); } - if (event.affectsConfiguration("discloud.status.bar.behavior")) return core.statusBar.setDefault(); + if (event.affectsConfiguration(_discloudStatusBarBehavior)) return core.statusBar.setDefault(); }); // Refresh extension when session was removed @@ -83,14 +89,9 @@ async function migrateAuthenticationProvider(core: ExtensionCore) { oldSessionIdList[0] = newSessionId; - await Promise.all(promises); - - await Promise.all([ + await Promise.all(promises.concat( core.globalStorage.update(GlobalStorageKeys.sessionIdList, oldSessionIdList), - ]); - - await Promise.all([ core.globalStorage.delete("sessionIdList"), - ]); + )); } } diff --git a/src/events/error.ts b/src/events/error.ts index cbc52062..fe449df4 100644 --- a/src/events/error.ts +++ b/src/events/error.ts @@ -1,11 +1,10 @@ import { t } from "@vscode/l10n"; -import { constants } from "http2"; import { CancellationError, commands, version, window } from "vscode"; import type ExtensionCore from "../core/extension"; import WarningError from "../errors/warning"; import DiscloudAPIError from "../services/discloud/errors/api"; -const _belowInternalServerError = constants.HTTP_STATUS_INTERNAL_SERVER_ERROR - 1; +const _belowInternalServerError = 499; export default async function (core: ExtensionCore, error: any) { if (!error) return; From 8e43da996c04190463748a195e12b8accb7b64d0 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Tue, 4 Aug 2026 12:10:17 -0300 Subject: [PATCH 5/7] feat: refactor context management and implement DiscloudOutputChannel for improved output handling --- src/core/extension.ts | 17 ++----- src/output/LogOutputChannel.ts | 45 ++++++------------ src/output/OutputChannel.ts | 85 ++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 43 deletions(-) create mode 100644 src/output/OutputChannel.ts diff --git a/src/core/extension.ts b/src/core/extension.ts index 6642b669..166b1193 100644 --- a/src/core/extension.ts +++ b/src/core/extension.ts @@ -1,13 +1,14 @@ import { t } from "@vscode/l10n"; import { EventEmitter } from "events"; import { normalize } from "path"; -import { commands, type Disposable, type ExtensionContext, type LogOutputChannel, type OutputChannel, type SecretStorage, Uri, window, workspace } from "vscode"; +import { commands, type Disposable, type ExtensionContext, type LogOutputChannel, type SecretStorage, Uri, window, workspace } from "vscode"; import { type ExtensionContextId } from "../@enum"; import type { Events, GetWorkspaceFolderOptions, IGlobalStateStorage, TaskData } from "../@types"; import AuthenticationProviderContainer from "../authentication/providers"; import { commandsRegister } from "../commands"; import { loadEvents } from "../events"; import DiscloudLogOutputChannel from "../output/LogOutputChannel"; +import DiscloudOutputChannel from "../output/OutputChannel"; import CustomDomainTreeDataProvider from "../providers/CustomDomainTreeDataProvider"; import SubDomainTreeDataProvider from "../providers/SubDomainTreeDataProvider"; import TeamAppTreeDataProvider from "../providers/TeamAppTreeDataProvider"; @@ -52,7 +53,6 @@ export default class ExtensionCore extends EventEmitter implements Dispo declare readonly userAppTree: UserAppTreeDataProvider; declare readonly userTree: UserTreeDataProvider; - readonly outputChannels = new Map(); readonly timers = new TimerMap(); readonly user = new VSUser(); @@ -97,7 +97,6 @@ export default class ExtensionCore extends EventEmitter implements Dispo dispose() { this.removeAllListeners(); - this.outputChannels.clear(); this.timers.dispose(); } @@ -119,16 +118,8 @@ export default class ExtensionCore extends EventEmitter implements Dispo return DiscloudLogOutputChannel.getInstance(this.context, name); } - protected _createOutputChannel(key: string) { - const output = window.createOutputChannel(key); - this.context.subscriptions.push(output); - this.outputChannels.set(key, output); - return output; - } - - getOutputChannel(name: string, languageId?: string) { - const key = `${name}${languageId}`; - return this.outputChannels.get(key) ?? this._createOutputChannel(key); + getOutputChannel(name: string) { + return DiscloudOutputChannel.getInstance(this.context, name); } async getWorkspaceFolder(options?: GetWorkspaceFolderOptions | null): Promise { diff --git a/src/output/LogOutputChannel.ts b/src/output/LogOutputChannel.ts index 335ed570..8d488f3a 100644 --- a/src/output/LogOutputChannel.ts +++ b/src/output/LogOutputChannel.ts @@ -9,46 +9,31 @@ export default abstract class DiscloudLogOutputChannel implements LogOutputChann } static getInstance(context: ExtensionContext, name: string) { - const instance = DiscloudLogOutputChannel._instances.get(name); - - if (instance) { - instance._clearDisposeTimer(); + const instance = DiscloudLogOutputChannel._instances.getOrInsertComputed(name, () => { + const channel = window.createOutputChannel(name, { log: true }); + const instance = new _DiscloudLogOutputChannel(context, channel); + context.subscriptions.push(instance); return instance; - } - - return new _DiscloudLogOutputChannel(context, name); + }); + instance._clearDisposeTimer(); + return instance; } - constructor(readonly context: ExtensionContext, name: string) { - const instance = DiscloudLogOutputChannel._instances.get(name); - - if (instance) { - instance._clearDisposeTimer(); - return instance; - } + constructor( + readonly context: ExtensionContext, + protected readonly _channel: LogOutputChannel, + ) { } - DiscloudLogOutputChannel._instances.set(name, this); - this._channel = window.createOutputChannel(name, { log: true }); - context.subscriptions.push(this); - } - - declare protected readonly _channel: LogOutputChannel; declare protected _disposeTimer: NodeJS.Timeout; /** @readonly */ - get logLevel(): LogLevel { - return this._channel.logLevel; - } + get logLevel(): LogLevel { return this._channel.logLevel; } /** @readonly */ - get name() { - return this._channel.name; - } + get name() { return this._channel.name; } /** @readonly */ - get onDidChangeLogLevel(): Event { - return this._channel.onDidChangeLogLevel; - } + get onDidChangeLogLevel(): Event { return this._channel.onDidChangeLogLevel; } trace(message: string, ...args: any[]) { this._clearDisposeTimer(); @@ -111,7 +96,7 @@ export default abstract class DiscloudLogOutputChannel implements LogOutputChann this._clearDisposeTimer(); if (typeof delay === "number") { - this._disposeTimer = setTimeout(() => this._dispose(), delay).unref(); + this._disposeTimer = setTimeout(this._dispose.bind(this), delay).unref(); } else { this._dispose(); } diff --git a/src/output/OutputChannel.ts b/src/output/OutputChannel.ts new file mode 100644 index 00000000..6deedb9f --- /dev/null +++ b/src/output/OutputChannel.ts @@ -0,0 +1,85 @@ + +import { type ExtensionContext, type OutputChannel, window } from "vscode"; + +export default abstract class DiscloudOutputChannel implements OutputChannel { + protected static readonly _instances = new Map(); + + static disposeNamed(name: string, delay?: number) { + DiscloudOutputChannel._instances.get(name)?.dispose(delay); + } + + static getInstance(context: ExtensionContext, name: string) { + const instance = DiscloudOutputChannel._instances.getOrInsertComputed(name, () => { + const channel = window.createOutputChannel(name); + const instance = new _DiscloudOutputChannel(context, channel); + context.subscriptions.push(instance); + return instance; + }); + instance._clearDisposeTimer(); + return instance; + } + + constructor( + readonly context: ExtensionContext, + protected readonly _channel: OutputChannel, + ) { } + + declare protected _disposeTimer: NodeJS.Timeout; + + /** @readonly */ + get name() { return this._channel.name; } + + append(value: string) { + this._clearDisposeTimer(); + this._channel.append(value); + } + + appendLine(value: string) { + this._clearDisposeTimer(); + this._channel.appendLine(value); + } + + replace(value: string) { + this._clearDisposeTimer(); + this._channel.replace(value); + } + + clear() { + this._clearDisposeTimer(); + this._channel.clear(); + } + + show(preserveFocus?: boolean): void + show(): void + show(preserveFocus?: boolean) { + this._clearDisposeTimer(); + this._channel.show(preserveFocus); + } + + hide() { + this._clearDisposeTimer(); + this._channel.hide(); + } + + dispose(delay?: number) { + this._clearDisposeTimer(); + + if (typeof delay === "number") { + this._disposeTimer = setTimeout(this._dispose.bind(this), delay).unref(); + } else { + this._dispose(); + } + } + + protected _clearDisposeTimer() { + clearTimeout(this._disposeTimer); + } + + protected _dispose() { + this._clearDisposeTimer(); + DiscloudOutputChannel._instances.delete(this.name); + this._channel.dispose(); + } +} + +class _DiscloudOutputChannel extends DiscloudOutputChannel { } From 37508ea894907dcdc8ab53d227a1db143cb3cff2 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Tue, 4 Aug 2026 12:15:49 -0300 Subject: [PATCH 6/7] feat: Refactor view management and optimize application type insertion. --- src/providers/UserAppTreeDataProvider.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/providers/UserAppTreeDataProvider.ts b/src/providers/UserAppTreeDataProvider.ts index 9e857588..b6043284 100644 --- a/src/providers/UserAppTreeDataProvider.ts +++ b/src/providers/UserAppTreeDataProvider.ts @@ -28,14 +28,6 @@ export default class UserAppTreeDataProvider extends BaseTreeDataProvider protected readonly _views = new DisposableMap(); - protected _getView(type: AppType) { - let view = this._views.get(type); - if (view) return view; - view = new AppTypeTreeItemView(type); - this._views.set(type, view); - return view; - } - protected _sort(children: Item[]) { const sort = this.core.config.get(ConfigKeys.appSortBy); @@ -197,7 +189,8 @@ export default class UserAppTreeDataProvider extends BaseTreeDataProvider const child = new UserAppTreeItem(data); - this._getView(child.type).set(child.appId, child); + this._views.getOrInsertComputed(child.type, () => new AppTypeTreeItemView(child.type)) + .set(child.appId, child); this.children.set(child.appId, child); From b82fb35bab6ae08c9458a50326bd1b80b5c1ca36 Mon Sep 17 00:00:00 2001 From: Gorniaky Date: Tue, 4 Aug 2026 12:17:20 -0300 Subject: [PATCH 7/7] bump deps --- package.json | 8 +-- yarn.lock | 134 +++++++++++++++++++++++++-------------------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/package.json b/package.json index 467802f5..9c7ddcec 100644 --- a/package.json +++ b/package.json @@ -1325,7 +1325,7 @@ "bytes": "^3.1.2", "jose": "^6.2.8", "json-schema-library": "^11.6.2", - "ws": "^8.21.1", + "ws": "^8.21.2", "yocto-queue": "^1.2.2" }, "devDependencies": { @@ -1335,8 +1335,8 @@ "@types/node": "24", "@types/vscode": "^1.125.0", "@types/ws": "^8.18.1", - "@typescript-eslint/eslint-plugin": "^8.65.0", - "@typescript-eslint/parser": "^8.65.0", + "@typescript-eslint/eslint-plugin": "^8.66.0", + "@typescript-eslint/parser": "^8.66.0", "@vscode/test-cli": "^0.0.15", "@vscode/test-electron": "^3.1.0", "esbuild": "^0.28.1", @@ -1346,4 +1346,4 @@ "npm-run-all": "^4.1.5", "typescript": "^6.0.3" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index cdab1f40..3e071208 100644 --- a/yarn.lock +++ b/yarn.lock @@ -374,100 +374,100 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz#0a58df6fea8c0bf6b396f518077099bc8b762bb5" - integrity sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA== +"@typescript-eslint/eslint-plugin@^8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz#76e86aa5a2459fbf5bbd7a839c0dc0cce1d56224" + integrity sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A== dependencies: "@eslint-community/regexpp" "^4.12.2" - "@typescript-eslint/scope-manager" "8.65.0" - "@typescript-eslint/type-utils" "8.65.0" - "@typescript-eslint/utils" "8.65.0" - "@typescript-eslint/visitor-keys" "8.65.0" + "@typescript-eslint/scope-manager" "8.66.0" + "@typescript-eslint/type-utils" "8.66.0" + "@typescript-eslint/utils" "8.66.0" + "@typescript-eslint/visitor-keys" "8.66.0" ignore "^7.0.5" natural-compare "^1.4.0" ts-api-utils "^2.5.0" -"@typescript-eslint/parser@^8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.65.0.tgz#5295c1058c0a1dd746ef28baaf9c0341dbdf03dc" - integrity sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA== +"@typescript-eslint/parser@^8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.66.0.tgz#88e3865ecf73b0118134e7cb831da87a961a57a1" + integrity sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g== dependencies: - "@typescript-eslint/scope-manager" "8.65.0" - "@typescript-eslint/types" "8.65.0" - "@typescript-eslint/typescript-estree" "8.65.0" - "@typescript-eslint/visitor-keys" "8.65.0" + "@typescript-eslint/scope-manager" "8.66.0" + "@typescript-eslint/types" "8.66.0" + "@typescript-eslint/typescript-estree" "8.66.0" + "@typescript-eslint/visitor-keys" "8.66.0" debug "^4.4.3" -"@typescript-eslint/project-service@8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.65.0.tgz#65fbbc9a1591abffaeab5513200f848271cb0aa5" - integrity sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q== +"@typescript-eslint/project-service@8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.66.0.tgz#828f788895df52d9eb2b543445a3a5a13e35ab4e" + integrity sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g== dependencies: - "@typescript-eslint/tsconfig-utils" "^8.65.0" - "@typescript-eslint/types" "^8.65.0" + "@typescript-eslint/tsconfig-utils" "^8.66.0" + "@typescript-eslint/types" "^8.66.0" debug "^4.4.3" -"@typescript-eslint/scope-manager@8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz#9547202ce7e608e7b6283df585703b980a0ea70d" - integrity sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg== +"@typescript-eslint/scope-manager@8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz#4fffcc6ebd0df9fe7983c0256967567ea6f5ac63" + integrity sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g== dependencies: - "@typescript-eslint/types" "8.65.0" - "@typescript-eslint/visitor-keys" "8.65.0" + "@typescript-eslint/types" "8.66.0" + "@typescript-eslint/visitor-keys" "8.66.0" -"@typescript-eslint/tsconfig-utils@8.65.0", "@typescript-eslint/tsconfig-utils@^8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz#36f168fcdbb1295f7446ff0379667f98c3cf1bf3" - integrity sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg== +"@typescript-eslint/tsconfig-utils@8.66.0", "@typescript-eslint/tsconfig-utils@^8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz#3a89066c507aa30541dc176804685b4b444e1e52" + integrity sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g== -"@typescript-eslint/type-utils@8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz#d316d7522d93cff4cd14f305e02f3df2d804f9c1" - integrity sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g== +"@typescript-eslint/type-utils@8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz#b2315303eca72fad9afa7be4f58f053c8f2a0479" + integrity sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g== dependencies: - "@typescript-eslint/types" "8.65.0" - "@typescript-eslint/typescript-estree" "8.65.0" - "@typescript-eslint/utils" "8.65.0" + "@typescript-eslint/types" "8.66.0" + "@typescript-eslint/typescript-estree" "8.66.0" + "@typescript-eslint/utils" "8.66.0" debug "^4.4.3" ts-api-utils "^2.5.0" -"@typescript-eslint/types@8.65.0", "@typescript-eslint/types@^8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.65.0.tgz#3e86738416a777c8b8925ab46745f48ecf904c9f" - integrity sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg== +"@typescript-eslint/types@8.66.0", "@typescript-eslint/types@^8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.66.0.tgz#3cacab94d3b564c1d48c56eb37b89f89a6d48479" + integrity sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ== -"@typescript-eslint/typescript-estree@8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz#f1f514808f6aa713e2d678ae8ff592a65e1632af" - integrity sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg== +"@typescript-eslint/typescript-estree@8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz#1a38c3a97dc6c669b66d585d7f90ebc4fbb32a50" + integrity sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg== dependencies: - "@typescript-eslint/project-service" "8.65.0" - "@typescript-eslint/tsconfig-utils" "8.65.0" - "@typescript-eslint/types" "8.65.0" - "@typescript-eslint/visitor-keys" "8.65.0" + "@typescript-eslint/project-service" "8.66.0" + "@typescript-eslint/tsconfig-utils" "8.66.0" + "@typescript-eslint/types" "8.66.0" + "@typescript-eslint/visitor-keys" "8.66.0" debug "^4.4.3" minimatch "^10.2.2" semver "^7.7.3" tinyglobby "^0.2.15" ts-api-utils "^2.5.0" -"@typescript-eslint/utils@8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.65.0.tgz#afedd974a0c8deeef553b509df5800bafd615a72" - integrity sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA== +"@typescript-eslint/utils@8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.66.0.tgz#e277d67427043cdca2580ee91aa62921e4689969" + integrity sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA== dependencies: "@eslint-community/eslint-utils" "^4.9.1" - "@typescript-eslint/scope-manager" "8.65.0" - "@typescript-eslint/types" "8.65.0" - "@typescript-eslint/typescript-estree" "8.65.0" + "@typescript-eslint/scope-manager" "8.66.0" + "@typescript-eslint/types" "8.66.0" + "@typescript-eslint/typescript-estree" "8.66.0" -"@typescript-eslint/visitor-keys@8.65.0": - version "8.65.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz#e3704c13cb4a1c22454c1abf28ff4737e15018c6" - integrity sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A== +"@typescript-eslint/visitor-keys@8.66.0": + version "8.66.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz#4c494e94745fb2724a4f37a310091e56b644d18a" + integrity sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg== dependencies: - "@typescript-eslint/types" "8.65.0" + "@typescript-eslint/types" "8.66.0" eslint-visitor-keys "^5.0.0" "@vscode/l10n@^0.0.18": @@ -3047,10 +3047,10 @@ wrap-ansi@^9.0.0: string-width "^7.0.0" strip-ansi "^7.1.0" -ws@^8.21.1: - version "8.21.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.1.tgz#045650cd4b1207809e7547146223c3814a9af586" - integrity sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw== +ws@^8.21.2: + version "8.21.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.2.tgz#e6976cfe96161d40420689a5f051378d11b0f0ce" + integrity sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw== y18n@^5.0.5: version "5.0.8"