@oclif/plugin-search
Advanced tools
Comparing version 1.2.12-dev.0 to 1.2.12
import { Command } from '@oclif/core'; | ||
export default class Search extends Command { | ||
static description: string; | ||
static readonly flags: { | ||
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; | ||
}; | ||
static readonly strict = false; | ||
static summary: string; | ||
run(): Promise<unknown>; | ||
} |
@@ -7,32 +7,11 @@ /* | ||
*/ | ||
import search from '@inquirer/search'; | ||
import { Command, Flags, loadHelpClass, toConfiguredId, toStandardizedId, ux } from '@oclif/core'; | ||
import { bold } from 'ansis'; | ||
import clipboard from 'clipboardy'; | ||
import fuzzysort from 'fuzzysort'; | ||
import { got } from 'got'; | ||
import { Command, loadHelpClass, toConfiguredId, toStandardizedId, ux } from '@oclif/core'; | ||
import ansiEscapes from 'ansi-escapes'; | ||
import autocomplete from 'inquirer-autocomplete-standalone'; | ||
import readline from 'node:readline'; | ||
import open from 'open'; | ||
export default class Search extends Command { | ||
static description = 'Once you select a command, hit enter and it will show the help for that command.'; | ||
static flags = { | ||
action: Flags.string({ | ||
char: 'a', | ||
options: ['help', 'copy', 'doctor', 'source', 'npm'], | ||
summary: 'Action to take on the selected command', | ||
}), | ||
}; | ||
static strict = false; | ||
static summary = 'Search for a command.'; | ||
async run() { | ||
const { argv, flags } = await this.parse(Search); | ||
// Join the args together into a string so you can pass something like 'dep sta' to fuzzy search | ||
const args = argv.join(' '); | ||
const theme = { | ||
helpMode: 'never', | ||
prefix: ux.colorize('cyan', `Use ${ux.colorize('bold', '↑')} and ${ux.colorize('bold', '↓')} keys or type to search for a command.\nPress ${ux.colorize('bold', 'ENTER')} to view help. Press ${ux.colorize('bold', 'ESC')} to exit.\n\n`), | ||
style: { | ||
description: (desc) => `\n${ux.colorize('cyan', desc)}`, // Give the description a little extra padding | ||
}, | ||
}; | ||
this.log(ux.colorize('cyan', `Use ${ux.colorize('bold', '↑')} and ${ux.colorize('bold', '↓')} keys or type to search for a command.\nPress ${ux.colorize('bold', 'ENTER')} to view help. Press ${ux.colorize('bold', 'ESC')} to exit.\n`)); | ||
const commandChoices = this.config.commands | ||
@@ -50,33 +29,22 @@ .filter((c) => !c.hidden && !c.aliases.includes(c.id)) | ||
const pageSize = Math.floor(process.stdout.rows < 20 ? process.stdout.rows / 2 : 10); | ||
const commandPromise = search({ | ||
const commandPromise = autocomplete({ | ||
emptyText: 'Nothing found!', | ||
message: 'Search for a command', | ||
pageSize, | ||
async source(input) { | ||
// TODO: There is a bug here somewhere: | ||
// - pass an arg | ||
// - hit down arrow | ||
// - hit any other character with clear the input | ||
// - hitting delete will clear input, but keep the fuzzy results | ||
if (input === undefined && args) | ||
input = args; | ||
const results = fuzzysort.go(input, commandChoices, { all: true, key: 'name' }); | ||
return results.map((r) => ({ | ||
description: r.obj.description, | ||
name: r.highlight(bold.open, bold.close), | ||
value: r.obj.value, | ||
})); | ||
return input ? commandChoices.filter((c) => c.name.includes(input)) : commandChoices; | ||
}, | ||
// @ts-expect-error Not sure why this is complaining about the helpMode type | ||
theme, | ||
}, { clearPromptOnDone: true }); | ||
}); | ||
function cancel() { | ||
commandPromise.cancel(); | ||
// erase the list of commands | ||
process.stdout.write(ansiEscapes.eraseLines(pageSize + 3)); | ||
} | ||
readline.emitKeypressEvents(process.stdin); | ||
process.stdin.setRawMode(true); | ||
// If args were passed in, we "replay" the key presses to populate the search | ||
if (args) | ||
process.stdin.emit('data', args); | ||
// Allow the user to exit the search with the escape key or with ctrl+c | ||
process.stdin.on('keypress', (_, key) => { | ||
if ((key.name === 'escape' || (key.name === 'c' && key.ctrl)) && commandPromise) { | ||
commandPromise.cancel(); | ||
} | ||
if (key.name === 'escape') | ||
cancel(); | ||
if (key.name === 'c' && key.ctrl) | ||
cancel(); | ||
}); | ||
@@ -92,118 +60,6 @@ const command = await commandPromise | ||
return; | ||
// eslint-disable-next-line unicorn/consistent-function-scoping | ||
const getPluginDetails = (command) => { | ||
const commandId = toStandardizedId(command, this.config); | ||
const commandConfig = this.config.findCommand(commandId); | ||
const pluginName = commandConfig?.pluginName; | ||
if (!pluginName) | ||
this.error('Key `pluginName` not found in the config for this command.'); | ||
const commandPjson = this.config.plugins.get(pluginName)?.pjson; | ||
const homepage = commandPjson?.homepage; | ||
// TODO: add a check for homepage | ||
const commandVersion = commandPjson?.version; | ||
return { commandConfig, commandId, commandPjson, commandVersion, homepage, pluginName }; | ||
}; | ||
const getSourceUrl = async (homepage, commandId, commandVersion) => { | ||
if (!homepage) | ||
return; | ||
const commandToPath = `${commandId.replaceAll(':', '/')}.ts`; | ||
// TODO: do we need to take into account directory level index.ts command files? | ||
// TODO: talk to Mike about dynamically built command paths | ||
const urls = [ | ||
`/blob/${commandVersion}/src/commands/${commandToPath}`, | ||
`/blob/v${commandVersion}/src/commands/${commandToPath}`, | ||
`/blob/-/src/commands/${commandToPath}`, | ||
`/blob/main/src/commands/${commandToPath}`, | ||
`/blob/master/src/commands/${commandToPath}`, | ||
]; | ||
const responses = await Promise.all(urls.map((url) => got(`${homepage}${url}`, { throwHttpErrors: false }))); | ||
return responses.find((r) => r.statusCode === 200)?.url ?? undefined; | ||
}; | ||
const { commandId, commandVersion, homepage, pluginName } = getPluginDetails(command); | ||
const sourceUrl = await getSourceUrl(homepage, commandId, commandVersion); | ||
let actionPrompt; | ||
if (!flags.action) { | ||
const actions = [ | ||
{ | ||
description: 'Show the help text for this command', | ||
name: 'Show help', | ||
value: 'help', | ||
}, | ||
{ | ||
description: 'Copy the command to your clipboard', | ||
name: 'Copy command', | ||
value: 'copy', | ||
}, | ||
{ | ||
description: 'Copy the command to your clipboard for use with the doctor command', | ||
name: 'Copy command for doctor', | ||
value: 'doctor', | ||
}, | ||
{ | ||
description: 'Open the source code for this command on GitHub', | ||
disabled: sourceUrl ? false : '(Unable to resolve source code URL)', | ||
name: 'Go to source code', | ||
value: 'source', | ||
}, | ||
{ | ||
description: 'View the npm details for this package', | ||
name: 'View package on npm', | ||
value: 'npm', | ||
}, | ||
]; | ||
const actionPromise = search({ | ||
message: `Select an action for: ${ux.colorize('dim', '$ sf ' + command)}`, | ||
pageSize, | ||
async source(input) { | ||
const results = fuzzysort.go(input, actions, { all: true, key: 'name' }); | ||
return results.map((r) => ({ | ||
description: r.obj.description, | ||
disabled: r.obj.disabled, | ||
name: r.highlight(bold.open, bold.close), | ||
value: r.obj.value, | ||
})); | ||
}, | ||
// @ts-expect-error Not sure why this is complaining about the helpMode type | ||
theme, | ||
}, { clearPromptOnDone: true }); | ||
// Allow the user to exit the search with the escape key or with ctrl+c | ||
process.stdin.on('keypress', (_, key) => { | ||
if ((key.name === 'escape' || (key.name === 'c' && key.ctrl)) && actionPromise) { | ||
actionPromise.cancel(); | ||
} | ||
}); | ||
actionPrompt = await actionPromise | ||
.catch((error) => { | ||
if (error.message === 'Prompt was canceled') | ||
return; | ||
throw error; | ||
}) | ||
.then((result) => result); | ||
} | ||
switch (flags.action ?? actionPrompt) { | ||
case 'help': { | ||
const Help = await loadHelpClass(this.config); | ||
const help = new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions); | ||
return help.showHelp([toStandardizedId(command, this.config)]); | ||
} | ||
case 'copy': { | ||
clipboard.writeSync(`sf ${command} `); | ||
this.log(ux.colorize('green', 'Command copied to clipboard!')); | ||
break; | ||
} | ||
case 'doctor': { | ||
clipboard.writeSync(`sf doctor --command "${command}"`); | ||
this.log(ux.colorize('green', 'Command copied to clipboard!')); | ||
break; | ||
} | ||
case 'npm': { | ||
open(`https://www.npmjs.com/package/${pluginName}/v/${commandVersion}`); | ||
break; | ||
} | ||
case 'source': { | ||
open(sourceUrl); | ||
break; | ||
} | ||
} | ||
const Help = await loadHelpClass(this.config); | ||
const help = new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions); | ||
return help.showHelp([toStandardizedId(command, this.config)]); | ||
} | ||
} |
@@ -7,19 +7,3 @@ { | ||
"description": "Once you select a command, hit enter and it will show the help for that command.", | ||
"flags": { | ||
"action": { | ||
"char": "a", | ||
"name": "action", | ||
"summary": "Action to take on the selected command", | ||
"hasDynamicHelp": false, | ||
"multiple": false, | ||
"options": [ | ||
"help", | ||
"copy", | ||
"doctor", | ||
"source", | ||
"npm" | ||
], | ||
"type": "option" | ||
} | ||
}, | ||
"flags": {}, | ||
"hasDynamicHelp": false, | ||
@@ -31,3 +15,3 @@ "hiddenAliases": [], | ||
"pluginType": "core", | ||
"strict": false, | ||
"strict": true, | ||
"summary": "Search for a command.", | ||
@@ -47,3 +31,3 @@ "enableJsonFlag": false, | ||
}, | ||
"version": "1.2.12-dev.0" | ||
"version": "1.2.12" | ||
} |
{ | ||
"name": "@oclif/plugin-search", | ||
"version": "1.2.12-dev.0", | ||
"version": "1.2.12", | ||
"description": "A command for searching commands", | ||
@@ -8,9 +8,5 @@ "author": "Salesforce", | ||
"dependencies": { | ||
"@inquirer/search": "^2.0.1", | ||
"@oclif/core": "^4", | ||
"ansis": "^3.3.2", | ||
"clipboardy": "^4.0.0", | ||
"fuzzysort": "^3.0.2", | ||
"got": "^13.0.0", | ||
"open": "^10.1.0" | ||
"ansi-escapes": "^7.0.0", | ||
"inquirer-autocomplete-standalone": "^0.8.1" | ||
}, | ||
@@ -24,3 +20,3 @@ "devDependencies": { | ||
"@types/inquirer": "^8.2.10", | ||
"@types/mocha": "^10.0.8", | ||
"@types/mocha": "^10.0.9", | ||
"@types/node": "^18", | ||
@@ -31,3 +27,3 @@ "chai": "^4", | ||
"eslint-config-oclif": "^5.2.1", | ||
"eslint-config-oclif-typescript": "^3.1.11", | ||
"eslint-config-oclif-typescript": "^3.1.12", | ||
"eslint-config-prettier": "^9.1.0", | ||
@@ -37,3 +33,3 @@ "husky": "^9.1.6", | ||
"mocha": "^10.7.3", | ||
"oclif": "^4.15.3", | ||
"oclif": "^4.15.6", | ||
"prettier": "^3.3.3", | ||
@@ -40,0 +36,0 @@ "shx": "^0.3.3", |
@@ -25,3 +25,3 @@ # @oclif/plugin-search | ||
$ @oclif/plugin-search (--version) | ||
@oclif/plugin-search/1.2.12-dev.0 linux-x64 node-v20.17.0 | ||
@oclif/plugin-search/1.2.12 linux-x64 node-v20.17.0 | ||
$ @oclif/plugin-search --help [COMMAND] | ||
@@ -45,8 +45,4 @@ USAGE | ||
USAGE | ||
$ @oclif/plugin-search search [-a help|copy|doctor|source|npm] | ||
$ @oclif/plugin-search search | ||
FLAGS | ||
-a, --action=<option> Action to take on the selected command | ||
<options: help|copy|doctor|source|npm> | ||
DESCRIPTION | ||
@@ -58,3 +54,3 @@ Search for a command. | ||
_See code: [src/commands/search.ts](https://github.com/oclif/plugin-search/blob/v1.2.12-dev.0/src/commands/search.ts)_ | ||
_See code: [src/commands/search.ts](https://github.com/oclif/plugin-search/blob/v1.2.12/src/commands/search.ts)_ | ||
<!-- commandsstop --> | ||
@@ -61,0 +57,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
3
0
8303
99
58
+ Addedansi-escapes@^7.0.0
+ Added@inquirer/core@3.1.2(transitive)
+ Added@inquirer/type@1.5.5(transitive)
+ Added@types/mute-stream@0.0.1(transitive)
+ Added@types/node@20.17.10(transitive)
+ Addedansi-escapes@7.0.0(transitive)
+ Addedenvironment@1.1.0(transitive)
+ Addedescape-string-regexp@1.0.55.0.0(transitive)
+ Addedfigures@3.2.05.0.0(transitive)
+ Addedinquirer-autocomplete-standalone@0.8.1(transitive)
+ Addedis-unicode-supported@1.3.0(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedrun-async@3.0.0(transitive)
+ Addedundici-types@6.19.8(transitive)
- Removed@inquirer/search@^2.0.1
- Removedansis@^3.3.2
- Removedclipboardy@^4.0.0
- Removedfuzzysort@^3.0.2
- Removedgot@^13.0.0
- Removedopen@^10.1.0
- Removed@inquirer/core@9.2.1(transitive)
- Removed@inquirer/figures@1.0.8(transitive)
- Removed@inquirer/search@2.0.1(transitive)
- Removed@inquirer/type@2.0.0(transitive)
- Removed@sindresorhus/is@5.6.0(transitive)
- Removed@szmarczak/http-timer@5.0.1(transitive)
- Removed@types/http-cache-semantics@4.0.4(transitive)
- Removed@types/mute-stream@0.0.4(transitive)
- Removed@types/node@22.10.2(transitive)
- Removedbundle-name@4.1.0(transitive)
- Removedcacheable-lookup@7.0.0(transitive)
- Removedcacheable-request@10.2.14(transitive)
- Removedclipboardy@4.0.0(transitive)
- Removedcross-spawn@7.0.6(transitive)
- Removeddecompress-response@6.0.0(transitive)
- Removeddefault-browser@5.2.1(transitive)
- Removeddefault-browser-id@5.0.0(transitive)
- Removeddefer-to-connect@2.0.1(transitive)
- Removeddefine-lazy-prop@3.0.0(transitive)
- Removedexeca@8.0.1(transitive)
- Removedform-data-encoder@2.1.4(transitive)
- Removedfuzzysort@3.1.0(transitive)
- Removedget-stream@6.0.18.0.1(transitive)
- Removedgot@13.0.0(transitive)
- Removedhttp-cache-semantics@4.1.1(transitive)
- Removedhttp2-wrapper@2.2.1(transitive)
- Removedhuman-signals@5.0.0(transitive)
- Removedis-docker@3.0.0(transitive)
- Removedis-inside-container@1.0.0(transitive)
- Removedis-stream@3.0.0(transitive)
- Removedis-wsl@3.1.0(transitive)
- Removedis64bit@2.0.0(transitive)
- Removedisexe@2.0.0(transitive)
- Removedjson-buffer@3.0.1(transitive)
- Removedkeyv@4.5.4(transitive)
- Removedlowercase-keys@3.0.0(transitive)
- Removedmerge-stream@2.0.0(transitive)
- Removedmimic-fn@4.0.0(transitive)
- Removedmimic-response@3.1.04.0.0(transitive)
- Removednormalize-url@8.0.1(transitive)
- Removednpm-run-path@5.3.0(transitive)
- Removedonetime@6.0.0(transitive)
- Removedopen@10.1.0(transitive)
- Removedp-cancelable@3.0.0(transitive)
- Removedpath-key@3.1.14.0.0(transitive)
- Removedquick-lru@5.1.1(transitive)
- Removedresolve-alpn@1.2.1(transitive)
- Removedresponselike@3.0.0(transitive)
- Removedrun-applescript@7.0.0(transitive)
- Removedshebang-command@2.0.0(transitive)
- Removedshebang-regex@3.0.0(transitive)
- Removedsignal-exit@4.1.0(transitive)
- Removedstrip-final-newline@3.0.0(transitive)
- Removedsystem-architecture@0.1.0(transitive)
- Removedundici-types@6.20.0(transitive)
- Removedwhich@2.0.2(transitive)
- Removedyoctocolors-cjs@2.1.2(transitive)