feat(completion): print completion code to stdout (#7597)

* feat(completion): generate-completion

close #3083

* feat: better error message

* test: generate-completion

* feat(completion): add powershell

* chore(deps): update @pnpm/tabtab to 0.3.0

* switch to provided type declarations
* fix typings
* update tests
* update bundle scripts

* refactor: remove unnecessary `??`

* refactor: replace a type def with provided types

* chore(deps): update @pnpm/tabtab to 0.4.0

* feat(cli): rename completion command

* chore(deps): update @pnpm/tabtab to 0.4.1

* refactor: use tabtab's new features

* fix: pass shell

* chore(deps): update @pnpm/tabtab to 0.5.0

* chore(deps): update @pnpm/tabtab to 0.5.1

* fix: remove unused import

* refactor: move completion to plugins

* feat: remove `{install,uninstall}-completion`

Just `pnpm completion` is enough

* test: fix

* refactor: direct import

* refactor: move tests to next to the lib

* refactor: merge 2 packages into 1

* fix: update changeset and remove install-completion
This commit is contained in:
Khải
2024-02-07 05:18:17 +07:00
committed by GitHub
parent 985381c091
commit 004addf63e
30 changed files with 379 additions and 104 deletions

View File

@@ -0,0 +1,5 @@
---
"pnpm": major
---
A new command added for printing completion code to the console: `pnpm completion [shell]`. The old command that modified the user's shell dotfiles has been removed [#3083](https://github.com/pnpm/pnpm/issues/3083).

View File

@@ -0,0 +1,5 @@
---
"pnpm": minor
---
PowerShell completion support added [#7597](https://github.com/pnpm/pnpm/pull/7597).

View File

@@ -0,0 +1,5 @@
---
"@pnpm/plugin-commands-completion": major
---
Initial release.

View File

@@ -201,7 +201,7 @@ async function updateManifest (workspaceDir: string, manifest: ProjectManifest,
}
scripts.compile += ' && rimraf dist bin/nodes && pnpm run bundle \
&& shx cp -r node-gyp-bin dist/node-gyp-bin \
&& shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts \
&& shx cp -r node_modules/@pnpm/tabtab/lib/templates dist/templates \
&& shx cp -r node_modules/ps-list/vendor dist/vendor \
&& shx cp pnpmrc dist/pnpmrc'
} else {

View File

@@ -1,9 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module '@pnpm/tabtab' {
const anything: any
export = anything
}
declare module 'hyperdrive-schemas' {
const anything: any
export = anything

View File

@@ -28,6 +28,9 @@
},
"homepage": "https://github.com/pnpm/pnpm/blob/main/cli/command#readme",
"funding": "https://opencollective.com/pnpm",
"dependencies": {
"@pnpm/tabtab": "^0.5.2"
},
"devDependencies": {
"@pnpm/command": "workspace:*"
},

View File

@@ -1,6 +1,6 @@
export interface Completion { name: string, description?: string }
import { type CompletionItem } from '@pnpm/tabtab'
export type CompletionFunc = (
options: Record<string, unknown>,
params: string[]
) => Promise<Completion[]>
) => Promise<CompletionItem[]>

View File

@@ -0,0 +1 @@
module.exports = require('../../jest-with-registry.config.js')

View File

@@ -0,0 +1,52 @@
{
"name": "@pnpm/plugin-commands-completion",
"version": "0.0.0",
"description": "Commands for shell completions",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"!*.map"
],
"engines": {
"node": ">=18.12"
},
"scripts": {
"start": "tsc --watch",
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
"_test": "jest",
"test": "pnpm run compile && pnpm run _test",
"prepublishOnly": "pnpm run compile",
"compile": "tsc --build && pnpm run lint --fix"
},
"repository": "https://github.com/pnpm/pnpm/blob/main/completion/plugin-commands-completion",
"license": "MIT",
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"devDependencies": {
"@pnpm/plugin-commands-completion": "workspace:*",
"@types/ramda": "0.28.20"
},
"dependencies": {
"@pnpm/cli-utils": "workspace:^",
"@pnpm/command": "workspace:^",
"@pnpm/error": "workspace:^",
"@pnpm/find-workspace-dir": "workspace:^",
"@pnpm/nopt": "^0.2.1",
"@pnpm/parse-cli-args": "workspace:^",
"@pnpm/tabtab": "^0.5.2",
"@pnpm/workspace.find-packages": "workspace:^",
"ramda": "npm:@pnpm/ramda@0.28.1",
"render-help": "^1.0.3",
"split-cmd": "^1.0.1"
},
"funding": "https://opencollective.com/pnpm",
"exports": {
".": "./lib/index.js"
},
"keywords": [
"pnpm9"
],
"homepage": "https://github.com/pnpm/pnpm/blob/main/completion/plugin-commands-completion#readme"
}

View File

@@ -1,4 +1,4 @@
import { complete } from '../src/cmd/complete'
import { complete } from './complete'
test('complete an option value', async () => {
const completions = await complete(
@@ -12,6 +12,7 @@ test('complete an option value', async () => {
initialCompletion: () => [],
shorthandsByCommandName: {},
universalOptionsTypes: {},
universalShorthands: {},
},
{
cmd: 'install',
@@ -42,6 +43,7 @@ test('complete a command', async () => {
universalOptionsTypes: {
filter: String,
},
universalShorthands: {},
}
expect(
await complete(ctx,
@@ -108,6 +110,7 @@ test('if command completion fails, return empty array', async () => {
universalOptionsTypes: {
filter: String,
},
universalShorthands: {},
},
{
cmd: 'run',
@@ -134,6 +137,7 @@ test('initial completion', async () => {
universalOptionsTypes: {
filter: String,
},
universalShorthands: {},
}
expect(
await complete(ctx,
@@ -193,6 +197,7 @@ test('suggest no completions for after --version', async () => {
],
shorthandsByCommandName: {},
universalOptionsTypes: {},
universalShorthands: {},
},
{
cmd: null,

View File

@@ -1,17 +1,18 @@
import { type Completion, type CompletionFunc } from '@pnpm/command'
import { type CompletionItem } from '@pnpm/tabtab'
import { type CompletionFunc } from '@pnpm/command'
import { findWorkspaceDir } from '@pnpm/find-workspace-dir'
import { findWorkspacePackages } from '@pnpm/workspace.find-packages'
import { getOptionCompletions } from '../getOptionType'
import { optionTypesToCompletions } from '../optionTypesToCompletions'
import { shorthands as universalShorthands } from '../shorthands'
import { getOptionCompletions } from './getOptionType'
import { optionTypesToCompletions } from './optionTypesToCompletions'
export async function complete (
ctx: {
cliOptionsTypesByCommandName: Record<string, () => Record<string, unknown>>
completionByCommandName: Record<string, CompletionFunc>
initialCompletion: () => Completion[]
initialCompletion: () => CompletionItem[]
shorthandsByCommandName: Record<string, Record<string, string | string[]>>
universalOptionsTypes: Record<string, unknown>
universalShorthands: Record<string, string>
},
input: {
params: string[]
@@ -20,7 +21,7 @@ export async function complete (
lastOption: string | null
options: Record<string, unknown>
}
) {
): Promise<CompletionItem[]> {
if (input.options.version) return []
const optionTypes = {
...ctx.universalOptionsTypes,
@@ -39,13 +40,13 @@ export async function complete (
},
})
return allProjects
.filter(({ manifest }) => manifest.name)
.map(({ manifest }) => ({ name: manifest.name }))
.filter((item): item is CompletionItem => !!item.name)
} else if (input.lastOption) {
const optionCompletions = getOptionCompletions(
optionTypes as any, // eslint-disable-line
{
...universalShorthands,
...ctx.universalShorthands,
...(input.cmd ? ctx.shorthandsByCommandName[input.cmd] : {}),
},
input.lastOption
@@ -55,7 +56,7 @@ export async function complete (
}
}
}
let completions: Completion[] = []
let completions: CompletionItem[] = []
if (input.currentTypedWordType !== 'option') {
if (!input.cmd || input.currentTypedWordType === 'value' && !ctx.completionByCommandName[input.cmd]) {
completions = ctx.initialCompletion()

View File

@@ -1,23 +1,28 @@
import { type Completion, type CompletionFunc } from '@pnpm/command'
import { type CompletionItem, getShellFromEnv } from '@pnpm/tabtab'
import { type CompletionFunc } from '@pnpm/command'
import { split as splitCmd } from 'split-cmd'
import tabtab from '@pnpm/tabtab'
import {
currentTypedWordType,
getLastOption,
} from '../getOptionType'
import { parseCliArgs } from '../parseCliArgs'
} from './getOptionType'
import { type ParsedCliArgs } from '@pnpm/parse-cli-args'
import { complete } from './complete'
export function createCompletion (
export function createCompletionServer (
opts: {
cliOptionsTypesByCommandName: Record<string, () => Record<string, unknown>>
completionByCommandName: Record<string, CompletionFunc>
initialCompletion: () => Completion[]
initialCompletion: () => CompletionItem[]
shorthandsByCommandName: Record<string, Record<string, string | string[]>>
parseCliArgs: (args: string[]) => Promise<ParsedCliArgs>
universalOptionsTypes: Record<string, unknown>
universalShorthands: Record<string, string>
}
) {
return async () => {
const shell = getShellFromEnv(process.env)
const env = tabtab.parseEnv(process.env)
if (!env.complete) return
@@ -27,8 +32,8 @@ export function createCompletion (
const inputArgv = splitCmd(finishedArgv).slice(1)
// We cannot autocomplete what a user types after "pnpm test --"
if (inputArgv.includes('--')) return
const { params, options, cmd } = await parseCliArgs(inputArgv)
return tabtab.log(
const { params, options, cmd } = await opts.parseCliArgs(inputArgv)
tabtab.log(
await complete(
opts,
{
@@ -38,7 +43,8 @@ export function createCompletion (
options,
params,
}
)
),
shell
)
}
}

View File

@@ -0,0 +1,34 @@
import renderHelp from 'render-help'
import { docsUrl } from '@pnpm/cli-utils'
import { getCompletionScript, SUPPORTED_SHELLS } from '@pnpm/tabtab'
import { getShellFromParams } from './getShell'
export const commandNames = ['completion']
export const rcOptionsTypes = () => ({})
export const cliOptionsTypes = () => ({})
export function help () {
return renderHelp({
description: 'Print shell completion code to stdout',
url: docsUrl('completion'),
usages: SUPPORTED_SHELLS.map(shell => `pnpm completion ${shell}`),
})
}
export interface Context {
readonly log: (output: string) => void
}
export function createCompletionGenerator (ctx: Context) {
return async function handler (_opts: unknown, params: string[]): Promise<void> {
const shell = getShellFromParams(params)
const output = await getCompletionScript({ name: 'pnpm', completer: 'pnpm', shell })
ctx.log(output)
}
}
export const handler = createCompletionGenerator({
log: console.log,
})

View File

@@ -2,7 +2,7 @@ import {
currentTypedWordType,
getLastOption,
getOptionCompletions,
} from '../src/getOptionType'
} from './getOptionType'
const TYPES = {
color: ['red', 'blue', Array],

View File

@@ -0,0 +1,37 @@
import { getShellFromString, getShellFromParams } from './getShell'
test('getShellFromString errors on undefined', () => {
expect(() => getShellFromString()).toThrow('`pnpm completion` requires a shell name')
})
test('getShellFromString errors on empty input', () => {
expect(() => getShellFromString('')).toThrow('`pnpm completion` requires a shell name')
})
test('getShellFromString errors on white space', () => {
expect(() => getShellFromString(' ')).toThrow('`pnpm completion` requires a shell name')
})
test('getShellFromString errors on unsupported shell', () => {
expect(() => getShellFromString('weird-shell-nobody-uses')).toThrow("'weird-shell-nobody-uses' is not supported")
})
test('getShellFromString returns supported shell as-is', () => {
expect(getShellFromString('bash')).toBe('bash')
})
test('getShellFromString trims whitespaces on support shell', () => {
expect(getShellFromString(' bash\n')).toBe('bash')
})
test('getShellFromParams errors on empty input', () => {
expect(() => getShellFromParams([])).toThrow('`pnpm completion` requires a shell name')
})
test('getShellFromParams errors on redundant parameters', () => {
expect(() => getShellFromParams(['bash', 'zsh', 'fish', 'pwsh'])).toThrow('The 3 parameters after shell is not necessary')
})
test('getShellFromParams returns supported shell', () => {
expect(getShellFromParams(['bash'])).toBe('bash')
})

View File

@@ -0,0 +1,28 @@
import { PnpmError } from '@pnpm/error'
import { isShellSupported, SUPPORTED_SHELLS, type SupportedShell } from '@pnpm/tabtab'
export function getShellFromString (shell?: string): SupportedShell {
shell = shell?.trim()
if (!shell) {
throw new PnpmError('MISSING_SHELL_NAME', '`pnpm completion` requires a shell name')
}
if (!isShellSupported(shell)) {
throw new PnpmError('UNSUPPORTED_SHELL', `'${shell}' is not supported`, {
hint: `Supported shells are: ${SUPPORTED_SHELLS.join(', ')}`,
})
}
return shell
}
export function getShellFromParams (params: string[]): SupportedShell {
const [shell, ...rest] = params
if (rest.length) {
throw new PnpmError('REDUNDANT_PARAMETERS', `The ${rest.length} parameters after shell is not necessary`)
}
return getShellFromString(shell)
}

View File

@@ -0,0 +1,2 @@
export * as generateCompletion from './generateCompletion'
export { createCompletionServer } from './completionServer'

View File

@@ -0,0 +1,14 @@
import { optionTypesToCompletions } from './optionTypesToCompletions'
test('optionTypesToCompletions', () => {
expect(optionTypesToCompletions({
number: Number,
string: String,
boolean: Boolean,
})).toStrictEqual([
{ name: '--number' },
{ name: '--string' },
{ name: '--boolean' },
{ name: '--no-boolean' },
])
})

View File

@@ -1,7 +1,7 @@
import { type Completion } from '@pnpm/command'
import { type CompletionItem } from '@pnpm/tabtab'
export function optionTypesToCompletions (optionTypes: Record<string, unknown>) {
const completions: Completion[] = []
const completions: CompletionItem[] = []
for (const [name, typeObj] of Object.entries(optionTypes)) {
if (typeObj === Boolean) {
completions.push({ name: `--${name}` })

View File

@@ -0,0 +1,48 @@
import { SUPPORTED_SHELLS } from '@pnpm/tabtab'
import { generateCompletion } from '@pnpm/plugin-commands-completion'
function createHandler () {
const log = jest.fn()
const handler = generateCompletion.createCompletionGenerator({ log })
return { log, handler }
}
test('pnpm completion requires the shell argument', async () => {
const { log, handler } = createHandler()
const promise = handler({}, [])
await expect(promise).rejects.toMatchObject({
code: 'ERR_PNPM_MISSING_SHELL_NAME',
message: '`pnpm completion` requires a shell name',
})
expect(log).not.toHaveBeenCalled()
})
test('pnpm completion errors on unsupported shell', async () => {
const { log, handler } = createHandler()
const promise = handler({}, ['weird-shell-nobody-uses'])
await expect(promise).rejects.toMatchObject({
code: 'ERR_PNPM_UNSUPPORTED_SHELL',
message: '\'weird-shell-nobody-uses\' is not supported',
})
expect(log).not.toHaveBeenCalled()
})
test('pnpm completion errors on redundant parameters', async () => {
const { log, handler } = createHandler()
const promise = handler({}, ['bash', 'fish', 'pwsh', 'zsh'])
await expect(promise).rejects.toMatchObject({
code: 'ERR_PNPM_REDUNDANT_PARAMETERS',
message: 'The 3 parameters after shell is not necessary',
})
expect(log).not.toHaveBeenCalled()
})
for (const shell of SUPPORTED_SHELLS) {
test(`pnpm completion ${shell}`, async () => {
const { log, handler } = createHandler()
await handler({}, [shell])
expect(log).toHaveBeenCalledWith(expect.stringContaining('###-begin-pnpm-completion-###'))
expect(log).toHaveBeenCalledWith(expect.stringContaining('###-end-pnpm-completion-###'))
expect(log).toHaveBeenCalledTimes(1)
})
}

View File

@@ -0,0 +1,32 @@
{
"extends": "@pnpm/tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": [
"src/**/*.ts",
"../../__typings__/**/*.d.ts"
],
"references": [
{
"path": "../../cli/cli-utils"
},
{
"path": "../../cli/command"
},
{
"path": "../../cli/parse-cli-args"
},
{
"path": "../../packages/error"
},
{
"path": "../../workspace/find-packages"
},
{
"path": "../../workspace/find-workspace-dir"
}
],
"composite": true
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
"test/**/*.ts",
"../../__typings__/**/*.d.ts"
]
}

96
pnpm-lock.yaml generated
View File

@@ -430,6 +430,10 @@ importers:
version: 0.28.20
cli/command:
dependencies:
'@pnpm/tabtab':
specifier: ^0.5.2
version: 0.5.2
devDependencies:
'@pnpm/command':
specifier: workspace:*
@@ -554,6 +558,49 @@ importers:
specifier: ^1.0.1
version: 1.0.1
completion/plugin-commands-completion:
dependencies:
'@pnpm/cli-utils':
specifier: workspace:^
version: link:../../cli/cli-utils
'@pnpm/command':
specifier: workspace:^
version: link:../../cli/command
'@pnpm/error':
specifier: workspace:^
version: link:../../packages/error
'@pnpm/find-workspace-dir':
specifier: workspace:^
version: link:../../workspace/find-workspace-dir
'@pnpm/nopt':
specifier: ^0.2.1
version: 0.2.1
'@pnpm/parse-cli-args':
specifier: workspace:^
version: link:../../cli/parse-cli-args
'@pnpm/tabtab':
specifier: ^0.5.2
version: 0.5.2
'@pnpm/workspace.find-packages':
specifier: workspace:^
version: link:../../workspace/find-packages
ramda:
specifier: npm:@pnpm/ramda@0.28.1
version: /@pnpm/ramda@0.28.1
render-help:
specifier: ^1.0.3
version: 1.0.3
split-cmd:
specifier: ^1.0.1
version: 1.0.1
devDependencies:
'@pnpm/plugin-commands-completion':
specifier: workspace:*
version: 'link:'
'@types/ramda':
specifier: 0.28.20
version: 0.28.20
config/config:
dependencies:
'@pnpm/config.env-replace':
@@ -4438,6 +4485,9 @@ importers:
'@pnpm/plugin-commands-audit':
specifier: workspace:*
version: link:../lockfile/plugin-commands-audit
'@pnpm/plugin-commands-completion':
specifier: workspace:*
version: link:../completion/plugin-commands-completion
'@pnpm/plugin-commands-config':
specifier: workspace:*
version: link:../config/plugin-commands-config
@@ -4505,8 +4555,8 @@ importers:
specifier: workspace:*
version: link:../exec/run-npm
'@pnpm/tabtab':
specifier: ^0.1.2
version: 0.1.2
specifier: ^0.5.2
version: 0.5.2
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../__utils__/test-fixtures
@@ -6620,7 +6670,6 @@ packages:
/@babel/parser@7.18.4(@babel/types@7.19.0):
resolution: {integrity: sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==}
engines: {node: '>=6.0.0'}
hasBin: true
peerDependencies:
'@babel/types': '*'
dependencies:
@@ -6630,7 +6679,6 @@ packages:
/@babel/parser@7.23.6(@babel/types@7.23.3):
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
engines: {node: '>=6.0.0'}
hasBin: true
peerDependencies:
'@babel/types': '*'
dependencies:
@@ -6640,7 +6688,6 @@ packages:
/@babel/parser@7.23.6(@babel/types@7.23.6):
resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
engines: {node: '>=6.0.0'}
hasBin: true
peerDependencies:
'@babel/types': '*'
dependencies:
@@ -7803,7 +7850,6 @@ packages:
/@gar/promisify@1.1.3:
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
requiresBuild: true
dev: false
/@gwhitney/detect-indent@7.0.1:
@@ -8680,7 +8726,6 @@ packages:
/@pnpm/registry-mock@3.20.0(typanion@3.14.0):
resolution: {integrity: sha512-JKSBYTZn/Nk+2HytdYdz9avy53WWqh4QhP6uE9ImIFw4aw5vRUWq6aQQn6xkm8I/wzPogOpDaEuAfFXhhsj3uA==}
engines: {node: '>=10.13'}
hasBin: true
dependencies:
anonymous-npm-registry-client: 0.2.0
execa: 5.1.1
@@ -8737,9 +8782,9 @@ packages:
'@pnpm/types': 9.1.0
dev: true
/@pnpm/tabtab@0.1.2:
resolution: {integrity: sha512-AYg+Vir0D0rigS9/O7M+v80J4WpTbl68pElNIQ9K5IYxfJ5h3Zk0NJI7bVciV/xbHj3SalmaE6Il8GbPOlKo7g==}
engines: {node: '>=10'}
/@pnpm/tabtab@0.5.2:
resolution: {integrity: sha512-ihbP3cE9ejh7EuR4B9TC0syGPlIml6AQWNPZnGSGAMOAax2+4jpCuCNwD2c+Iw/0V1bjvGh9bhs/Lf49EDMuaQ==}
engines: {node: '>=18'}
dependencies:
debug: 4.3.4
enquirer: 2.4.1
@@ -8747,7 +8792,6 @@ packages:
untildify: 4.0.0
transitivePeerDependencies:
- supports-color
dev: true
/@pnpm/text.comments-parser@2.0.0:
resolution: {integrity: sha512-DRWtTmmxQQtuWHf1xPt9bqzCSq8d0MQF5x1kdpCDMLd7xk3nP4To2/OGkPrb8MKbrWsgCNDwXyKCFlEKrAg7fg==}
@@ -9638,7 +9682,6 @@ packages:
/@yarnpkg/shell@4.0.0(typanion@3.14.0):
resolution: {integrity: sha512-Yk2gyiQvsoee/jXP9q0jMl412Nx27LYu+P1O4DHuxeutL9qtd6t3Ktuv+zZmOzFc6gMQ7+/6mQFPo3/LlXZM3w==}
engines: {node: '>=18.12.0'}
hasBin: true
dependencies:
'@yarnpkg/fslib': 3.0.1
'@yarnpkg/parsers': 3.0.0
@@ -9972,7 +10015,6 @@ packages:
/are-we-there-yet@3.0.1:
resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
requiresBuild: true
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
@@ -10686,7 +10728,6 @@ packages:
/code-point-at@1.1.0:
resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==}
engines: {node: '>=0.10.0'}
requiresBuild: true
/collect-v8-coverage@1.0.2:
resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==}
@@ -10711,8 +10752,6 @@ packages:
/color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
requiresBuild: true
dev: false
/colors@0.6.2:
@@ -11516,7 +11555,6 @@ packages:
/err-code@2.0.3:
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
requiresBuild: true
dev: false
/error-ex@1.3.2:
@@ -12466,7 +12504,6 @@ packages:
/gauge@4.0.4:
resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
requiresBuild: true
dependencies:
aproba: 2.0.0
color-support: 1.1.3
@@ -12616,7 +12653,6 @@ packages:
/glob@10.3.10:
resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
requiresBuild: true
dependencies:
foreground-child: 3.1.1
@@ -13197,7 +13233,6 @@ packages:
/is-fullwidth-code-point@1.0.0:
resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies:
number-is-nan: 1.0.1
@@ -13514,7 +13549,6 @@ packages:
/jest-cli@29.7.0(@babel/types@7.23.3)(@types/node@18.14.6)(ts-node@10.9.2):
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
@@ -13870,7 +13904,6 @@ packages:
/jest@29.7.0(@babel/types@7.23.3)(@types/node@18.14.6)(ts-node@10.9.2):
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
peerDependenciesMeta:
@@ -14592,7 +14625,6 @@ packages:
/minimatch@9.0.3:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
requiresBuild: true
dependencies:
brace-expansion: 2.0.1
dev: false
@@ -14707,7 +14739,6 @@ packages:
/mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
dependencies:
minimist: 1.2.8
@@ -14770,7 +14801,6 @@ packages:
/ncp@2.0.0:
resolution: {integrity: sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==}
hasBin: true
/ndjson@2.0.0:
resolution: {integrity: sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==}
@@ -14882,7 +14912,6 @@ packages:
/node-gyp@10.0.1:
resolution: {integrity: sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==}
engines: {node: ^16.14.0 || >=18.0.0}
hasBin: true
requiresBuild: true
dependencies:
env-paths: 2.2.1
@@ -14943,7 +14972,6 @@ packages:
/nopt@7.2.0:
resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
hasBin: true
requiresBuild: true
dependencies:
abbrev: 2.0.0
@@ -15072,7 +15100,6 @@ packages:
/number-is-nan@1.0.1:
resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==}
engines: {node: '>=0.10.0'}
requiresBuild: true
/oauth-sign@0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
@@ -15535,7 +15562,6 @@ packages:
/pkg@5.8.1(patch_hash=pp6fkuhwkrqq7cjcj7uqpf37e4):
resolution: {integrity: sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==}
hasBin: true
peerDependencies:
node-notifier: '>=9.0.1'
peerDependenciesMeta:
@@ -16072,7 +16098,6 @@ packages:
/request@2.88.2:
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
engines: {node: '>= 6'}
deprecated: request has been deprecated, see https://github.com/request/request/issues/3142
dependencies:
aws-sign2: 0.7.0
aws4: 1.12.0
@@ -16211,7 +16236,6 @@ packages:
/rimraf@2.4.5:
resolution: {integrity: sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==}
hasBin: true
dependencies:
glob: 6.0.4
@@ -16655,7 +16679,6 @@ packages:
/split-cmd@1.0.1:
resolution: {integrity: sha512-HnyUFgtv7yNcGKK1+tO1O2eyXwEVnXqQzjshvroHsCu4M9fxS8lJ3bpW9XfD8YG0SdxW6hXNHdT/VFAxtNY1yw==}
dev: true
/split2@3.2.2:
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
@@ -16835,7 +16858,6 @@ packages:
/strip-ansi@3.0.1:
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies:
ansi-regex: 2.1.1
@@ -17163,7 +17185,6 @@ packages:
/ts-jest@29.1.1(@babel/core@7.23.3)(jest@29.7.0)(typescript@5.3.3):
resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
hasBin: true
peerDependencies:
'@babel/core': '>=7.0.0-beta.0 <8'
'@jest/types': ^29.0.0
@@ -17196,7 +17217,6 @@ packages:
/ts-node@10.9.2(@types/node@18.14.6)(typescript@5.3.3):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
@@ -17227,7 +17247,6 @@ packages:
/ts-node@10.9.2(@types/node@20.5.1)(typescript@5.3.3):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
@@ -17473,7 +17492,6 @@ packages:
/unique-slug@3.0.0:
resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
requiresBuild: true
dependencies:
imurmurhash: 0.1.4
dev: false
@@ -17536,11 +17554,9 @@ packages:
/untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
dev: true
/update-browserslist-db@1.0.13(browserslist@4.22.2):
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
@@ -17573,8 +17589,6 @@ packages:
/uuid@3.4.0:
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
hasBin: true
/uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}

View File

@@ -4,6 +4,7 @@ packages:
- __utils__/*
- "!__utils__/build-artifacts"
- cli/*
- completion/*
- config/*
- crypto/*
- dedupe/*

View File

@@ -43,6 +43,7 @@
"@pnpm/parse-cli-args": "workspace:*",
"@pnpm/pick-registry-for-package": "workspace:*",
"@pnpm/plugin-commands-audit": "workspace:*",
"@pnpm/plugin-commands-completion": "workspace:*",
"@pnpm/plugin-commands-config": "workspace:*",
"@pnpm/plugin-commands-deploy": "workspace:*",
"@pnpm/plugin-commands-doctor": "workspace:*",
@@ -65,7 +66,7 @@
"@pnpm/read-project-manifest": "workspace:*",
"@pnpm/registry-mock": "3.20.0",
"@pnpm/run-npm": "workspace:*",
"@pnpm/tabtab": "^0.1.2",
"@pnpm/tabtab": "^0.5.2",
"@pnpm/test-fixtures": "workspace:*",
"@pnpm/test-ipc-server": "workspace:*",
"@pnpm/types": "workspace:*",
@@ -163,7 +164,7 @@
"prepublishOnly": "pnpm compile && npm cache clear --force && publish-packed --prune --npm-client=pnpm --dest=dist",
"postpublish": "publish-packed",
"_compile": "tsc --build",
"compile": "tsc --build && pnpm run lint --fix && rimraf dist bin/nodes && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts && shx cp -r node_modules/ps-list/vendor dist/vendor && shx cp pnpmrc dist/pnpmrc"
"compile": "tsc --build && pnpm run lint --fix && rimraf dist bin/nodes && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/templates dist/templates && shx cp -r node_modules/ps-list/vendor dist/vendor && shx cp pnpmrc dist/pnpmrc"
},
"publishConfig": {
"tag": "next-9",

View File

@@ -1,6 +1,7 @@
import { type CompletionFunc } from '@pnpm/command'
import { types as allTypes } from '@pnpm/config'
import { audit } from '@pnpm/plugin-commands-audit'
import { generateCompletion, createCompletionServer } from '@pnpm/plugin-commands-completion'
import { config, getCommand, setCommand } from '@pnpm/plugin-commands-config'
import { doctor } from '@pnpm/plugin-commands-doctor'
import { env } from '@pnpm/plugin-commands-env'
@@ -27,8 +28,9 @@ import { catFile, catIndex, findHash } from '@pnpm/plugin-commands-store-inspect
import { init } from '@pnpm/plugin-commands-init'
import pick from 'ramda/src/pick'
import { type PnpmOptions } from '../types'
import { shorthands as universalShorthands } from '../shorthands'
import { parseCliArgs } from '../parseCliArgs'
import * as bin from './bin'
import { createCompletion } from './completion'
import { createHelp } from './help'
import * as installTest from './installTest'
import * as recursive from './recursive'
@@ -117,6 +119,7 @@ const commands: CommandDefinition[] = [
env,
exec,
fetch,
generateCompletion,
importCommand,
init,
install,
@@ -190,12 +193,14 @@ for (let i = 0; i < commands.length; i++) {
}
handlerByCommandName.help = createHelp(helpByCommandName)
handlerByCommandName.completion = createCompletion({
handlerByCommandName['completion-server'] = createCompletionServer({
cliOptionsTypesByCommandName,
completionByCommandName,
initialCompletion,
shorthandsByCommandName,
universalOptionsTypes: GLOBAL_OPTIONS,
universalShorthands,
parseCliArgs,
})
function initialCompletion () {

View File

@@ -14,16 +14,6 @@ const argv = process.argv.slice(2)
console.log(version)
break
}
case 'install-completion': {
const { install: installCompletion } = await import('@pnpm/tabtab')
await installCompletion({ name: 'pnpm', completer: 'pnpm', shell: argv[1] })
return
}
case 'uninstall-completion': {
const { uninstall: uninstallCompletion } = await import('@pnpm/tabtab')
await uninstallCompletion({ name: 'pnpm' })
return
}
// commands that are passed through to npm:
case 'access':
case 'adduser':

View File

@@ -1,20 +0,0 @@
import { optionTypesToCompletions } from '../src/optionTypesToCompletions'
test('optionTypesToCompletions()', () => {
expect(
optionTypesToCompletions({
bar: String,
foo: Boolean,
})
).toStrictEqual([
{
name: '--bar',
},
{
name: '--foo',
},
{
name: '--no-foo',
},
])
})

View File

@@ -43,6 +43,9 @@
{
"path": "../cli/parse-cli-args"
},
{
"path": "../completion/plugin-commands-completion"
},
{
"path": "../config/config"
},