extensions.ts ×23

Frontier kind: Code frontier

unlabeled · c_ab99339b32ea

1165 tests · 12225 LOC · 52 files · introduces 0 tests · 476 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
23 ranges476 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1382 ranges12225 lines · 52 files · Browse complete extent
All tests (intent)
1165 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 476 introduced LOC across 23 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/workbench/services/extensions/common/extensions.ts 476 introduced LOC · 23 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- extensions.ts
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { Event } from '../../../../base/common/event.js';
7 > import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
8 > import { raceTimeout } from '../../../../base/common/async.js';
9 > import Severity from '../../../../base/common/severity.js';
10 > import { URI } from '../../../../base/common/uri.js';
11 > import { IMessagePassingProtocol } from '../../../../base/parts/ipc/common/ipc.js';
12 > import { IAssignmentService } from '../../../../platform/assignment/common/assignment.js';
13 > import { getExtensionId, getGalleryExtensionId } from '../../../../platform/extensionManagement/common/extensionManagementUtil.js';
14 > import { ImplicitActivationEvents } from '../../../../platform/extensionManagement/common/implicitActivationEvents.js';
15 > import { ExtensionIdentifier, ExtensionIdentifierMap, ExtensionIdentifierSet, ExtensionType, IExtension, IExtensionContributions, IExtensionDescription, TargetPlatform } from '../../../../platform/extensions/common/extensions.js';
16 > import { ApiProposalName } from '../../../../platform/extensions/common/extensionsApiProposals.js';
17 > import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
18 > import { IV8Profile } from '../../../../platform/profiling/common/profiling.js';
19 > import { ExtensionHostKind } from './extensionHostKind.js';
20 > import { IExtensionDescriptionDelta, IExtensionDescriptionSnapshot } from './extensionHostProtocol.js';
21 > import { ExtensionRunningLocation } from './extensionRunningLocation.js';
22 > import { IExtensionPoint } from './extensionsRegistry.js';
23 >
24 > export const nullExtensionDescription = Object.freeze<IExtensionDescription>({
25 > identifier: new ExtensionIdentifier('nullExtensionDescription'),
26 > name: 'Null Extension Description',
27 > version: '0.0.0',
28 > publisher: 'vscode',
29 > engines: { vscode: '' },
30 > extensionLocation: URI.parse('void:location'),
31 > isBuiltin: false,
32 > targetPlatform: TargetPlatform.UNDEFINED,
33 > isUserBuiltin: false,
34 > isUnderDevelopment: false,
35 > preRelease: false,
36 > });
37 >
38 > export type WebWorkerExtHostConfigValue = boolean | 'auto';
39 > export const webWorkerExtHostConfig = 'extensions.webWorker';
40 >
41 > export const IExtensionService = createDecorator<IExtensionService>('extensionService');
42 >
43 > export interface IMessage {
44 > type: Severity;
45 > message: string;
46 > extensionId: ExtensionIdentifier;
47 > extensionPointId: string;
48 > }
49 >
50 > export interface IExtensionsStatus {
51 > id: ExtensionIdentifier;
52 > messages: IMessage[];
53 > activationStarted: boolean;
54 > activationTimes: ActivationTimes | undefined;
55 > runtimeErrors: Error[];
56 > runningLocation: ExtensionRunningLocation | null;
57 > }
58 >
59 > export class MissingExtensionDependency {
60 > constructor(readonly dependency: string) { }
61 > }
62 >
63 > /**
64 > * e.g.
65 > * ```
66 > * {
67 > * startTime: 1511954813493000,
68 > * endTime: 1511954835590000,
69 > * deltas: [ 100, 1500, 123456, 1500, 100000 ],
70 > * ids: [ 'idle', 'self', 'extension1', 'self', 'idle' ]
71 > * }
72 > * ```
73 > */
74 > export interface IExtensionHostProfile {
75 > /**
76 > * Profiling start timestamp in microseconds.
77 > */
78 > startTime: number;
79 > /**
80 > * Profiling end timestamp in microseconds.
81 > */
82 > endTime: number;
83 > /**
84 > * Duration of segment in microseconds.
85 > */
86 > deltas: number[];
87 > /**
88 > * Segment identifier: extension id or one of the four known strings.
89 > */
90 > ids: ProfileSegmentId[];
91 >
92 > /**
93 > * Get the information as a .cpuprofile.
94 > */
95 > data: IV8Profile;
96 >
97 > /**
98 > * Get the aggregated time per segmentId
99 > */
100 > getAggregatedTimes(): Map<ProfileSegmentId, number>;
101 > }
102 >
103 > export const enum ExtensionHostStartup {
104 > /**
105 > * The extension host should be launched immediately and doesn't require a `$startExtensionHost` call.
106 > */
107 > EagerAutoStart = 1,
108 > /**
109 > * The extension host should be launched immediately and needs a `$startExtensionHost` call.
110 > */
111 > EagerManualStart = 2,
112 > /**
113 > * The extension host should be launched lazily and only when it has extensions it needs to host. It doesn't require a `$startExtensionHost` call.
114 > */
115 > LazyAutoStart = 3,
116 > }
117 >
118 > export interface IExtensionInspectInfo {
119 > readonly port: number;
120 > readonly host: string;
121 > readonly devtoolsUrl?: string;
122 > readonly devtoolsLabel?: string;
123 > }
124 >
125 > export interface IExtensionHost {
126 > readonly pid: number | null;
127 > readonly runningLocation: ExtensionRunningLocation;
128 > readonly remoteAuthority: string | null;
129 > readonly startup: ExtensionHostStartup;
130 > /**
131 > * A collection of extensions which includes information about which
132 > * extension will execute or is executing on this extension host.
133 > * **NOTE**: this will reflect extensions correctly only after `start()` resolves.
134 > */
135 > readonly extensions: ExtensionHostExtensions | null;
136 > readonly onExit: Event<[number, string | null]>;
137 >
138 > start(): Promise<IMessagePassingProtocol>;
139 > getInspectPort(): IExtensionInspectInfo | undefined;
140 > enableInspectPort(): Promise<boolean>;
141 > disconnect?(): Promise<void>;
142 > dispose(): void;
143 > }
144 >
145 > export class ExtensionHostExtensions {
146 > private _versionId: number;
147 > private _allExtensions: IExtensionDescription[];
148 > private _myExtensions: ExtensionIdentifier[];
149 > private _myActivationEvents: Set<string> | null;
150 >
151 > public get versionId(): number {
152 return this._versionId;
153 }
155 > public get allExtensions(): IExtensionDescription[] {
156 return this._allExtensions;
157 }
159 > public get myExtensions(): ExtensionIdentifier[] {
160 return this._myExtensions;
161 }
163 > constructor(versionId: number, allExtensions: readonly IExtensionDescription[], myExtensions: ExtensionIdentifier[]) {
164 this._versionId = versionId;
165 this._allExtensions = allExtensions.slice(0);
167 this._myActivationEvents = null;
168 }
170 > toSnapshot(): IExtensionDescriptionSnapshot {
171 return {
172 versionId: this._versionId,
176 };
177 }
179 > public set(versionId: number, allExtensions: IExtensionDescription[], myExtensions: ExtensionIdentifier[]): IExtensionDescriptionDelta {
180 if (this._versionId > versionId) {
181 throw new Error(`ExtensionHostExtensions: invalid versionId ${versionId} (current: ${this._versionId})`);
245 return delta;
246 }
248 > public delta(extensionsDelta: IExtensionDescriptionDelta): IExtensionDescriptionDelta | null {
249 if (this._versionId >= extensionsDelta.versionId) {
250 // ignore older deltas
281 return extensionsDelta;
282 }
284 > public containsExtension(extensionId: ExtensionIdentifier): boolean {
285 for (const myExtensionId of this._myExtensions) {
286 if (ExtensionIdentifier.equals(myExtensionId, extensionId)) {
290 return false;
291 }
293 > public containsActivationEvent(activationEvent: string): boolean {
294 if (!this._myActivationEvents) {
295 this._myActivationEvents = this._readMyActivationEvents();
297 return this._myActivationEvents.has(activationEvent);
298 }
300 > private _readMyActivationEvents(): Set<string> {
301 const result = new Set<string>();
302
314 return result;
315 }
316 > } extensions.ts
317 >
318 function extensionDescriptionArrayToMap(extensions: IExtensionDescription[]): ExtensionIdentifierMap<IExtensionDescription> {
319 const result = new ExtensionIdentifierMap<IExtensionDescription>();
323 return result;
324 }
326 > export function isProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): boolean {
327 if (!extension.enabledApiProposals) {
328 return false;
339 return enabled;
340 }
342 > export interface IProposedApiUsage {
343 > /**
344 > * The identifier of the extension that attempted to use the proposal.
345 > */
346 > readonly extensionId: string;
347 > /**
348 > * The name of the API proposal that the extension is not allowed to use.
349 > */
350 > readonly proposalName: ApiProposalName;
351 > }
352 >
353 > type ProposedApiUsageReporter = (usage: IProposedApiUsage) => void;
354 >
355 > let _proposedApiUsageReporter: ProposedApiUsageReporter | undefined;
356 > const _reportedProposedApiUsages = new Set<string>();
357 >
358 > /**
359 > * Registers a reporter that is invoked whenever an extension attempts to use a proposed API
360 > * that it has not declared via its `enabledApiProposals`-property. This is used to gather
361 > * telemetry about extensions that rely on proposed API they are not entitled to use.
362 > *
363 > * Each unique extension/proposal combination is reported at most once per session in order to
364 > * avoid flooding telemetry from the (potentially hot) call sites of {@link isProposedApiEnabled}.
365 > */
366 > export function setProposedApiUsageReporter(reporter: ProposedApiUsageReporter): IDisposable {
367 _proposedApiUsageReporter = reporter;
368 return toDisposable(() => {
372 });
373 }
375 function reportDisabledProposedApiUsage(extension: IExtensionDescription, proposal: ApiProposalName): void {
376 const reporter = _proposedApiUsageReporter;
385 reporter({ extensionId: extension.identifier.value, proposalName: proposal });
386 }
388 > type ProposedApiEnabledResolver = (extension: IExtensionDescription, proposal: ApiProposalName) => boolean;
389 >
390 > let _proposedApiEnabledResolver: ProposedApiEnabledResolver | undefined;
391 >
392 > /**
393 > * The name of the experiment ("treatment") that can grant proposed API access to
394 > * extension/proposal combinations that have not declared the proposal themselves.
395 > */
396 > export const enabledApiProposalsFallbackExperimentName = 'extensionEnabledApiProposalsFallback';
397 >
398 > /**
399 > * Experiment value that explicitly blocks all proposals reaching the fallback.
400 > */
401 > export const enabledApiProposalsFallbackNone = 'none';
402 >
403 > /**
404 > * Resolves the value of the {@link enabledApiProposalsFallbackExperimentName}-experiment, or
405 > * `undefined` when it does not apply (non-`stable` quality) or cannot be read in time.
406 > */
407 export async function resolveEnabledApiProposalsFallbackExperiment(assignmentService: IAssignmentService, quality: string | undefined): Promise<string | undefined> {
408 if (quality !== 'stable') {
419 }
420 }
422 > /**
423 > * Enables the {@link enabledApiProposalsFallbackExperimentName}-experiment which can grant proposed
424 > * API access to extension/proposal combinations that have not declared the proposal via their
425 > * `enabledApiProposals`-property. It only takes effect on `stable` builds.
426 > *
427 > * Note that the experiment only applies to extensions that already declare at least one proposal:
428 > * an extension with no `enabledApiProposals` at all is never granted access.
429 > *
430 > * @param value A comma-separated list of `publisher.extension:proposalName` entries. Any combination
431 > * that appears here will have {@link isProposedApiEnabled} return `true` even when the extension has
432 > * not declared that particular proposal. When unset, all proposals are allowed;
433 > * {@link enabledApiProposalsFallbackNone} blocks all proposals that reach the fallback.
434 > * @param quality The product quality. The experiment only takes effect when this is `stable`.
435 > */
436 > export function setEnabledApiProposalsFallbackExperiment(value: string | undefined, quality: string | undefined): IDisposable {
437 if (quality !== 'stable') {
438 return Disposable.None;
463 });
464 }
466 > export function checkProposedApiEnabled(extension: IExtensionDescription, proposal: ApiProposalName): void {
467 if (!isProposedApiEnabled(extension, proposal)) {
468 throw new Error(`Extension '${extension.identifier.value}' CANNOT use API proposal: ${proposal}.\nIts package.json#enabledApiProposals-property declares: ${extension.enabledApiProposals?.join(', ') ?? '[]'} but NOT ${proposal}.\n The missing proposal MUST be added and you must start in extension development mode or use the following command line switch: --enable-proposed-api ${extension.identifier.value}`);
469 }
470 }
472 >
473 > /**
474 > * Extension id or one of the four known program states.
475 > */
476 > export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self';
477 >
478 > export interface ExtensionActivationReason {
479 > readonly startup: boolean;
480 > readonly extensionId: ExtensionIdentifier;
481 > readonly activationEvent: string;
482 > }
483 >
484 > export class ActivationTimes {
485 > constructor(
486 public readonly codeLoadingTime: number,
487 public readonly activateCallTime: number,
490 ) {
491 }
492 > } extensions.ts
493 >
494 > export class ExtensionPointContribution<T> {
495 > readonly description: IExtensionDescription;
496 > readonly value: T;
497 >
498 > constructor(description: IExtensionDescription, value: T) {
499 this.description = description;
500 this.value = value;
501 }
502 > } extensions.ts
503 >
504 > export interface IWillActivateEvent {
505 > readonly event: string;
506 > readonly activation: Promise<void>;
507 > readonly activationKind: ActivationKind;
508 > }
509 >
510 > export interface IResponsiveStateChangeEvent {
511 > extensionHostKind: ExtensionHostKind;
512 > isResponsive: boolean;
513 > /**
514 > * Return the inspect port or `0`. `0` means inspection is not possible.
515 > */
516 > getInspectListener(tryEnableInspector: boolean): Promise<IExtensionInspectInfo | undefined>;
517 > }
518 >
519 > export const enum ActivationKind {
520 > Normal = 0,
521 > Immediate = 1
522 > }
523 >
524 > export interface WillStopExtensionHostsEvent {
525 >
526 > /**
527 > * A human readable reason for stopping the extension hosts
528 > * that e.g. can be shown in a confirmation dialog to the
529 > * user.
530 > */
531 > readonly reason: string;
532 >
533 > /**
534 > * A flag to indicate if the operation was triggered automatically
535 > */
536 > readonly auto: boolean;
537 >
538 > /**
539 > * Allows to veto the stopping of extension hosts. The veto can be a long running
540 > * operation.
541 > *
542 > * @param reason a human readable reason for vetoing the extension host stop in case
543 > * where the resolved `value: true`.
544 > */
545 > veto(value: boolean | Promise<boolean>, reason: string): void;
546 > }
547 >
548 > export interface IExtensionService {
549 > readonly _serviceBrand: undefined;
550 >
551 > /**
552 > * An event emitted when extensions are registered after their extension points got handled.
553 > *
554 > * This event will also fire on startup to signal the installed extensions.
555 > *
556 > * @returns the extensions that got registered
557 > */
558 > readonly onDidRegisterExtensions: Event<void>;
559 >
560 > /**
561 > * @event
562 > * Fired when extensions status changes.
563 > * The event contains the ids of the extensions that have changed.
564 > */
565 > readonly onDidChangeExtensionsStatus: Event<ExtensionIdentifier[]>;
566 >
567 > /**
568 > * Fired when the available extensions change (i.e. when extensions are added or removed).
569 > */
570 > readonly onDidChangeExtensions: Event<{ readonly added: readonly IExtensionDescription[]; readonly removed: readonly IExtensionDescription[] }>;
571 >
572 > /**
573 > * All registered extensions.
574 > * - List will be empty initially during workbench startup and will be filled with extensions as they are registered
575 > * - Listen to `onDidChangeExtensions` event for any changes to the extensions list. It will change as extensions get registered or de-reigstered.
576 > * - Listen to `onDidRegisterExtensions` event or wait for `whenInstalledExtensionsRegistered` promise to get the initial list of registered extensions.
577 > */
578 > readonly extensions: readonly IExtensionDescription[];
579 >
580 > /**
581 > * An event that is fired when activation happens.
582 > */
583 > readonly onWillActivateByEvent: Event<IWillActivateEvent>;
584 >
585 > /**
586 > * An event that is fired when an extension host changes its
587 > * responsive-state.
588 > */
589 > readonly onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent>;
590 >
591 > /**
592 > * Fired before stop of extension hosts happens. Allows listeners to veto against the
593 > * stop to prevent it from happening.
594 > */
595 > readonly onWillStop: Event<WillStopExtensionHostsEvent>;
596 >
597 > /**
598 > * Send an activation event and activate interested extensions.
599 > *
600 > * This will wait for the normal startup of the extension host(s).
601 > *
602 > * In extraordinary circumstances, if the activation event needs to activate
603 > * one or more extensions before the normal startup is finished, then you can use
604 > * `ActivationKind.Immediate`. Please do not use this flag unless really necessary
605 > * and you understand all consequences.
606 > */
607 > activateByEvent(activationEvent: string, activationKind?: ActivationKind): Promise<void>;
608 >
609 > /**
610 > * Send an activation ID and activate interested extensions.
611 > *
612 > */
613 > activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
614 >
615 > /**
616 > * Determine if `activateByEvent(activationEvent)` has resolved already.
617 > *
618 > * i.e. the activation event is finished and all interested extensions are already active.
619 > */
620 > activationEventIsDone(activationEvent: string): boolean;
621 >
622 > /**
623 > * An promise that resolves when the installed extensions are registered after
624 > * their extension points got handled.
625 > */
626 > whenInstalledExtensionsRegistered(): Promise<boolean>;
627 >
628 > /**
629 > * Return a specific extension
630 > * @param id An extension id
631 > */
632 > getExtension(id: string): Promise<IExtensionDescription | undefined>;
633 >
634 > /**
635 > * Returns `true` if the given extension can be added. Otherwise `false`.
636 > * @param extension An extension
637 > */
638 > canAddExtension(extension: IExtensionDescription): boolean;
639 >
640 > /**
641 > * Returns `true` if the given extension can be removed. Otherwise `false`.
642 > * @param extension An extension
643 > */
644 > canRemoveExtension(extension: IExtensionDescription): boolean;
645 >
646 > /**
647 > * Read all contributions to an extension point.
648 > */
649 > readExtensionPointContributions<T extends IExtensionContributions[keyof IExtensionContributions]>(extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]>;
650 >
651 > /**
652 > * Get information about extensions status.
653 > */
654 > getExtensionsStatus(): { [id: string]: IExtensionsStatus };
655 >
656 > /**
657 > * Return the inspect ports (if inspection is possible) for extension hosts of kind `extensionHostKind`.
658 > */
659 > getInspectPorts(extensionHostKind: ExtensionHostKind, tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]>;
660 >
661 > /**
662 > * Stops the extension hosts.
663 > *
664 > * @param reason a human readable reason for stopping the extension hosts. This maybe
665 > * can be presented to the user when showing dialogs.
666 > *
667 > * @param auto indicates if the operation was triggered by an automatic action
668 > *
669 > * @returns a promise that resolves to `true` if the extension hosts were stopped, `false`
670 > * if the operation was vetoed by listeners of the `onWillStop` event.
671 > */
672 > stopExtensionHosts(reason: string, auto?: boolean): Promise<boolean>;
673 >
674 > /**
675 > * Starts the extension hosts. If updates are provided, the extension hosts are started with the given updates.
676 > */
677 > startExtensionHosts(updates?: { readonly toAdd: readonly IExtension[]; readonly toRemove: readonly string[] }): Promise<void>;
678 >
679 > /**
680 > * Modify the environment of the remote extension host
681 > * @param env New properties for the remote extension host
682 > */
683 > setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void>;
684 > }
685 >
686 > export interface IInternalExtensionService {
687 > _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void>;
688 > _onWillActivateExtension(extensionId: ExtensionIdentifier): void;
689 > _onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void;
690 > _onDidActivateExtensionError(extensionId: ExtensionIdentifier, error: Error): void;
691 > _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
692 > }
693 >
694 > export interface ProfileSession {
695 > stop(): Promise<IExtensionHostProfile>;
696 > }
697 >
698 > export function toExtension(extensionDescription: IExtensionDescription): IExtension {
699 return {
700 type: extensionDescription.isBuiltin ? ExtensionType.System : ExtensionType.User,
710 };
711 }
713 > export function toExtensionDescription(extension: IExtension, isUnderDevelopment?: boolean): IExtensionDescription {
714 const id = getExtensionId(extension.manifest.publisher, extension.manifest.name);
715 return {
727 };
728 }
730 >
731 > export class NullExtensionService implements IExtensionService {
732 declare readonly _serviceBrand: undefined;
733 readonly onDidRegisterExtensions: Event<void> = Event.None;
738 readonly onWillStop: Event<WillStopExtensionHostsEvent> = Event.None;
739 readonly extensions = [];
740 > activateByEvent(_activationEvent: string): Promise<void> { return Promise.resolve(undefined); } extensions.ts
741 > activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> { return Promise.resolve(undefined); }
742 > activationEventIsDone(_activationEvent: string): boolean { return false; }
743 > whenInstalledExtensionsRegistered(): Promise<boolean> { return Promise.resolve(true); }
744 > getExtension() { return Promise.resolve(undefined); }
745 > readExtensionPointContributions<T>(_extPoint: IExtensionPoint<T>): Promise<ExtensionPointContribution<T>[]> { return Promise.resolve(Object.create(null)); }
746 > getExtensionsStatus(): { [id: string]: IExtensionsStatus } { return Object.create(null); }
747 > getInspectPorts(_extensionHostKind: ExtensionHostKind, _tryEnableInspector: boolean): Promise<IExtensionInspectInfo[]> { return Promise.resolve([]); }
748 > async stopExtensionHosts(): Promise<boolean> { return true; }
749 > async startExtensionHosts(): Promise<void> { }
750 > async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise<void> { }
751 > canAddExtension(): boolean { return false; }
752 > canRemoveExtension(): boolean { return false; }
753 > }