Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: uninstall modules #229

Merged
merged 6 commits into from May 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/devtools-kit/src/_types/rpc.ts
Expand Up @@ -50,6 +50,7 @@ export interface ServerFunctions {
openInEditor(filepath: string): Promise<boolean>
restartNuxt(hard?: boolean): Promise<void>
installNuxtModule(name: string, dry?: boolean): Promise<InstallModuleReturn>
unInstallNuxtModule(name: string, dry?: boolean): Promise<InstallModuleReturn>
antfu marked this conversation as resolved.
Show resolved Hide resolved
}

export interface ClientFunctions {
Expand Down
31 changes: 21 additions & 10 deletions packages/devtools/client/components/ModuleInstallList.vue
Expand Up @@ -4,9 +4,16 @@ import { RecycleScroller } from 'vue-virtual-scroller'
import type { InstallModuleReturn, ModuleStaticInfo } from '@nuxt/devtools-kit/types'
import Fuse from 'fuse.js'

const Dialog = createTemplatePromise<boolean, [info: ModuleStaticInfo, result: InstallModuleReturn]>()
const Dialog = createTemplatePromise<boolean, [info: ModuleStaticInfo, result: InstallModuleReturn, type: 'install' | 'uninstall']>()
const collection = await useModulesInfo()
const nuxt3only = collection.filter(i => i.compatibility.nuxt.includes('^3'))
const { packageModules } = useModules()
const nuxt3only = collection.map((module) => {
const installed = packageModules.value.find(m => m.entryPath.includes(module.name))
return {
...module,
installed,
}
})

const config = useServerConfig()
const router = useRouter()
Expand All @@ -26,17 +33,18 @@ const items = computed(() => {
return fuse.value.search(search.value).map(r => r.item)
})

async function install(item: ModuleStaticInfo) {
const result = await rpc.installNuxtModule(item.npm, true)
async function action(item: ModuleStaticInfo, type: 'install' | 'uninstall') {
const method = type === 'install' ? rpc.installNuxtModule : rpc.unInstallNuxtModule
const result = await method(item.npm, true)

if (!result.commands)
return

if (!await Dialog.start(item, result))
if (!await Dialog.start(item, result, type))
return

router.push(`/modules/terminals?id=${encodeURIComponent(result.processId)}`)
await rpc.installNuxtModule(item.npm, false)
await method(item.npm, false)
}

const openInEditor = useOpenInEditor()
Expand Down Expand Up @@ -71,8 +79,11 @@ const openInEditor = useOpenInEditor()
role="button"
:info="item"
mb2 h-full class="hover:bg-active!"
:class="{
'text-green border-green': item.installed,
}"
antfu marked this conversation as resolved.
Show resolved Hide resolved
:compact="true"
@click="install(item)"
@click="action(item, item.installed ? 'uninstall' : 'install')"
/>
</RecycleScroller>
</div>
Expand All @@ -83,7 +94,7 @@ const openInEditor = useOpenInEditor()
<ModuleItemBase :mod="{}" :info="args[0]" border="none" w-150 n-panel-grids />
<div flex="~ col gap-2" w-150 p4 border="t base">
<h2 text-xl>
Installing <span capitalize text-primary>{{ args[0].name }}</span> module?
{{ args[2] }} <span capitalize :class="args[2] === 'install' ? 'text-primary' : 'text-red'">{{ args[0].name }}</span> module?
</h2>

<p op50>
Expand Down Expand Up @@ -114,8 +125,8 @@ const openInEditor = useOpenInEditor()
<NButton @click="resolve(false)">
Cancel
</NButton>
<NButton n="solid primary" @click="resolve(true)">
Install
<NButton n="solid primary" capitalize @click="resolve(true)">
{{ args[2] }}
</NButton>
</div>
</div>
Expand Down
38 changes: 37 additions & 1 deletion packages/devtools/client/composables/state.ts
Expand Up @@ -5,14 +5,50 @@ import { objectPick } from '@antfu/utils'
import type { HookInfo, ModuleBuiltinTab, ModuleCustomTab, ModuleStaticInfo, RouteInfo, TabCategory } from '../../src/types'

let modules: Promise<ModuleStaticInfo[]> | undefined
const ignores = [
'pages',
'meta',
'components',
'imports',
'nuxt-config-schema',
'@nuxt/devtools',
'@nuxt/telemetry',
]

export async function useModulesInfo() {
if (modules)
return modules
modules = $fetch('https://cdn.jsdelivr.net/npm/@nuxt/modules@latest/modules.json')
modules = $fetch('https://cdn.jsdelivr.net/npm/@nuxt/modules@latest/modules.json').then((res) => {
return res.filter((m: any) => !ignores.includes(m.npm))
})
return modules
}

export function useModules() {
const config = useServerConfig()
const modules = computed(() => config.value?._installedModules || [])
const packageModules = ref<any[]>([])
const userModules = ref<any[]>([])

watchEffect(() => {
packageModules.value.length = 0
userModules.value.length = 0
for (const m of modules.value) {
if (ignores.includes(m.meta?.name))
continue
if (m.entryPath && isNodeModulePath(m.entryPath))
packageModules.value.push(m)
else
userModules.value.push(m)
}
})

return {
packageModules,
userModules,
}
}

export function useComponents() {
const client = useClient()
const serverComponents = useAsyncState('getComponents', () => rpc.getComponents())
Expand Down
28 changes: 1 addition & 27 deletions packages/devtools/client/pages/modules/modules.vue
Expand Up @@ -5,35 +5,9 @@ definePageMeta({
order: 4,
})

const ignores = [
'pages',
'meta',
'components',
'imports',
'nuxt-config-schema',
'@nuxt/devtools',
'@nuxt/telemetry',
]

const config = useServerConfig()
const modules = computed(() => config.value?._installedModules || [])
const packageModules = ref<any[]>([])
const userModules = ref<any[]>([])
const installModuleOpen = ref(false)
const { showExperimentalFeatures } = useDevToolsSettings()

watchEffect(() => {
packageModules.value.length = 0
userModules.value.length = 0
for (const m of modules.value) {
if (ignores.includes(m.meta?.name))
continue
if (m.entryPath && isNodeModulePath(m.entryPath))
packageModules.value.push(m)
else
userModules.value.push(m)
}
})
const { packageModules, userModules } = useModules()
</script>

<template>
Expand Down
53 changes: 52 additions & 1 deletion packages/devtools/src/server-rpc/npm.ts
Expand Up @@ -3,7 +3,7 @@ import { startSubprocess } from '@nuxt/devtools-kit'
import isInstalledGlobally from 'is-installed-globally'
import { detectPackageManager } from 'nypm'
import { parseModule } from 'magicast'
import { addNuxtModule } from 'magicast/helpers'
import { addNuxtModule, getDefaultExportOptions } from 'magicast/helpers'
import { checkForUpdateOf } from '../npm'
import type { NpmCommandOptions, NpmCommandType, NuxtDevtoolsServerContext, PackageManagerName, PackageUpdateInfo, ServerFunctions } from '../types'

Expand All @@ -26,6 +26,9 @@ export function setupNpmRPC({ nuxt }: NuxtDevtoolsServerContext) {
// TODO: smartly detect dev/global installs as default
if (command === 'install' || command === 'update')
return [agent, agent === 'npm' ? 'install' : 'add', `${packageName}@latest`, dev ? '-D' : '', global ? '-g' : '', '--ignore-scripts'].filter(Boolean)

if (command === 'uninstall')
return [agent, agent === 'npm' ? 'uninstall' : 'remove', packageName, global ? '-g' : ''].filter(Boolean)
}

async function runNpmCommand(command: NpmCommandType, packageName: string, options: NpmCommandOptions = {}) {
Expand Down Expand Up @@ -92,6 +95,54 @@ export function setupNpmRPC({ nuxt }: NuxtDevtoolsServerContext) {
await fs.writeFile(filepath, generated, 'utf-8')
}

return {
configOriginal: source,
configGenerated: generated,
commands,
processId,
}
},
async unInstallNuxtModule(name: string, dry = true) {
const commands = (await getNpmCommand('uninstall', name))!

const filepath = nuxt.options._nuxtConfigFile
const source = await fs.readFile(filepath, 'utf-8')
const mod = parseModule(source, { sourceFileName: filepath })

// TODO: remove module from config
// removeNuxtModule(mod, name)
const config = getDefaultExportOptions(mod)
config.modules ||= []
if (config.modules.includes(name)) {
Object.values(config.modules).forEach((value, index) => {
if (value === name)
config.modules.splice(index - 1, 1)
})
}

const generated = mod.generate().code

const processId = `nuxt:remove-module:${name}`

if (!dry) {
const process = startSubprocess({
command: commands[0],
args: commands.slice(1),
}, {
id: processId,
name: `Uninstall ${name}`,
icon: 'carbon:new-tab',
restartable: false,
})

await process.getProcess()

if (process.getProcess().exitCode !== 0)
throw new Error('Failed to uninstall module')

await fs.writeFile(filepath, generated, 'utf-8')
}

return {
configOriginal: source,
configGenerated: generated,
Expand Down