├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── auto-imports.d.ts ├── cli.mjs ├── components.d.ts ├── logo.svg ├── package.json ├── pnpm-lock.yaml ├── src ├── App.vue ├── components │ ├── Deps.vue │ ├── Download.vue │ ├── Guide.vue │ ├── Header.vue │ ├── Input.ts │ ├── InputBox.vue │ └── Package.vue ├── main.ts ├── services │ ├── algolia.ts │ ├── ni.ts │ └── npm-registry.ts └── store │ ├── index.ts │ └── search.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite-env.d.ts ├── vite.config.ts └── vtui-npm.gif /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .history 4 | temp 5 | **/*.log 6 | **/.DS_Store 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@antfu", 3 | "rules":{ 4 | "no-console":0, 5 | "@typescript-eslint/ban-ts-comment":0, 6 | "vue/one-component-per-file":0, 7 | "vue/attribute-hyphenation": 0 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | .eslintcache 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 webfansplz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |
4 | 5 | VTui NPM logo 6 | 7 |
8 |
9 |

10 |

11 | Interactive CLI for npm - search and install JavaScript package. Powered by vue-termui. 12 |

13 | 14 | 15 | ## 📺 Preview 16 | 17 |

18 | VTui NPM 19 |

20 | 21 | ## 🔥 Features 22 | 23 | - 🔍 **Powerful Search:** Search all the packages from npm and Yarn. 24 | - 👼 **User Friendly:** Search and install packages with a simple keystroke. 25 | - 📦 **Powerful Installer:** Support npm · yarn · pnpm · bun. 26 | 27 | ## 📦 Install 28 | 29 | ```sh 30 | npm install vtui-npm 31 | ``` 32 | 33 | ## 🎮 Usage 34 | 35 | Open the terminal and then typing `vnpm`. 36 | 37 | ```sh 38 | vnpm 39 | ``` 40 | 41 | ```sh 42 | # Switch to npm registry for search (algolia by default),we recommend using the algolia search. 43 | vnpm -n | vnpm --npm 44 | ``` 45 | 46 | 47 | 48 | ## 💗 Credits 49 | 50 | - Search powered by [Algolia](https://github.com/algolia/algoliasearch-client-javascript). 51 | - Installer powered by [@antfu/ni](https://github.com/antfu/ni). 52 | - Terminal UI powered by [vue-termui](https://github.com/vue-terminal/vue-termui). 53 | 54 | 55 | ## 📄 License 56 | 57 | [MIT](./LICENSE) 58 | -------------------------------------------------------------------------------- /auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by 'unplugin-auto-import' 2 | export {} 3 | declare global { 4 | const EffectScope: typeof import('vue-termui')['EffectScope'] 5 | const MouseEventType: typeof import('vue-termui')['MouseEventType'] 6 | const computed: typeof import('vue-termui')['computed'] 7 | const createApp: typeof import('vue-termui')['createApp'] 8 | const customRef: typeof import('vue-termui')['customRef'] 9 | const defineAsyncComponent: typeof import('vue-termui')['defineAsyncComponent'] 10 | const defineComponent: typeof import('vue-termui')['defineComponent'] 11 | const effectScope: typeof import('vue-termui')['effectScope'] 12 | const getCurrentInstance: typeof import('vue-termui')['getCurrentInstance'] 13 | const getCurrentScope: typeof import('vue-termui')['getCurrentScope'] 14 | const h: typeof import('vue-termui')['h'] 15 | const inject: typeof import('vue-termui')['inject'] 16 | const inputDataToString: typeof import('vue-termui')['inputDataToString'] 17 | const isInputDataEvent: typeof import('vue-termui')['isInputDataEvent'] 18 | const isKeyDataEvent: typeof import('vue-termui')['isKeyDataEvent'] 19 | const isMouseDataEvent: typeof import('vue-termui')['isMouseDataEvent'] 20 | const isReadonly: typeof import('vue-termui')['isReadonly'] 21 | const isRef: typeof import('vue-termui')['isRef'] 22 | const markRaw: typeof import('vue-termui')['markRaw'] 23 | const nextTick: typeof import('vue-termui')['nextTick'] 24 | const onActivated: typeof import('vue-termui')['onActivated'] 25 | const onBeforeMount: typeof import('vue-termui')['onBeforeMount'] 26 | const onBeforeUnmount: typeof import('vue-termui')['onBeforeUnmount'] 27 | const onBeforeUpdate: typeof import('vue-termui')['onBeforeUpdate'] 28 | const onDeactivated: typeof import('vue-termui')['onDeactivated'] 29 | const onErrorCaptured: typeof import('vue-termui')['onErrorCaptured'] 30 | const onInputData: typeof import('vue-termui')['onInputData'] 31 | const onKeyData: typeof import('vue-termui')['onKeyData'] 32 | const onMounted: typeof import('vue-termui')['onMounted'] 33 | const onMouseData: typeof import('vue-termui')['onMouseData'] 34 | const onRenderTracked: typeof import('vue-termui')['onRenderTracked'] 35 | const onRenderTriggered: typeof import('vue-termui')['onRenderTriggered'] 36 | const onScopeDispose: typeof import('vue-termui')['onScopeDispose'] 37 | const onServerPrefetch: typeof import('vue-termui')['onServerPrefetch'] 38 | const onUnmounted: typeof import('vue-termui')['onUnmounted'] 39 | const onUpdated: typeof import('vue-termui')['onUpdated'] 40 | const provide: typeof import('vue-termui')['provide'] 41 | const reactive: typeof import('vue-termui')['reactive'] 42 | const readonly: typeof import('vue-termui')['readonly'] 43 | const ref: typeof import('vue-termui')['ref'] 44 | const resolveComponent: typeof import('vue-termui')['resolveComponent'] 45 | const shallowReactive: typeof import('vue-termui')['shallowReactive'] 46 | const shallowReadonly: typeof import('vue-termui')['shallowReadonly'] 47 | const shallowRef: typeof import('vue-termui')['shallowRef'] 48 | const toRaw: typeof import('vue-termui')['toRaw'] 49 | const toRef: typeof import('vue-termui')['toRef'] 50 | const toRefs: typeof import('vue-termui')['toRefs'] 51 | const triggerRef: typeof import('vue-termui')['triggerRef'] 52 | const unref: typeof import('vue-termui')['unref'] 53 | const useAttrs: typeof import('vue-termui')['useAttrs'] 54 | const useInterval: typeof import('vue-termui')['useInterval'] 55 | const useLog: typeof import('vue-termui')['useLog'] 56 | const useRootNode: typeof import('vue-termui')['useRootNode'] 57 | const useSlots: typeof import('vue-termui')['useSlots'] 58 | const useTimeout: typeof import('vue-termui')['useTimeout'] 59 | const watch: typeof import('vue-termui')['watch'] 60 | const watchEffect: typeof import('vue-termui')['watchEffect'] 61 | } 62 | -------------------------------------------------------------------------------- /cli.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import('./dist/main.mjs') 3 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/core/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | export {} 7 | 8 | declare module '@vue/runtime-core' { 9 | export interface GlobalComponents { 10 | Br: typeof import('vue-termui')['TuiNewline'] 11 | Deps: typeof import('./src/components/Deps.vue')['default'] 12 | Div: typeof import('vue-termui')['TuiBox'] 13 | Download: typeof import('./src/components/Download.vue')['default'] 14 | Guide: typeof import('./src/components/Guide.vue')['default'] 15 | Header: typeof import('./src/components/Header.vue')['default'] 16 | InputBox: typeof import('./src/components/InputBox.vue')['default'] 17 | Link: typeof import('vue-termui')['TuiLink'] 18 | Package: typeof import('./src/components/Package.vue')['default'] 19 | Progressbar: typeof import('vue-termui')['TuiProgressBar'] 20 | Span: typeof import('vue-termui')['TuiText'] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vtui-npm", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "packageManager": "pnpm@7.9.0", 6 | "description": "Interactive CLI for npm - search and install JavaScript package.", 7 | "author": "webfansplz", 8 | "license": "MIT", 9 | "homepage": "https://github.com/webfansplz/vtui-npm#readme", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/webfansplz/vtui-npm.git" 13 | }, 14 | "keywords": [ 15 | "vue", 16 | "vite", 17 | "terminal", 18 | "vue-termui", 19 | "npm", 20 | "vue-npm" 21 | ], 22 | "bin": { 23 | "vnpm": "./cli.mjs" 24 | }, 25 | "files": [ 26 | "dist/*.mjs" 27 | ], 28 | "scripts": { 29 | "dev": "vtui dev", 30 | "build": "vtui build", 31 | "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx,.md,.json --max-warnings 0 --cache", 32 | "lint:fix": "pnpm run lint --fix", 33 | "release": "pnpm run build && npm publish" 34 | }, 35 | "dependencies": { 36 | "@algolia/requester-node-http": "^4.14.2", 37 | "@antfu/ni": "^0.18.3", 38 | "@vue/runtime-core": "^3.2.41", 39 | "algoliasearch": "^4.14.2", 40 | "chalk": "^5.1.2", 41 | "execa": "^6.1.0", 42 | "ohmyfetch": "^0.4.21", 43 | "pinia": "^2.0.23", 44 | "vue": "^3.2.41", 45 | "vue-termui": "^0.0.17" 46 | }, 47 | "devDependencies": { 48 | "@antfu/eslint-config": "^0.29.3", 49 | "@types/node": "^18.11.9", 50 | "@vitejs/plugin-vue": "3.2.0", 51 | "@vue-termui/cli": "^0.0.17", 52 | "eslint": "^8.26.0", 53 | "typescript": "^4.8.4", 54 | "unplugin-auto-import": "^0.11.2", 55 | "unplugin-vue-components": "^0.22.8", 56 | "vite": "3.2.2", 57 | "vite-plugin-vue-termui": "^0.0.11" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | -------------------------------------------------------------------------------- /src/components/Deps.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 34 | -------------------------------------------------------------------------------- /src/components/Download.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 70 | -------------------------------------------------------------------------------- /src/components/Guide.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 44 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/components/Input.ts: -------------------------------------------------------------------------------- 1 | import { computed, defineComponent, h, ref } from '@vue/runtime-core' 2 | import type { PropType } from '@vue/runtime-core' 3 | import type { KeyDataEvent } from 'vue-termui' 4 | import { TuiText, onInputData } from 'vue-termui' 5 | import chalk from 'chalk' 6 | 7 | const SKIP_EVENT_KEY = ['ArrowUp', 'ArrowDown', 'Ctrl', 'Tab', 'Shift', ' ', 'ArrowLeft', 'ArrowRight', 'Enter'] 8 | 9 | export const Input = defineComponent({ 10 | props: { 11 | placeholder: { 12 | type: String, 13 | default: '', 14 | }, 15 | modelValue: { 16 | type: String, 17 | required: true, 18 | }, 19 | type: { 20 | type: String as PropType<'text' | 'password'>, 21 | default: 'text', 22 | }, 23 | }, 24 | emits: ['update:modelValue'], 25 | setup(props, { emit }) { 26 | const active = ref(true) 27 | const content = computed(() => { 28 | if (active.value) { 29 | if (props.modelValue) { 30 | return ( 31 | props.modelValue 32 | + chalk.inverse(' ') 33 | ) 34 | } 35 | else { 36 | return props.placeholder ? '' : chalk.inverse(' ') 37 | } 38 | } 39 | else { 40 | return props.modelValue 41 | } 42 | }) 43 | 44 | function updateValue(value: string) { 45 | emit('update:modelValue', value) 46 | } 47 | 48 | onInputData(({ data, event }) => { 49 | if (!active.value) 50 | return 51 | const eventKey = (event!).key 52 | if (SKIP_EVENT_KEY.includes(eventKey) || !eventKey) 53 | return 54 | 55 | // Delete Content 56 | if ( 57 | eventKey === 'Backspace' 58 | || eventKey === 'Delete' 59 | || (eventKey === 'H' && data !== 'H') // Windows compatible 60 | ) { 61 | props.modelValue && updateValue( 62 | props.modelValue.slice(0, props.modelValue.length - 1), 63 | ) 64 | } 65 | // Typing Content 66 | else { 67 | updateValue( 68 | props.modelValue 69 | + data, 70 | ) 71 | } 72 | }) 73 | 74 | return () => 75 | props.placeholder && !props.modelValue 76 | ? h( 77 | TuiText, 78 | { 79 | dimmed: true, 80 | }, 81 | () => props.placeholder, 82 | ) 83 | : h(TuiText, () => content.value) 84 | }, 85 | }) 86 | -------------------------------------------------------------------------------- /src/components/InputBox.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/components/Package.vue: -------------------------------------------------------------------------------- 1 | 108 | 109 | 177 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue-termui' 2 | import { createPinia } from 'pinia' 3 | import App from './App.vue' 4 | const pinia = createPinia() 5 | const app = createApp(App, { swapScreens: process.env.NODE_ENV === 'production' }) 6 | app.use(pinia) 7 | app.mount() 8 | 9 | process.on('exit', () => { 10 | app.unmount() 11 | }) 12 | -------------------------------------------------------------------------------- /src/services/algolia.ts: -------------------------------------------------------------------------------- 1 | import algoliasearch from 'algoliasearch' 2 | import { createNodeHttpRequester } from '@algolia/requester-node-http' 3 | 4 | export interface PackageInfo { 5 | name: string 6 | version: string 7 | descriptions: string 8 | owner: { 9 | name: string 10 | link: string 11 | } 12 | repository: { 13 | url: string 14 | } 15 | humanDownloadsLast30Days: string 16 | versions: string[] 17 | author: string 18 | downloads: string 19 | versionIndex: number 20 | activeVersion: string 21 | repoLink: string 22 | authorLink: string 23 | } 24 | 25 | /* Config */ 26 | const algolia = { 27 | appId: 'OFCNCOG2CU', 28 | apiKey: 'f54e21fa3a2a0160595bb058179bfb1e', 29 | indexName: 'npm-search', 30 | } 31 | 32 | const client = algoliasearch(algolia.appId, algolia.apiKey, { 33 | requester: createNodeHttpRequester(), 34 | }).initIndex( 35 | algolia.indexName, 36 | ) 37 | 38 | export const search = async ( 39 | query: string, 40 | page = 0, 41 | ) => { 42 | const res = await client.search(query, { 43 | attributesToRetrieve: [ 44 | 'name', 45 | 'version', 46 | 'description', 47 | 'owner', 48 | 'repository', 49 | 'humanDownloadsLast30Days', 50 | 'versions', 51 | ], 52 | page, 53 | hitsPerPage: 10, 54 | }) as unknown as { hits: PackageInfo[]; query: string } 55 | 56 | return res 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/services/ni.ts: -------------------------------------------------------------------------------- 1 | import { detect, parseNi, parseNr } from '@antfu/ni' 2 | import { execaCommand } from 'execa' 3 | 4 | export interface InstallPackageOptions { 5 | 6 | /** 7 | * Whether to DevDependencies 8 | * @default false 9 | */ 10 | isDev?: boolean 11 | /** 12 | Current working directory. 13 | 14 | Using a `URL` is only supported in Node.js `14.18.0`, `16.14.0` or above. 15 | 16 | @default process.cwd() 17 | */ 18 | cwd?: string 19 | } 20 | 21 | export async function installPackage(packages: string[], options: InstallPackageOptions = {}) { 22 | const { 23 | isDev = false, 24 | cwd = process.cwd(), 25 | } = options 26 | const agent = await detect({}) 27 | 28 | const command = await parseNi(agent!, [...(isDev ? ['-D'] : []), ...packages]) 29 | 30 | await execaCommand(command!, { stdio: 'pipe', encoding: 'utf-8', cwd }) 31 | } 32 | 33 | export async function execScript(script: string) { 34 | const agent = await detect({}) 35 | const command = await parseNr(agent!, [script]) 36 | await execaCommand(command!, { stdio: 'inherit', encoding: 'utf-8' }) 37 | } 38 | -------------------------------------------------------------------------------- /src/services/npm-registry.ts: -------------------------------------------------------------------------------- 1 | import { $fetch } from 'ohmyfetch' 2 | 3 | interface PackageInfo { 4 | name: string 5 | version: string 6 | description: string 7 | keywords: string[] 8 | date: string 9 | author?: { 10 | name?: string 11 | email?: string 12 | url?: string 13 | } 14 | links?: { 15 | npm?: string 16 | homepage?: string 17 | repository?: string 18 | bugs?: string 19 | } 20 | publisher?: { 21 | username: string 22 | email: string 23 | } 24 | maintainers?: { 25 | username: string 26 | email: string 27 | }[] 28 | } 29 | 30 | export interface NpmPackageInfo { 31 | package: PackageInfo 32 | score: { 33 | final: number 34 | detail: { 35 | quality: number 36 | popularity: number 37 | maintenance: number 38 | } 39 | } 40 | searchScore: number 41 | } 42 | 43 | let controller: AbortController | null = null 44 | 45 | export const search = async ( 46 | query: string, 47 | page = 10, 48 | ) => { 49 | controller?.abort() 50 | controller = new AbortController() 51 | const signal = controller.signal 52 | const res = await $fetch(`http://registry.npmjs.com/-/v1/search?text=${query}&from=${page}`, { signal }).catch(() => { 53 | return { count: 0 } 54 | }) 55 | if (res.count <= 0) 56 | return { query, hits: [] } 57 | 58 | return { query, hits: res.objects } 59 | } 60 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './search' 2 | -------------------------------------------------------------------------------- /src/store/search.ts: -------------------------------------------------------------------------------- 1 | import { ref } from '@vue/runtime-core' 2 | import { defineStore } from 'pinia' 3 | import { search as algoliaSearch } from '@/services/algolia' 4 | import { search as npmSearch } from '@/services/npm-registry' 5 | import type { PackageInfo } from '@/services/algolia' 6 | import type { NpmPackageInfo } from '@/services/npm-registry' 7 | 8 | interface DepsInfo { 9 | name: string 10 | version: string 11 | } 12 | 13 | // for algolia registry 14 | function normalizePackages(data: PackageInfo[]) { 15 | const value = data.map((item) => { 16 | const versions = item.versions as unknown as Record 17 | const normalizedVersions = Object.keys(versions).sort((a, b) => new Date(versions[b]).getTime() - new Date(versions[a]).getTime()) 18 | return { 19 | ...item, 20 | downloads: item.humanDownloadsLast30Days, 21 | author: item.owner.name, 22 | versions: [...new Set([item.version, ...normalizedVersions])], 23 | versionIndex: 0, 24 | activeVersion: item.version, 25 | repoLink: item.repository?.url, 26 | authorLink: item.owner?.link, 27 | } 28 | }) 29 | return value 30 | } 31 | 32 | // for npm registry 33 | function normalizeNpmPackages(data: NpmPackageInfo[]) { 34 | const value = data.map(({ package: item }) => { 35 | return { 36 | ...item, 37 | downloads: '---', 38 | humanDownloadsLast30Days: '', 39 | descriptions: item.description, 40 | author: item.author?.name ?? '', 41 | versions: [item.version], 42 | versionIndex: 0, 43 | activeVersion: item.version, 44 | repoLink: item.links?.repository, 45 | authorLink: item.author?.url, 46 | owner: { 47 | link: item.author?.url, 48 | name: item.author?.name, 49 | }, 50 | repository: { 51 | url: item.links?.repository, 52 | }, 53 | } 54 | }) as PackageInfo[] 55 | return value 56 | } 57 | 58 | export const useSearchStore = defineStore('search', () => { 59 | const page = ref(0) 60 | const keyword = ref('') 61 | const packages = ref([]) 62 | const searchRegistry = ref<'algolia' | 'npm'>('algolia') 63 | 64 | async function search(k: string, p = 0) { 65 | page.value = p 66 | if (k === '') { 67 | packages.value = [] 68 | return 69 | } 70 | const request = { 71 | algolia: algoliaSearch, 72 | npm: npmSearch, 73 | }[searchRegistry.value] 74 | 75 | const normalize = { 76 | algolia: normalizePackages, 77 | npm: normalizeNpmPackages, 78 | }[searchRegistry.value] 79 | 80 | const result = await request(k, p).catch((e) => { 81 | setTimeout(() => { 82 | console.log(e) 83 | }, 2000) 84 | return { query: null, hits: [] } 85 | }) 86 | 87 | if (result.query === keyword.value) { 88 | const normalizedResult = normalize(result.hits) 89 | packages.value = page.value === 0 ? normalizedResult : [...packages.value, ...normalizedResult] 90 | } 91 | } 92 | 93 | function toggleRegistry(source: 'algolia' | 'npm') { 94 | searchRegistry.value = source 95 | } 96 | 97 | watch(keyword, () => { 98 | search(keyword.value) 99 | }) 100 | 101 | return { keyword, packages, page, search, searchRegistry, toggleRegistry } 102 | }) 103 | 104 | export const useDepsStore = defineStore('deps', () => { 105 | const deps = ref([]) 106 | const devDeps = ref([]) 107 | const add = (dep: DepsInfo, isDev = false) => { 108 | if (isDev) 109 | devDeps.value = [...devDeps.value, dep] 110 | else 111 | deps.value = [...deps.value, dep] 112 | } 113 | const remove = (name: string, isDev = false) => { 114 | if (isDev) 115 | devDeps.value = devDeps.value.filter(item => item.name !== name) 116 | else 117 | deps.value = deps.value.filter(item => item.name !== name) 118 | } 119 | const has = (name: string, isDev = false) => { 120 | if (isDev) 121 | return devDeps.value.some(item => item.name === name) 122 | else 123 | return deps.value.some(item => item.name === name) 124 | } 125 | const normalize = (deps: DepsInfo[]) => { 126 | return deps.map(item => `${item.name}@${item.version}`) 127 | } 128 | const reset = () => { 129 | deps.value = [] 130 | devDeps.value = [] 131 | } 132 | return { deps, devDeps, add, remove, has, normalize, reset } 133 | }) 134 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "jsx": "preserve", 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "noEmit": true, 16 | "allowSyntheticDefaultImports": true, 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | }, 22 | "types": [ 23 | "vite/client" 24 | ] 25 | }, 26 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue", "*.d.ts"], 27 | "references": [{ "path": "./tsconfig.node.json" }] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true, 7 | "resolveJsonModule": true 8 | }, 9 | "include": ["vite.config.ts", "package.json"] 10 | } 11 | -------------------------------------------------------------------------------- /vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig } from 'vite' 3 | import VueTermui from 'vite-plugin-vue-termui' 4 | import pkg from './package.json' 5 | 6 | const deps = Object.keys(pkg.dependencies) 7 | 8 | export default defineConfig({ 9 | define: { 10 | __DEV__: JSON.stringify(!(process.env.NODE_ENV === 'production')), 11 | }, 12 | 13 | resolve: { 14 | alias: [ 15 | { find: '@', replacement: resolve(__dirname, 'src') }, 16 | ], 17 | mainFields: process.env.NODE_ENV === 'production' ? ['module', 'main'] : ['main', 'module'], 18 | }, 19 | 20 | plugins: [ 21 | VueTermui(), 22 | ], 23 | 24 | build: { 25 | rollupOptions: { 26 | external: deps, 27 | }, 28 | }, 29 | 30 | }) 31 | -------------------------------------------------------------------------------- /vtui-npm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webfansplz/vtui-npm/ff270786534c64bb8a79f4220e1ed1f933c66279/vtui-npm.gif --------------------------------------------------------------------------------