fix: 修复扩展管理对话框测试和代码质量问题

- 为 ActionSelectionStep.test.tsx 和 ExtensionListStep.test.tsx 添加 KeypressProvider 包裹
- 修复 KeypressContext 导入路径
- 更新测试快照
- 重构 ExtensionsManagerDialog:提取 enable/disable 公共逻辑到 handleToggleExtensionState
- 添加错误消息状态和用户友好的错误提示
This commit is contained in:
LaZzyMan 2026-02-28 16:38:10 +08:00
parent 4d27950a95
commit 0072c47996
5 changed files with 160 additions and 394 deletions

View file

@ -7,6 +7,7 @@
import { render } from 'ink-testing-library';
import { describe, it, expect, vi } from 'vitest';
import { ActionSelectionStep } from './ActionSelectionStep.js';
import { KeypressProvider } from '../../../contexts/KeypressContext.js';
import type { Extension } from '@qwen-code/qwen-code-core';
const createMockExtension = (name: string, isActive = true): Extension =>
@ -38,11 +39,13 @@ describe('ActionSelectionStep Snapshots', () => {
it('should render for active extension without update', () => {
const { lastFrame } = render(
<ActionSelectionStep
selectedExtension={createMockExtension('active-ext', true)}
hasUpdateAvailable={false}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ActionSelectionStep
selectedExtension={createMockExtension('active-ext', true)}
hasUpdateAvailable={false}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -50,11 +53,13 @@ describe('ActionSelectionStep Snapshots', () => {
it('should render for disabled extension', () => {
const { lastFrame } = render(
<ActionSelectionStep
selectedExtension={createMockExtension('disabled-ext', false)}
hasUpdateAvailable={false}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ActionSelectionStep
selectedExtension={createMockExtension('disabled-ext', false)}
hasUpdateAvailable={false}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -62,11 +67,13 @@ describe('ActionSelectionStep Snapshots', () => {
it('should render for extension with update available', () => {
const { lastFrame } = render(
<ActionSelectionStep
selectedExtension={createMockExtension('update-ext', true)}
hasUpdateAvailable={true}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ActionSelectionStep
selectedExtension={createMockExtension('update-ext', true)}
hasUpdateAvailable={true}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -74,11 +81,13 @@ describe('ActionSelectionStep Snapshots', () => {
it('should render for disabled extension with update', () => {
const { lastFrame } = render(
<ActionSelectionStep
selectedExtension={createMockExtension('disabled-update-ext', false)}
hasUpdateAvailable={true}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ActionSelectionStep
selectedExtension={createMockExtension('disabled-update-ext', false)}
hasUpdateAvailable={true}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -86,11 +95,13 @@ describe('ActionSelectionStep Snapshots', () => {
it('should render with no extension selected', () => {
const { lastFrame } = render(
<ActionSelectionStep
selectedExtension={null}
hasUpdateAvailable={false}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ActionSelectionStep
selectedExtension={null}
hasUpdateAvailable={false}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();

View file

@ -7,6 +7,7 @@
import { render } from 'ink-testing-library';
import { describe, it, expect, vi } from 'vitest';
import { ExtensionListStep } from './ExtensionListStep.js';
import { KeypressProvider } from '../../../contexts/KeypressContext.js';
import type { Extension } from '@qwen-code/qwen-code-core';
import { ExtensionUpdateState } from '../../../state/extensions.js';
@ -41,11 +42,13 @@ describe('ExtensionListStep Snapshots', () => {
it('should render empty state', () => {
const { lastFrame } = render(
<ExtensionListStep
extensions={[]}
extensionsUpdateState={new Map()}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ExtensionListStep
extensions={[]}
extensionsUpdateState={new Map()}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -54,11 +57,13 @@ describe('ExtensionListStep Snapshots', () => {
it('should render list with single extension', () => {
const extensions = [createMockExtension('test-extension', true)];
const { lastFrame } = render(
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={new Map()}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={new Map()}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -77,11 +82,13 @@ describe('ExtensionListStep Snapshots', () => {
]);
const { lastFrame } = render(
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={updateState}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={updateState}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -94,11 +101,13 @@ describe('ExtensionListStep Snapshots', () => {
]);
const { lastFrame } = render(
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={updateState}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={updateState}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();
@ -111,11 +120,13 @@ describe('ExtensionListStep Snapshots', () => {
]);
const { lastFrame } = render(
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={updateState}
{...baseProps}
/>,
<KeypressProvider kittyProtocolEnabled={false}>
<ExtensionListStep
extensions={extensions}
extensionsUpdateState={updateState}
{...baseProps}
/>
</KeypressProvider>,
);
expect(lastFrame()).toMatchSnapshot();

View file

@ -1,166 +1,38 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ActionSelectionStep Snapshots > should render for active extension without update 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- useSelectionList (src/ui/hooks/useSelectionList.ts:287:3)
- BaseSelectionList (src/ui/components/shared/BaseSelectionList.tsx:64:27)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
"
"● View Details
Disable Extension
Uninstall Extension
Back"
`;
exports[`ActionSelectionStep Snapshots > should render for disabled extension 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- useSelectionList (src/ui/hooks/useSelectionList.ts:287:3)
- BaseSelectionList (src/ui/components/shared/BaseSelectionList.tsx:64:27)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
"
"● View Details
Enable Extension
Uninstall Extension
Back"
`;
exports[`ActionSelectionStep Snapshots > should render for disabled extension with update 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- useSelectionList (src/ui/hooks/useSelectionList.ts:287:3)
- BaseSelectionList (src/ui/components/shared/BaseSelectionList.tsx:64:27)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
"
"● View Details
Update Extension
Enable Extension
Uninstall Extension
Back"
`;
exports[`ActionSelectionStep Snapshots > should render for extension with update available 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- useSelectionList (src/ui/hooks/useSelectionList.ts:287:3)
- BaseSelectionList (src/ui/components/shared/BaseSelectionList.tsx:64:27)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
"
"● View Details
Update Extension
Disable Extension
Uninstall Extension
Back"
`;
exports[`ActionSelectionStep Snapshots > should render with no extension selected 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- useSelectionList (src/ui/hooks/useSelectionList.ts:287:3)
- BaseSelectionList (src/ui/components/shared/BaseSelectionList.tsx:64:27)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
"
"● View Details
Enable Extension
Uninstall Extension
Back"
`;

View file

@ -1,171 +1,36 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ExtensionListStep Snapshots > should render empty state 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- ExtensionListStep (src/ui/components/extensions/steps/ExtensionListStep.tsx:36:3)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
-workLoopSyn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.devel
opment.js:12644:41)
"
"No extensions installed.
Use '/extensions install' to install your first extension."
`;
exports[`ExtensionListStep Snapshots > should render list with multiple extensions 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
"● active-extension v1.0.0 (active) [up to date]
disabled-extension v1.0.0 (disabled) [not updatable]
update-available v1.0.0 (active) [update available]
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- ExtensionListStep (src/ui/components/extensions/steps/ExtensionListStep.tsx:36:3)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
-workLoopSyn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.devel
opment.js:12644:41)
"
3 extensions installed"
`;
exports[`ExtensionListStep Snapshots > should render list with single extension 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
"● test-extension v1.0.0 (active)
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- ExtensionListStep (src/ui/components/extensions/steps/ExtensionListStep.tsx:36:3)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
-workLoopSyn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.devel
opment.js:12644:41)
"
1 extensions installed"
`;
exports[`ExtensionListStep Snapshots > should render with checking status 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
"● checking-extension v1.0.0 (active) [checking for updates]
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- ExtensionListStep (src/ui/components/extensions/steps/ExtensionListStep.tsx:36:3)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
-workLoopSyn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.devel
opment.js:12644:41)
"
1 extensions installed"
`;
exports[`ExtensionListStep Snapshots > should render with error status 1`] = `
"
ERROR useKeypressContext must be used within a KeypressProvider
"● error-extension v1.0.0 (active) [error]
src/ui/contexts/KeypressContext.tsx:77:11
74: export function useKeypressContext() {
75: const context = useContext(KeypressContext);
76: if (!context) {
77: throw new Error(
78: 'useKeypressContext must be used within a KeypressProvider',
79: );
80: }
- useKeypressContext (src/ui/contexts/KeypressContext.tsx:77:11)
- useKeypress (src/ui/hooks/useKeypress.ts:24:38)
- ExtensionListStep (src/ui/components/extensions/steps/ExtensionListStep.tsx:36:3)
-Object.react-stack-bott (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reco
m-frame nciler.development.js:15859:20)
-renderWithHoo (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.dev
s elopment.js:3221:22)
-updateFunctionComp (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconcile
nent r.development.js:6475:19)
-beginWor (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.developm
ent.js:8009:18)
-runWithFiberIn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
EV velopment.js:1738:13)
-performUnitOfW (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.de
rk velopment.js:12834:22)
-workLoopSyn (/Users/mochi/code/qwen-code/node_modules/react-reconciler/cjs/react-reconciler.devel
opment.js:12644:41)
"
1 extensions installed"
`;