fix(plugin-inventory): rename install Remote to installPlugin

The RemoteNamespaceService reserves `install` as its private registration
method, so a Remote method also named `install` collided at client-api boot
(`pluginInventory/install conflicts with its namespace service`). Rename the
plugin-install Remote to `installPlugin` across host, client, and tests.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-14 18:57:08 +08:00
parent 6cd7c5a590
commit a0efe02c04
5 changed files with 21 additions and 21 deletions
@@ -22,7 +22,7 @@ export interface PluginInventorySettingsTabInjected {
/** List the offline-installable optional bundles. */ /** List the offline-installable optional bundles. */
availableBundles: () => Promise<AvailableBundlesSnapshot> availableBundles: () => Promise<AvailableBundlesSnapshot>
/** Install a bundle or registry plugin; the host persists the change. */ /** Install a bundle or registry plugin; the host persists the change. */
install: (spec: InstallSpec) => Promise<InstallResult> installPlugin: (spec: InstallSpec) => Promise<InstallResult>
/** Un-compose an offline optional bundle. */ /** Un-compose an offline optional bundle. */
uninstall: (name: string) => Promise<InstallResult> uninstall: (name: string) => Promise<InstallResult>
} }
@@ -80,7 +80,7 @@ type BundleState =
/** Render the current Loader inventory with per-plugin enable/disable and install. */ /** Render the current Loader inventory with per-plugin enable/disable and install. */
export function PluginInventorySettingsTab({ export function PluginInventorySettingsTab({
list, setEnabled, availableBundles, install, uninstall, t, list, setEnabled, availableBundles, installPlugin, uninstall, t,
}: PluginInventorySettingsTabProps): ReactNode { }: PluginInventorySettingsTabProps): ReactNode {
const catalogId = useId() const catalogId = useId()
const [request, setRequest] = useState(0) const [request, setRequest] = useState(0)
@@ -126,7 +126,7 @@ export function PluginInventorySettingsTab({
setInstallBusy(name) setInstallBusy(name)
setInstallNote(null) setInstallNote(null)
const action = mode === 'install' const action = mode === 'install'
? install({ type: 'bundle', name }) ? installPlugin({ type: 'bundle', name })
: uninstall(name) : uninstall(name)
void action.then( void action.then(
() => { () => {
@@ -47,10 +47,10 @@ export function apply(ctx: ClientContext): void {
} }
return result.value return result.value
} }
const install: PluginInventorySettingsTabInjected['install'] = async (spec) => { const installPlugin: PluginInventorySettingsTabInjected['installPlugin'] = async (spec) => {
const result = await ctx.remote.pluginInventory.install(spec) const result = await ctx.remote.pluginInventory.installPlugin(spec)
if (!result.ok) { if (!result.ok) {
throw new Error(`pluginInventory.install failed: ${result.error.code}: ${result.error.message}`) throw new Error(`pluginInventory.installPlugin failed: ${result.error.code}: ${result.error.message}`)
} }
return result.value return result.value
} }
@@ -62,7 +62,7 @@ export function apply(ctx: ClientContext): void {
return result.value return result.value
} }
const injected = (): PluginInventorySettingsTabInjected => ({ const injected = (): PluginInventorySettingsTabInjected => ({
list, setEnabled, availableBundles, install, uninstall, list, setEnabled, availableBundles, installPlugin, uninstall,
}) })
ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({ ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
@@ -16,14 +16,14 @@ const t = ((key: PluginInventoryLocaleKey): string => en[key]) as PluginInventor
function props( function props(
list: PluginInventorySettingsTabInjected['list'], list: PluginInventorySettingsTabInjected['list'],
bundles: PluginInventorySettingsTabInjected['availableBundles'] = async () => ({ available: [] }), bundles: PluginInventorySettingsTabInjected['availableBundles'] = async () => ({ available: [] }),
install: PluginInventorySettingsTabInjected['install'] = async () => ({ ok: true as const, restartRequired: true }), installPlugin: PluginInventorySettingsTabInjected['installPlugin'] = async () => ({ ok: true as const, restartRequired: true }),
uninstall: PluginInventorySettingsTabInjected['uninstall'] = async () => ({ ok: true as const, restartRequired: true }), uninstall: PluginInventorySettingsTabInjected['uninstall'] = async () => ({ ok: true as const, restartRequired: true }),
): PluginInventorySettingsTabProps { ): PluginInventorySettingsTabProps {
return { return {
t, t,
list, list,
availableBundles: bundles, availableBundles: bundles,
install, installPlugin,
uninstall, uninstall,
} as PluginInventorySettingsTabProps } as PluginInventorySettingsTabProps
} }
@@ -163,7 +163,7 @@ describe('PluginInventorySettingsTab', () => {
}) })
it('renders the installable bundles with install and uninstall actions', async () => { it('renders the installable bundles with install and uninstall actions', async () => {
const install = vi.fn<PluginInventorySettingsTabInjected['install']>(async () => ({ ok: true, restartRequired: true })) const installPlugin = vi.fn<PluginInventorySettingsTabInjected['installPlugin']>(async () => ({ ok: true, restartRequired: true }))
const uninstall = vi.fn<PluginInventorySettingsTabInjected['uninstall']>(async () => ({ ok: true, restartRequired: true })) const uninstall = vi.fn<PluginInventorySettingsTabInjected['uninstall']>(async () => ({ ok: true, restartRequired: true }))
const bundles: PluginInventorySettingsTabInjected['availableBundles'] = async () => ({ const bundles: PluginInventorySettingsTabInjected['availableBundles'] = async () => ({
available: [ available: [
@@ -171,7 +171,7 @@ describe('PluginInventorySettingsTab', () => {
{ name: '@deepseek-ai/dsh-installed-bundle', installed: true }, { name: '@deepseek-ai/dsh-installed-bundle', installed: true },
], ],
}) })
render(<PluginInventorySettingsTab {...props(async () => SNAPSHOT, bundles, install, uninstall)} />) render(<PluginInventorySettingsTab {...props(async () => SNAPSHOT, bundles, installPlugin, uninstall)} />)
expect(await screen.findByText(en.available)).toBeTruthy() expect(await screen.findByText(en.available)).toBeTruthy()
expect(screen.getByText('@deepseek-ai/dsh-new-bundle')).toBeTruthy() expect(screen.getByText('@deepseek-ai/dsh-new-bundle')).toBeTruthy()
@@ -179,7 +179,7 @@ describe('PluginInventorySettingsTab', () => {
// Install a not-installed bundle. // Install a not-installed bundle.
fireEvent.click(screen.getByRole('button', { name: en.install })) fireEvent.click(screen.getByRole('button', { name: en.install }))
expect(install).toHaveBeenCalledWith({ type: 'bundle', name: '@deepseek-ai/dsh-new-bundle' }) expect(installPlugin).toHaveBeenCalledWith({ type: 'bundle', name: '@deepseek-ai/dsh-new-bundle' })
expect(await screen.findByText(en.restartRequired)).toBeTruthy() expect(await screen.findByText(en.restartRequired)).toBeTruthy()
// Uninstall an installed bundle. // Uninstall an installed bundle.
+2 -2
View File
@@ -138,8 +138,8 @@ export class PluginInventoryGateway extends TypertRemoteService {
* @param spec - the bundle name or registry package spec to install. * @param spec - the bundle name or registry package spec to install.
* @returns a confirmation; `restartRequired` tells the caller to restart. * @returns a confirmation; `restartRequired` tells the caller to restart.
*/ */
@Remote('install') @Remote('installPlugin')
install(spec: InstallSpec): InstallResult { installPlugin(spec: InstallSpec): InstallResult {
const profileDir = this.profileDir() const profileDir = this.profileDir()
const anchor = this.ctx.get('dshInstallAnchor') as string | undefined const anchor = this.ctx.get('dshInstallAnchor') as string | undefined
if (anchor === undefined) { if (anchor === undefined) {
@@ -49,7 +49,7 @@ describe('PluginInventoryGateway', () => {
{ method: 'list', invocation: { kind: 'direct' } }, { method: 'list', invocation: { kind: 'direct' } },
{ method: 'setEnabled', invocation: { kind: 'direct' } }, { method: 'setEnabled', invocation: { kind: 'direct' } },
{ method: 'availableBundles', invocation: { kind: 'direct' } }, { method: 'availableBundles', invocation: { kind: 'direct' } },
{ method: 'install', invocation: { kind: 'direct' } }, { method: 'installPlugin', invocation: { kind: 'direct' } },
{ method: 'uninstall', invocation: { kind: 'direct' } }, { method: 'uninstall', invocation: { kind: 'direct' } },
]) ])
}) })
@@ -151,9 +151,9 @@ describe('PluginInventoryGateway', () => {
ctx.baseUrl = pathToFileURL(dir + '/').href ctx.baseUrl = pathToFileURL(dir + '/').href
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'dsh-profile-test', dsh: { profile: { bundles: [] } } })) writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'dsh-profile-test', dsh: { profile: { bundles: [] } } }))
try { try {
expect(() => inventory.install({ type: 'bundle', name: 'x' })).toThrow(/install anchor is unavailable/) expect(() => inventory.installPlugin({ type: 'bundle', name: 'x' })).toThrow(/install anchor is unavailable/)
ctx.provide('dshInstallAnchor', join(dir, 'package.json')) ctx.provide('dshInstallAnchor', join(dir, 'package.json'))
expect(() => inventory.install({ type: 'registry', spec: 'x' })).toThrow(/not permitted/) expect(() => inventory.installPlugin({ type: 'registry', spec: 'x' })).toThrow(/not permitted/)
} finally { } finally {
rmSync(dir, { recursive: true, force: true }) rmSync(dir, { recursive: true, force: true })
} }
@@ -185,7 +185,7 @@ describe('PluginInventoryGateway', () => {
writeFileSync(join(dir, 'node_modules', 'b', 'cordis.patch.yml'), '[]\n') writeFileSync(join(dir, 'node_modules', 'b', 'cordis.patch.yml'), '[]\n')
ctx.provide('dshInstallAnchor', join(dir, 'package.json')) ctx.provide('dshInstallAnchor', join(dir, 'package.json'))
try { try {
inventory.install({ type: 'bundle', name: 'b' }) inventory.installPlugin({ type: 'bundle', name: 'b' })
expect(readProfileManifest('dsh', dir).dsh?.profile?.bundles).toEqual(['b']) expect(readProfileManifest('dsh', dir).dsh?.profile?.bundles).toEqual(['b'])
} finally { } finally {
rmSync(dir, { recursive: true, force: true }) rmSync(dir, { recursive: true, force: true })
@@ -203,7 +203,7 @@ describe('PluginInventoryGateway', () => {
try { try {
ctx.provide('dshInstallAnchor', join(dir, 'package.json')) ctx.provide('dshInstallAnchor', join(dir, 'package.json'))
ctx.provide('dshAllowPluginInstall', true) ctx.provide('dshAllowPluginInstall', true)
expect(inventory.install({ type: 'registry', spec: 'some-pkg' }).restartRequired).toBe(true) expect(inventory.installPlugin({ type: 'registry', spec: 'some-pkg' }).restartRequired).toBe(true)
} finally { } finally {
delete process.env.DSH_PNPM delete process.env.DSH_PNPM
rmSync(dir, { recursive: true, force: true }) rmSync(dir, { recursive: true, force: true })
@@ -260,7 +260,7 @@ describe('PluginInventoryGateway', () => {
try { try {
ctx.provide('dshInstallAnchor', join(dir, 'package.json')) ctx.provide('dshInstallAnchor', join(dir, 'package.json'))
ctx.provide('dshAllowPluginInstall', true) ctx.provide('dshAllowPluginInstall', true)
expect(() => inventory.install({ type: 'registry', spec: 'x' })).toThrow(/bundled pnpm is unavailable/) expect(() => inventory.installPlugin({ type: 'registry', spec: 'x' })).toThrow(/bundled pnpm is unavailable/)
} finally { } finally {
rmSync(dir, { recursive: true, force: true }) rmSync(dir, { recursive: true, force: true })
} }
@@ -276,7 +276,7 @@ describe('PluginInventoryGateway', () => {
try { try {
ctx.provide('dshInstallAnchor', join(dir, 'package.json')) ctx.provide('dshInstallAnchor', join(dir, 'package.json'))
ctx.provide('dshAllowPluginInstall', true) ctx.provide('dshAllowPluginInstall', true)
expect(() => inventory.install({ type: 'registry', spec: 'x' })).toThrow(/failed to read profile manifest/) expect(() => inventory.installPlugin({ type: 'registry', spec: 'x' })).toThrow(/failed to read profile manifest/)
} finally { } finally {
delete process.env.DSH_PNPM delete process.env.DSH_PNPM
rmSync(dir, { recursive: true, force: true }) rmSync(dir, { recursive: true, force: true })