diff --git a/src/ant.ts b/src/ant.ts index f8976b5c909e..99e72de90bc9 100644 --- a/src/ant.ts +++ b/src/ant.ts @@ -1,5 +1,5 @@ const tasksGenerator: Fig.Generator = { - script: "command ant -p | grep -i '^\\s' | tr -d ' '", + script: ["bash", "-c", "command ant -p | grep -i '^\\s' | tr -d ' '"], postProcess: (out) => out.split("\n").map((task) => ({ name: task, diff --git a/src/apt.ts b/src/apt.ts index d297075f3ebc..9af3741ca287 100644 --- a/src/apt.ts +++ b/src/apt.ts @@ -11,13 +11,18 @@ const packages: Fig.Generator = { if (finalToken.length === 0) { return []; } + const { stdout } = await executeShellCommand({ + command: "apt", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["list"], + }); + // Only lines matching the first character, delete characters after '/' - const out = await executeShellCommand( - `apt list | grep '^${finalToken[0]}' | sed 's#/.*##g'` - ); - return out + return stdout .trim() .split("\n") + .filter((name) => name.startsWith(finalToken)) + .map((name) => name.replace(/\/.*/, "")) .map((name) => ({ name, description: "Package", diff --git a/src/aws/cloudwatch.ts b/src/aws/cloudwatch.ts index e5274499ccc0..dbf8102022d8 100644 --- a/src/aws/cloudwatch.ts +++ b/src/aws/cloudwatch.ts @@ -177,14 +177,14 @@ const postPrecessGenerator = ( const listCustomGenerator = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, command: string, options: string[], parentKey: string, childKey = "" ): Promise => { try { - let cmd = `aws cloudwatch ${command}`; + let args = ["cloudwatch", command]; for (let i = 0; i < options.length; i++) { const option = options[i]; @@ -193,12 +193,15 @@ const listCustomGenerator = async ( continue; } const param = tokens[idx + 1]; - cmd += ` ${option} ${param}`; + args = [...args, option, param]; } - const out = await executeShellCommand(cmd); + const { stdout } = await executeShellCommand({ + command: "aws", + args, + }); - const list = JSON.parse(out)[parentKey]; + const list = JSON.parse(stdout)[parentKey]; if (!Array.isArray(list)) { return [ @@ -209,15 +212,13 @@ const listCustomGenerator = async ( ]; } - return list - .map((elm) => { - const name = (childKey ? elm[childKey] : elm) as string; - return { - name, - icon: "fig://icon?type=aws", - }; - }) - .filter(uniqueNames); + return list.map((elm) => { + const name = (childKey ? elm[childKey] : elm) as string; + return { + name, + icon: "fig://icon?type=aws", + }; + }); } catch (e) { console.log(e); } @@ -226,7 +227,7 @@ const listCustomGenerator = async ( const listDimensionTypes = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, command: string, option: string, parentKey: string, @@ -237,11 +238,13 @@ const listDimensionTypes = async ( if (idx < 0) { return []; } - const cmd = `aws cloudwatch ${command} ${option} ${tokens[idx + 1]}`; - const out = await executeShellCommand(cmd); + const { stdout } = await executeShellCommand({ + command: "aws", + args: ["cloudwatch", command, option, tokens[idx + 1]], + }); - const metrics = JSON.parse(out)[parentKey]; + const metrics = JSON.parse(stdout)[parentKey]; // traverse JSON & compose key-value style suggestion return metrics @@ -265,22 +268,29 @@ const listDimensionTypes = async ( const MultiSuggestionsGenerator = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, - enabled: Record[] + executeShellCommand: Fig.ExecuteCommandFunction, + enabled: { + command: string[]; + parentKey: string; + childKey: string; + }[] ) => { try { const list: Fig.Suggestion[][] = []; const promises: Promise[] = []; for (let i = 0; i < enabled.length; i++) { - promises[i] = executeShellCommand(enabled[i]["command"]); + promises[i] = executeShellCommand({ + command: "aws", + args: enabled[i].command, + }).then(({ stdout }) => stdout); } const result = await Promise.all(promises); for (let i = 0; i < enabled.length; i++) { list[i] = postPrecessGenerator( result[i], - enabled[i]["parentKey"], - enabled[i]["childKey"] + enabled[i].parentKey, + enabled[i].childKey ); } @@ -293,12 +303,15 @@ const MultiSuggestionsGenerator = async ( const getResultList = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, - command: string, + executeShellCommand: Fig.ExecuteCommandFunction, + args: string[], key: string ): Promise => { - const out = await executeShellCommand(command); - return JSON.parse(out)[key]; + const { stdout } = await executeShellCommand({ + command: "aws", + args, + }); + return JSON.parse(stdout)[key]; }; const _prefixFile = "file://"; @@ -535,19 +548,22 @@ const generators: Record = { // individually, to get an ARN custom: async function (tokens, executeShellCommand) { // get list of stream names - const result = await Promise.all([ - getResultList( - tokens, - executeShellCommand, - "aws firehose list-delivery-streams", - "DeliveryStreamNames" - ), - ]); + const result = await getResultList( + tokens, + executeShellCommand, + ["firehose", "list-delivery-streams"], + "DeliveryStreamNames" + ); // construct "query" const objects = result.flat().map((stream) => { return { - command: `aws firehose describe-delivery-stream --delivery-stream-name ${stream}`, + command: [ + "firehose", + "describe-delivery-stream", + "--delivery-stream-name", + Array.isArray(stream.name) ? stream.name[0] : stream.name, + ], parentKey: "DeliveryStreamDescription", childKey: "DeliveryStreamARN", }; diff --git a/src/aws/eks.ts b/src/aws/eks.ts index 7cab444f6666..4ccc050d96a8 100644 --- a/src/aws/eks.ts +++ b/src/aws/eks.ts @@ -32,14 +32,14 @@ const postPrecessGenerator = ( const listCustomGenerator = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, command: string, options: string[], parentKey: string, childKey = "" ): Promise => { try { - let cmd = `aws eks ${command}`; + let args = ["eks", command]; for (let i = 0; i < options.length; i++) { const option = options[i]; @@ -48,12 +48,15 @@ const listCustomGenerator = async ( continue; } const param = tokens[idx + 1]; - cmd += ` ${option} ${param}`; + args = [...args, option, param]; } - const out = await executeShellCommand(cmd); + const { stdout } = await executeShellCommand({ + command: "aws", + args, + }); - const list = JSON.parse(out)[parentKey]; + const list = JSON.parse(stdout)[parentKey]; if (!Array.isArray(list)) { return [ @@ -313,10 +316,11 @@ const generators: Record = { } const param = tokens[idx + 1]; - const out = await executeShellCommand( - `aws eks describe-cluster --name ${param}` - ); - const cluster = JSON.parse(out)["cluster"]; + const { stdout } = await executeShellCommand({ + command: "aws", + args: ["eks", "describe-cluster", "--name", param], + }); + const cluster = JSON.parse(stdout)["cluster"]; const subnets = cluster["resourcesVpcConfig"]["subnetIds"]; return subnets.map((subnet) => { return { diff --git a/src/aws/iam.ts b/src/aws/iam.ts index 2d36c6f2107e..cbaea79063b8 100644 --- a/src/aws/iam.ts +++ b/src/aws/iam.ts @@ -182,15 +182,15 @@ const awsPrincipals = [ ]; interface Identity { - command: string; + command: string[]; parentKey: string; childKey: string; } const identityStruct: Identity[] = [ - { command: "aws iam list-users", parentKey: "Users", childKey: "Arn" }, - { command: "aws iam list-groups", parentKey: "Groups", childKey: "Arn" }, - { command: "aws iam list-roles", parentKey: "Roles", childKey: "Arn" }, + { command: ["iam", "list-users"], parentKey: "Users", childKey: "Arn" }, + { command: ["iam", "list-groups"], parentKey: "Groups", childKey: "Arn" }, + { command: ["iam", "list-roles"], parentKey: "Roles", childKey: "Arn" }, ]; const _prefixFile = "file://"; @@ -288,7 +288,7 @@ const filterWithPrefix = (token: string, prefix: string): string => { const listCustomGenerator = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, command: string, option: string, parentKey: string, @@ -300,11 +300,12 @@ const listCustomGenerator = async ( return []; } const param = tokens[idx + 1]; - const out = await executeShellCommand( - `aws iam ${command} ${option} ${param}` - ); + const { stdout } = await executeShellCommand({ + command: "aws", + args: ["iam", command, option, param], + }); - const policies = JSON.parse(out)[parentKey]; + const policies = JSON.parse(stdout)[parentKey]; return policies.map((elm) => (childKey ? elm[childKey] : elm)); } catch (e) { console.log(e); @@ -328,14 +329,17 @@ const postPrecessGenerator = ( const MultiSuggestionsGenerator = async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, enabled: Identity[] ) => { try { const list: Fig.Suggestion[][] = []; const promises: Promise[] = []; for (let i = 0; i < enabled.length; i++) { - promises[i] = executeShellCommand(enabled[i]["command"]); + promises[i] = executeShellCommand({ + command: "aws", + args: enabled[i].command, + }).then(({ stdout }) => stdout); } const result = await Promise.all(promises); @@ -434,11 +438,17 @@ const generators: Record = { return []; } const param = tokens[idx + 1]; - const out = await executeShellCommand( - `aws iam get-instance-profile --instance-profile-name ${param}` - ); + const { stdout } = await executeShellCommand({ + command: "aws", + args: [ + "iam", + "get-instance-profile", + "--instance-profile-name", + param, + ], + }); - const policies = JSON.parse(out as string)["InstanceProfile"]; + const policies = JSON.parse(stdout)["InstanceProfile"]; return policies["Roles"].map((elm) => { return { name: elm["RoleName"], @@ -800,7 +810,7 @@ const generators: Record = { return MultiSuggestionsGenerator(tokens, executeShellCommand, [ ...identityStruct, { - command: "aws iam list-policies --scope Local", + command: ["iam", "list-policies", "--scope", "Local"], parentKey: "Policies", childKey: "Arn", }, diff --git a/src/aws/s3.ts b/src/aws/s3.ts index a4ce53808b32..c88bd6d948f4 100644 --- a/src/aws/s3.ts +++ b/src/aws/s3.ts @@ -53,8 +53,8 @@ const ttl = 30000; const appendFolderPath = ( whatHasUserTyped: string, - baseLSCommand: string -): string => { + baseLSCommand: string[] +): string[] => { let folderPath = ""; const lastSlashIndex = whatHasUserTyped.lastIndexOf("/"); @@ -66,7 +66,7 @@ const appendFolderPath = ( } } - return baseLSCommand + folderPath; + return [...baseLSCommand, folderPath]; }; const postProcessFiles = (out: string, prefix: string): Fig.Suggestion[] => { @@ -153,15 +153,15 @@ const _prefixFileb = "fileb://"; const generators: Record = { listFilesGenerator: { script: (tokens) => { - const baseLSCommand = "\\ls -1ApL "; + const baseLsCommand = ["ls", "-1ApL"]; const whatHasUserTyped = tokens[tokens.length - 1]; // Do not show file suggestions when s3:// typed if (whatHasUserTyped.startsWith(_prefixS3)) { - return ""; + return undefined; } - return appendFolderPath(whatHasUserTyped, baseLSCommand); + return appendFolderPath(whatHasUserTyped, baseLsCommand); }, postProcess: (out) => { return postProcessFiles(out, _prefixFile); @@ -186,13 +186,13 @@ const generators: Record = { // See more: https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-parameters-file.html listBlobsGenerator: { script: (tokens) => { - const baseLSCommand = "\\ls -1ApL "; + const baseLSCommand = ["ls", "-1ApL "]; let whatHasUserTyped = tokens[tokens.length - 1]; if (whatHasUserTyped.startsWith(_prefixFileb)) { whatHasUserTyped = whatHasUserTyped.slice(_prefixFileb.length); } else { - return "echo 'fileb://'"; + return ["echo", "fileb://"]; } return appendFolderPath(whatHasUserTyped, baseLSCommand); @@ -215,7 +215,7 @@ const generators: Record = { listRemoteFilesGenerator: { script: (tokens) => { const whatHasUserTyped = tokens[tokens.length - 1]; - const baseLSCommand = "\\aws s3 ls "; + const baseLsCommand = ["aws", "s3", "ls"]; let folderPath = ""; @@ -226,17 +226,17 @@ const generators: Record = { // then we can assume that the filepath generator is in work // so do not return any s3 related filepaths if (!_prefixS3.startsWith(whatHasUserTyped)) { - return ""; + return undefined; } - return "echo 's3://'"; + return ["echo", "s3://"]; } if (lastSlashIndex > -1) { folderPath = whatHasUserTyped.slice(0, lastSlashIndex + 1); } - return baseLSCommand + folderPath; + return [...baseLsCommand, folderPath]; }, postProcess: (out) => { if (out == "") { diff --git a/src/aws/secretsmanager.ts b/src/aws/secretsmanager.ts index 9360ec0c1106..d088d21677c2 100644 --- a/src/aws/secretsmanager.ts +++ b/src/aws/secretsmanager.ts @@ -252,10 +252,11 @@ const generators: Record = { return []; } const secretId = tokens[idx + 1]; - const out = await executeShellCommand( - `aws secretsmanager describe-secret --secret-id ${secretId}` - ); - const versions = JSON.parse(out as string)["VersionIdsToStages"]; + const { stdout } = await executeShellCommand({ + command: "aws", + args: ["secretsmanager", "describe-secret", "--secret-id", secretId], + }); + const versions = JSON.parse(stdout)["VersionIdsToStages"]; return Object.keys(versions).map((elm) => ({ name: elm })); } catch (e) { console.log(e); @@ -275,10 +276,11 @@ const generators: Record = { return []; } const secretId = tokens[idx + 1]; - const out = await executeShellCommand( - `aws secretsmanager describe-secret --secret-id ${secretId}` - ); - const versions = JSON.parse(out as string)["VersionIdsToStages"]; + const { stdout } = await executeShellCommand({ + command: "aws", + args: ["secretsmanager", "describe-secret", "--secret-id", secretId], + }); + const versions = JSON.parse(stdout)["VersionIdsToStages"]; return Object.keys(versions).map((elm) => ({ name: versions[elm][0] })); } catch (e) { console.log(e); @@ -299,10 +301,11 @@ const generators: Record = { return []; } const secretId = tokens[idx + 1]; - const out = await executeShellCommand( - `aws secretsmanager describe-secret --secret-id ${secretId}` - ); - const versions = JSON.parse(out as string)["Tags"]; + const { stdout } = await executeShellCommand({ + command: "aws", + args: ["secretsmanager", "describe-secret", "--secret-id", secretId], + }); + const versions = JSON.parse(stdout)["Tags"]; return versions.map((elm) => ({ name: elm["Key"] })); } catch (e) { console.log(e); diff --git a/src/bat.ts b/src/bat.ts index 9ee29589c985..b0f7841954bd 100644 --- a/src/bat.ts +++ b/src/bat.ts @@ -86,7 +86,11 @@ const completionSpec: Fig.Spec = { args: { name: "", generators: { - script: "bat --wrap unknow 2>&1 >/dev/null | grep possible", + script: [ + "bash", + "-c", + "bat --wrap unknow 2>&1 >/dev/null | grep possible", + ], postProcess: function (out) { return out .trim() @@ -123,7 +127,11 @@ const completionSpec: Fig.Spec = { args: { name: "", generators: { - script: "bat --color unknow 2>&1 >/dev/null | grep possible", + script: [ + "bash", + "-c", + "bat --color unknow 2>&1 >/dev/null | grep possible", + ], postProcess: function (out) { return out .trim() @@ -148,7 +156,11 @@ const completionSpec: Fig.Spec = { args: { name: "", generators: { - script: "bat --italic-text unknow 2>&1 >/dev/null | grep possible", + script: [ + "bash", + "-c", + "bat --italic-text unknow 2>&1 >/dev/null | grep possible", + ], postProcess: function (out) { return out .trim() @@ -174,7 +186,11 @@ const completionSpec: Fig.Spec = { args: { name: "", generators: { - script: "bat --decorations unknow 2>&1 >/dev/null | grep possible", + script: [ + "bash", + "-c", + "bat --decorations unknow 2>&1 >/dev/null | grep possible", + ], postProcess: function (out) { return out .trim() @@ -203,7 +219,11 @@ const completionSpec: Fig.Spec = { args: { name: "", generators: { - script: "bat --paging unknow 2>&1 >/dev/null | grep possible", + script: [ + "bash", + "-c", + "bat --paging unknow 2>&1 >/dev/null | grep possible", + ], postProcess: function (out) { return out .trim() diff --git a/src/black.ts b/src/black.ts index 33f9dbc599f2..4f4df0a523e7 100644 --- a/src/black.ts +++ b/src/black.ts @@ -1,6 +1,6 @@ // https://github.com/psf/black const blackVersions: Fig.Generator = { - script: ["gh","release","list","--repo","psf/black"], + script: ["gh", "release", "list", "--repo", "psf/black"], cache: { ttl: 1000 * 60 * 60 * 24 * 2, // 2 days }, diff --git a/src/brew.ts b/src/brew.ts index d5d10d3f978c..99b87c45ca9a 100644 --- a/src/brew.ts +++ b/src/brew.ts @@ -1,5 +1,5 @@ const servicesGenerator = (action: string): Fig.Generator => ({ - script: "brew services list | sed -e 's/ .*//' | tail -n +2", + script: ["bash", "-c", "brew services list | sed -e 's/ .*//' | tail -n +2"], postProcess: function (out) { return out .split("\n") @@ -34,7 +34,7 @@ const formulaeGenerator: Fig.Generator = { }; const outdatedformulaeGenerator: Fig.Generator = { - script: "brew outdated -q", + script: ["brew", "outdated", "-q"], postProcess: function (out) { return out.split("\n").map((formula) => ({ name: formula, @@ -68,7 +68,11 @@ const generateAllCasks: Fig.Generator = { }, }; const generateAliases: Fig.Generator = { - script: 'find ~/.brew-aliases/ -type f ! -name "*.*" -d 1 | sed "s/.*\\///"', + script: [ + "bash", + "-c", + 'find ~/.brew-aliases/ -type f ! -name "*.*" -d 1 | sed "s/.*\\///"', + ], postProcess: function (out) { return out .split("\n") @@ -1274,7 +1278,7 @@ const completionSpec: Fig.Spec = { isVariadic: true, generators: { - script: ["brew","list","-1","--cask"], + script: ["brew", "list", "-1", "--cask"], postProcess: function (out) { return out.split("\n").map((formula) => { return { diff --git a/src/browser-sync.ts b/src/browser-sync.ts index 48f36cae5d58..4fd800e8ad31 100644 --- a/src/browser-sync.ts +++ b/src/browser-sync.ts @@ -225,8 +225,11 @@ const completionSpec: Fig.Spec = { args: { name: "recipe-name", generators: { - script: + script: [ + "bash", + "-c", "browser-sync recipe ls | tail -n +3 | sed -e 's/^[[:space:]]*//'", + ], splitOn: "\n", cache: { strategy: "max-age", diff --git a/src/bunx.ts b/src/bunx.ts index a5a77ae4a1e5..f3ed883d3489 100644 --- a/src/bunx.ts +++ b/src/bunx.ts @@ -6,7 +6,11 @@ const bunx: Fig.Spec = { name: "command", isCommand: true, generators: { - script: `until [[ -d node_modules/ ]] || [[ $PWD = '/' ]]; do cd ..; done; ls -1 node_modules/.bin/`, + script: [ + "bash", + "-c", + "until [[ -d node_modules/ ]] || [[ $PWD = '/' ]]; do cd ..; done; ls -1 node_modules/.bin/`", + ], postProcess: function (out) { const cli = [...npxSuggestions].reduce( (acc, { name }) => [...acc, name], diff --git a/src/cargo.ts b/src/cargo.ts index 9d7dcb2ecf6d..6bcbb51c5476 100644 --- a/src/cargo.ts +++ b/src/cargo.ts @@ -47,37 +47,38 @@ const vcsOptions: { }, ]; -const testGenerator: Fig.Generator = { - cache: { - cacheByDirectory: true, - strategy: "stale-while-revalidate", - ttl: 1000 * 60 * 5, - }, - script: (context) => { - const base = context[context.length - 1]; - // allow split by single colon so that it triggers on a::b: - const indexIntoModPath = Math.max(base.split(/::?/).length, 1); - // split by :: so that tokens with a single colon are allowed - const moduleTokens = base.split("::"); - const lastModule = moduleTokens.pop(); - // check if the token has a : on the end - const hasColon = lastModule[lastModule.length - 1] == ":" ? ":" : ""; - return `cargo t -- --list | awk '/: test$/ { print substr($1, 1, length($1) - 1) }' | awk -F "::" '{ print "${hasColon}"$${indexIntoModPath},int( NF / ${indexIntoModPath} ) }'`; - }, - postProcess: (out) => { - return [...new Set(out.split("\n"))].map((line) => { - const [display, last] = line.split(" "); - const lastModule = parseInt(last); - const displayName = display.replaceAll(":", ""); - const name = displayName.length - ? `${display}${lastModule ? "" : "::"}` - : ""; - return { name, displayName }; - }); - }, - trigger: ":", - getQueryTerm: ":", -}; +// TODO(grant): add this back but better with no awk +// const testGenerator: Fig.Generator = { +// cache: { +// cacheByDirectory: true, +// strategy: "stale-while-revalidate", +// ttl: 1000 * 60 * 5, +// }, +// script: (context) => { +// const base = context[context.length - 1]; +// // allow split by single colon so that it triggers on a::b: +// const indexIntoModPath = Math.max(base.split(/::?/).length, 1); +// // split by :: so that tokens with a single colon are allowed +// const moduleTokens = base.split("::"); +// const lastModule = moduleTokens.pop(); +// // check if the token has a : on the end +// const hasColon = lastModule[lastModule.length - 1] == ":" ? ":" : ""; +// return `cargo t -- --list | awk '/: test$/ { print substr($1, 1, length($1) - 1) }' | awk -F "::" '{ print "${hasColon}"$${indexIntoModPath},int( NF / ${indexIntoModPath} ) }'`; +// }, +// postProcess: (out) => { +// return [...new Set(out.split("\n"))].map((line) => { +// const [display, last] = line.split(" "); +// const lastModule = parseInt(last); +// const displayName = display.replaceAll(":", ""); +// const name = displayName.length +// ? `${display}${lastModule ? "" : "::"}` +// : ""; +// return { name, displayName }; +// }); +// }, +// trigger: ":", +// getQueryTerm: ":", +// }; type Metadata = { packages: Package[]; @@ -160,11 +161,11 @@ const targetGenerator: ({ kind }: { kind?: TargetKind }) => Fig.Generator = ({ kind, }) => ({ custom: async (_, executeShellCommand, context) => { - const out = await executeShellCommand({ + const { stdout } = await executeShellCommand({ command: "cargo", args: ["metadata", "--format-version", "1", "--no-deps"], }); - const manifest: Metadata = JSON.parse(out); + const manifest: Metadata = JSON.parse(stdout); const packages = rootPackageOrLocal(manifest); let targets = packages.flatMap((pkg) => pkg.targets); @@ -318,7 +319,7 @@ const searchGenerator: Fig.Generator = { }; const tripleGenerator: Fig.Generator = { - script: ["rustc","--print","target-list"], + script: ["rustc", "--print", "target-list"], postProcess: (data: string) => { return data .split("\n") @@ -4706,7 +4707,6 @@ const completionSpec: (toolchain?: boolean) => Fig.Spec = ( name: "args", isOptional: true, isVariadic: true, - generators: testGenerator, }, ], }, @@ -5016,7 +5016,11 @@ const completionSpec: (toolchain?: boolean) => Fig.Spec = ( args: { name: "SPEC", generators: { - script: `cargo install --list | \\grep -E "^[a-zA-Z\\-]+\\sv" | cut -d ' ' -f 1`, + script: [ + "bash", + "-c", + `cargo install --list | \\grep -E "^[a-zA-Z\\-]+\\sv" | cut -d ' ' -f 1`, + ], splitOn: "\n", }, isVariadic: true, diff --git a/src/cf.ts b/src/cf.ts index ae22e674520e..58fc2ea82eb6 100644 --- a/src/cf.ts +++ b/src/cf.ts @@ -7,7 +7,7 @@ const postProcessCfList = .map((name) => ({ name, description })); const generateAppNames: Fig.Generator = { - script: `cf apps | cut -d " " -f1`, + script: ["bash", "-c", `cf apps | cut -d " " -f1`], postProcess: postProcessCfList("App name", 4), }; @@ -22,7 +22,7 @@ const generateSpaces: Fig.Generator = { }; const generateServices: Fig.Generator = { - script: `cf services | cut -d " " -f1 `, + script: ["bash", "-c", `cf services | cut -d " " -f1 `], postProcess: postProcessCfList("Service", 4), }; diff --git a/src/chown.ts b/src/chown.ts index 02ec642d0bd9..c271be0bd401 100644 --- a/src/chown.ts +++ b/src/chown.ts @@ -8,13 +8,20 @@ export const existingUsersandGroups: Fig.Generator = { // in the current command. If it is, get the system groups // else retrieve the list of system users if (colonAdded) { - shell = await executeShellCommand( - "dscl . -list /Groups PrimaryGroupID | tr -s ' '| sort -r" - ); + const { stdout } = await executeShellCommand({ + command: "bash", + args: [ + "-c", + "dscl . -list /Groups PrimaryGroupID | tr -s ' '| sort -r", + ], + }); + shell = stdout; } else { - shell = await executeShellCommand( - "dscl . -list /Users UniqueID | tr -s ' '| sort -r" - ); + const { stdout } = await executeShellCommand({ + command: "bash", + args: ["-c", "dscl . -list /Users UniqueID | tr -s ' '| sort -r"], + }); + shell = stdout; } return ( diff --git a/src/coda.ts b/src/coda.ts index e4c07cb61810..61b36cd4c9b3 100644 --- a/src/coda.ts +++ b/src/coda.ts @@ -1,7 +1,11 @@ import { filepaths } from "@fig/autocomplete-generators"; const formulaNames: Fig.Generator = { - script: `grep -A5 --include=\*.ts --exclude-dir=node_modules -r 'addFormula\\|addSyncTable\\|makeFormula\\|makeSyncTable' . | grep -A3 -i formula | grep name: | grep -oE "['\\"]\\w*['\\"]"`, + script: [ + "bash", + "-c", + `grep -A5 --include=\*.ts --exclude-dir=node_modules -r 'addFormula\\|addSyncTable\\|makeFormula\\|makeSyncTable' . | grep -A3 -i formula | grep name: | grep -oE "['\\"]\\w*['\\"]"`, + ], postProcess: (output) => { if (output.trim().length === 0) { return []; diff --git a/src/conda.ts b/src/conda.ts index 622fc1efa2ca..01e66016aca0 100644 --- a/src/conda.ts +++ b/src/conda.ts @@ -56,7 +56,7 @@ const getCondaEnvironments: Fig.Generator = { }; const getCondaConfigs: Fig.Generator = { - script: ["conda","config","--show"], + script: ["conda", "config", "--show"], postProcess: function (out) { const lines = out.split("\n"); const configs: Fig.Suggestion[] = []; diff --git a/src/copilot.ts b/src/copilot.ts index 36c980ae7bde..a292d9fce01c 100644 --- a/src/copilot.ts +++ b/src/copilot.ts @@ -1,7 +1,7 @@ import YAML from "yaml"; const applicationName: Fig.Generator = { - script: ["cat","copilot/.workspace"], + script: ["cat", "copilot/.workspace"], // TODO: I feel like there's a better way to do this. // There's only ever expected to be one `application` key. postProcess: (output) => { diff --git a/src/cordova.ts b/src/cordova.ts index 2110d3e7efc8..e7ae9a749b01 100644 --- a/src/cordova.ts +++ b/src/cordova.ts @@ -24,7 +24,7 @@ const commonOptions: Fig.Option[] = [ ]; const platformGenerator: Fig.Generator = { - script: ["cat","package.json"], + script: ["cat", "package.json"], postProcess: function (out: string) { const suggestions = []; try { diff --git a/src/deno/generators.ts b/src/deno/generators.ts index 76de2774d02d..709214b3c927 100644 --- a/src/deno/generators.ts +++ b/src/deno/generators.ts @@ -165,7 +165,7 @@ export const generateDocs: Fig.Generator = { !token.startsWith("(") ); command.push("--json"); - return command.join(" "); + return command; }, postProcess: (out, tokens) => { const docNodes = JSON.parse(out) as DocNode[]; @@ -191,7 +191,7 @@ type VersionsJSON = { }; export const generateVersions: Fig.Generator = { - script: `curl -sL 'https://cdn.deno.land/deno/meta/versions.json'`, + script: ["curl", "-sL", "https://cdn.deno.land/deno/meta/versions.json"], cache: { ttl: 1000 * 60 * 60 * 24 }, // 24 hours, in milliseconds postProcess: (out) => { const data = JSON.parse(out) as VersionsJSON; @@ -305,17 +305,26 @@ function getConfigPath(tokens: string[]): string | null { async function getDenoConfig( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction + executeShellCommand: Fig.ExecuteCommandFunction ): Promise { const configPath = getConfigPath(tokens); let jsonString: string; if (configPath) { - jsonString = await executeShellCommand(`\\cat '${configPath}'`); + const { stdout } = await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [configPath], + }); + jsonString = stdout; } else { // Move backwards through the directory heirarchy until we find a config file (or hit the root) - jsonString = await executeShellCommand( - "until [[ ( -f deno.json || -f deno.jsonc || $PWD = '/' ) ]]; do cd ..; done; \\cat deno.json 2>/dev/null || \\cat deno.jsonc 2>/dev/null" - ); + const { stdout } = await executeShellCommand({ + command: "bash", + args: [ + "-c", + "until [[ ( -f deno.json || -f deno.jsonc || $PWD = '/' ) ]]; do cd ..; done; \\cat deno.json 2>/dev/null || \\cat deno.jsonc 2>/dev/null", + ], + }); } try { return JSON.parse(stripJsonComments(jsonString)); @@ -351,7 +360,7 @@ export const generateTasks: Fig.Generator = { // --- Generate installed deno scripts export const generateInstalledDenoScripts: Fig.Generator = { - script: "\\find ~/.deno/bin -maxdepth 1 -perm -111 -type f", + script: ["bash", "-c", "\\find ~/.deno/bin -maxdepth 1 -perm -111 -type f"], postProcess: (out) => out .split("\n") diff --git a/src/deployctl.ts b/src/deployctl.ts index 101066ecbe41..62fc4593001f 100644 --- a/src/deployctl.ts +++ b/src/deployctl.ts @@ -67,7 +67,11 @@ const completionSpec: Fig.Spec = { name: "version", isOptional: true, generators: { - script: `curl -sL 'https://cdn.deno.land/deploy/meta/versions.json'`, + script: [ + "curl", + "-sL", + "https://cdn.deno.land/deploy/meta/versions.json", + ], cache: { ttl: 1000 * 60 * 60 * 24 }, // 24 hours, in milliseconds postProcess: (out) => { const data = JSON.parse(out) as VersionsJSON; diff --git a/src/docker-compose.ts b/src/docker-compose.ts index 532e88c31235..af49fadb68ae 100644 --- a/src/docker-compose.ts +++ b/src/docker-compose.ts @@ -1,7 +1,7 @@ const getComposeCommand = (tokens: string[]) => - tokens[0] === "docker" ? "docker compose" : "docker-compose"; + tokens[0] === "docker" ? ["docker", "compose"] : ["docker-compose"]; -const extractFileArgs = (tokens: string[]): string => { +const extractFileArgs = (tokens: string[]): string[] => { const files: string[] = []; for (let i = 0; i < tokens.length - 1; i++) { if (tokens[i] === "-f") { @@ -9,14 +9,14 @@ const extractFileArgs = (tokens: string[]): string => { i += 1; } } - return files.map((f) => `-f ${f}`).join(" "); + return files.flatMap((f) => ["-f", f]); }; const servicesGenerator: Fig.Generator = { script: (tokens) => { const compose = getComposeCommand(tokens); const fileArgs = extractFileArgs(tokens); - return `${compose} ${fileArgs} config --services`; + return [...compose, ...fileArgs, "config", "--services"]; }, splitOn: "\n", }; @@ -25,7 +25,7 @@ const profilesGenerator: Fig.Generator = { script: (tokens) => { const compose = getComposeCommand(tokens); const fileArgs = extractFileArgs(tokens); - return `${compose} ${fileArgs} config --profiles`; + return [...compose, ...fileArgs, "config", "--profiles"]; }, splitOn: "\n", }; diff --git a/src/doppler.ts b/src/doppler.ts index fa5dd6a90c11..7d730c20fcbd 100644 --- a/src/doppler.ts +++ b/src/doppler.ts @@ -64,7 +64,7 @@ const projectsGenerator: Fig.Generator = { cacheKey: "projects", cacheByDirectory: true, }, - script: ["doppler","projects","--json"], + script: ["doppler", "projects", "--json"], postProcess: (out) => { try { const obj = JSON.parse(out); diff --git a/src/dotnet/dotnet-run.ts b/src/dotnet/dotnet-run.ts index c8cc4e23d06b..b7a221f15ce0 100644 --- a/src/dotnet/dotnet-run.ts +++ b/src/dotnet/dotnet-run.ts @@ -58,7 +58,7 @@ const completionSpec: Fig.Spec = { name: "name", suggestions: ["Development", "Staging", "Production"], generators: { - script: ["cat","Properties/launchSettings.json"], + script: ["cat", "Properties/launchSettings.json"], postProcess(out) { const profiles: LaunchProfiles = JSON.parse(out).profiles; diff --git a/src/dotnet/dotnet-tool.ts b/src/dotnet/dotnet-tool.ts index 11306b390eb2..d7e53367ac0c 100644 --- a/src/dotnet/dotnet-tool.ts +++ b/src/dotnet/dotnet-tool.ts @@ -64,10 +64,10 @@ const toolListGenerator: Fig.Generator = { const globalFlags = ["-g", "--global"]; if (context.some((ctx) => globalFlags.includes(ctx))) { - return "dotnet tool list --global"; + return ["dotnet", "tool", "list", "--global"]; } - return "dotnet tool list"; + return ["dotnet", "tool", "list"]; }, postProcess(out) { const lines = out.split("\n").slice(2); diff --git a/src/drush.ts b/src/drush.ts index 7574f6f1e87f..d7f7549ae5a2 100644 --- a/src/drush.ts +++ b/src/drush.ts @@ -38,7 +38,11 @@ const completionSpec: Fig.Spec = { description: "Drush is a command line shell and Unix scripting interface for Drupal", generateSpec: async (tokens, executeShellCommand) => { - const jsonList = await executeShellCommand("drush --format=json"); + const { stdout: jsonList } = await executeShellCommand({ + command: "drush", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["--format=json"], + }); const subcommands: Fig.Subcommand[] = []; try { diff --git a/src/dscl.ts b/src/dscl.ts index eb4f0c5364ef..781a0db3cadf 100644 --- a/src/dscl.ts +++ b/src/dscl.ts @@ -85,8 +85,10 @@ const generateDsclPath: Fig.Generator = { } const lastSlashIndex = lastToken.lastIndexOf("/"); const path = lastToken.slice(0, lastSlashIndex < 0 ? 0 : lastSlashIndex); - const command = `dscl ${datasource} -list ${path || "/"}`; - const lines = await executeShellCommand(command); + const { stdout: lines } = await executeShellCommand({ + command: "dscl", + args: [datasource, "-list", path ?? "/"], + }); return lines .trim() .split("\n") diff --git a/src/esbuild.ts b/src/esbuild.ts index 1fdba4d5c28a..53934f64c830 100644 --- a/src/esbuild.ts +++ b/src/esbuild.ts @@ -6,10 +6,14 @@ const icon = const ignoreExtensions = new Set(["", "sample", "env"]); const extensions: Fig.Generator["custom"] = async (_, executeShellCommand) => { - const out = await executeShellCommand( - "find . -depth 3 -type f -name '*.*' -not -path '*/node_modules/*' | sed 's/.*\\.//' | sort -u" - ); - const lines = out.trim().split("\n"); + const { stdout } = await executeShellCommand({ + command: "bash", + args: [ + "-c", + "find . -depth 3 -type f -name '*.*' -not -path '*/node_modules/*' | sed 's/.*\\.//' | sort -u", + ], + }); + const lines = stdout.trim().split("\n"); return lines .filter((line) => !ignoreExtensions.has(line)) .map((line) => ({ name: "." + line })); diff --git a/src/eslint.ts b/src/eslint.ts index 2653d6a9fd09..663f213154ae 100644 --- a/src/eslint.ts +++ b/src/eslint.ts @@ -115,8 +115,11 @@ const completionSpec: Fig.Spec = { args: { name: "Plugin", generators: { - script: + script: [ + "bash", + "-c", "{ ls node_modules ; ls $(npm root -g) ; ls $(yarn global dir)/node_modules/ ; } | cat", + ], postProcess: (out) => out .split("\n") diff --git a/src/example/trigger.ts b/src/example/trigger.ts index 0108abf86ca0..52c6b0210306 100644 --- a/src/example/trigger.ts +++ b/src/example/trigger.ts @@ -7,13 +7,13 @@ // NOTE: replace _prefix_string_for_file_and_folder_suggestions with whatever prefix you'd like e.g. "s3://" const _prefix_string_for_file_and_folder_suggestions = "file://"; -var customArgument = { +var customArgument: Fig.Arg = { name: "FILE/FOLDER", description: "must start with " + _prefix_string_for_file_and_folder_suggestions, generators: { script: (tokens) => { - var baseLSCommand = "\\ls -1ApL "; + var baseLsCommand = ["ls", "-1ApL"]; var whatHasUserTyped = tokens[tokens.length - 1]; if ( @@ -23,7 +23,7 @@ var customArgument = { ) { whatHasUserTyped = whatHasUserTyped.slice(7); } else { - return "echo 'file://'"; + return ["echo", "file://"]; } // Get the folder path to run ls from based on what user has typed @@ -45,7 +45,7 @@ var customArgument = { else folderPath = whatHasUserTyped.slice(0, lastSlashIndex + 1); } - return baseLSCommand + folderPath; + return [...baseLsCommand, folderPath]; }, postProcess: (out) => { if (out.trim() === _prefix_string_for_file_and_folder_suggestions) { diff --git a/src/expo-cli.ts b/src/expo-cli.ts index a5a2ee7572fd..32a0620ce611 100644 --- a/src/expo-cli.ts +++ b/src/expo-cli.ts @@ -34,12 +34,12 @@ const _gen: Record = { }, }, "xcode-configuration": { - script: "xcodebuild -project ios/*.xcodeproj -list -json", + script: ["bash", "-c", "xcodebuild -project ios/*.xcodeproj -list -json"], postProcess: (script: string) => JSON.parse(script).project.configurations.map((name) => ({ name })), }, "xcode-scheme": { - script: "xcodebuild -project ios/*.xcodeproj -list -json", + script: ["bash", "-c", "xcodebuild -project ios/*.xcodeproj -list -json"], postProcess: (script: string) => JSON.parse(script).project.schemes.map((name) => ({ name })), }, diff --git a/src/ffmpeg.ts b/src/ffmpeg.ts index 00d5f996d94d..3992d115e9ce 100644 --- a/src/ffmpeg.ts +++ b/src/ffmpeg.ts @@ -413,7 +413,7 @@ const completionSpec: Fig.Spec = { args: { name: "codec", generators: { - script: ["ffmpeg","-codecs"], + script: ["ffmpeg", "-codecs"], postProcess: (out) => { return out .split("\n") @@ -924,7 +924,7 @@ const completionSpec: Fig.Spec = { args: { name: "codec", generators: { - script: ["ffmpeg","-codecs"], + script: ["ffmpeg", "-codecs"], postProcess: (out) => { return out .split("\n") diff --git a/src/fig/shared.ts b/src/fig/shared.ts index e1462b725c30..6beca5c8baa8 100644 --- a/src/fig/shared.ts +++ b/src/fig/shared.ts @@ -39,21 +39,6 @@ const graphql = async ({ return JSON.parse(stdout).data; }; -const devCompletionsFolderGenerator: Fig.Generator = { - script: '\\ls -d -1 "$PWD/"**/', - postProcess: (out) => - out.split("\n").map((folder) => { - const paths = folder.split("/"); - paths.pop(); - - return { - name: paths.pop(), - insertValue: folder, - icon: `fig://path/${folder}`, - }; - }), -}; - const disableForCommandsGenerator: Fig.Generator = { script: ["fig", "settings", "autocomplete.disableForCommands"], postProcess: (out) => { @@ -120,25 +105,29 @@ export const themesGenerator: Fig.Generator = { }; export const SETTINGS_GENERATOR: Record = { - "autocomplete.devCompletionsFolder": devCompletionsFolderGenerator, "autocomplete.disableForCommands": disableForCommandsGenerator, "autocomplete.theme": themesGenerator, }; export const subsystemsGenerator: Fig.Generator = { - script: "\\ls ~/.fig/logs", - trigger: (curr, prev) => { - // trigger on new token - return curr.length === 0 && prev.length > 0; - }, - postProcess: (out, tokens) => { - const insertedLogFiles = new Set(tokens.slice(0, -1)); - return out - .split("\n") - .map((name) => name.replace(".log", "")) - .concat("figterm") - .map((name) => ({ name, icon: "🪵" })) - .filter((suggestion) => !insertedLogFiles.has(suggestion.name)); + // script: "\\ls ~/.fig/logs", + // trigger: (curr, prev) => { + // // trigger on new token + // return curr.length === 0 && prev.length > 0; + // }, + // postProcess: (out, tokens) => { + // const insertedLogFiles = new Set(tokens.slice(0, -1)); + // return out + // .split("\n") + // .map((name) => name.replace(".log", "")) + // .concat("figterm") + // .map((name) => ({ name, icon: "🪵" })) + // .filter((suggestion) => !insertedLogFiles.has(suggestion.name)); + // }, + custom: async () => { + return ["figterm", "fig_cli", "fig_desktop", "daemon"].map((name) => ({ + name, + })); }, }; diff --git a/src/fin.ts b/src/fin.ts index 70c96b8fcac6..c6f42bf8b65f 100644 --- a/src/fin.ts +++ b/src/fin.ts @@ -373,8 +373,11 @@ const completionSpec: Fig.Spec = { name: "key-name", isOptional: true, generators: { - script: + script: [ + "bash", + "-c", "\\command ls $HOME/.ssh | \\command grep --color=never -v 'pub'", + ], splitOn: "\n", }, }, @@ -943,9 +946,10 @@ const completionSpec: Fig.Spec = { // Adding dynamic subcommands generateSpec: async (tokens, executeShellCommand) => { var new_subcommands = []; - const available_commands = await executeShellCommand( - "ls -1 ~/.docksal/commands/" - ); + const { stdout: available_commands } = await executeShellCommand({ + command: "bash", + args: ["-c", "ls -1 ~/.docksal/commands/"], + }); for (const command of available_commands.split("\n")) { if (command) { new_subcommands.push({ diff --git a/src/firebase.ts b/src/firebase.ts index bddab501a17c..2747db8f1bba 100644 --- a/src/firebase.ts +++ b/src/firebase.ts @@ -1,5 +1,5 @@ const projectAliasesGenerator: Fig.Generator = { - script: ["firebase","projects:list"], // this calls to a firebase server and is therefore slow + script: ["firebase", "projects:list"], // this calls to a firebase server and is therefore slow postProcess: (out) => { const getAliasRegex = /^│ (\w.*?)│/gm; const aliasesRaw = Array.from(out.matchAll(getAliasRegex)); diff --git a/src/fisher.ts b/src/fisher.ts index 5dd4efe25d67..dbd60c3c26b7 100644 --- a/src/fisher.ts +++ b/src/fisher.ts @@ -70,7 +70,7 @@ const pluginList = [ ]; const installedPlugins: Fig.Generator = { - script: ["fish","-c","fisher list"], + script: ["fish", "-c", "fisher list"], postProcess: (output: string) => { if (!output) { return []; diff --git a/src/git.ts b/src/git.ts index 50dbdcd0fad3..4db00b885ed3 100644 --- a/src/git.ts +++ b/src/git.ts @@ -444,8 +444,11 @@ export const gitGenerators: Record = { }, getStagedFiles: { - script: + script: [ + "bash", + "-c", "git --no-optional-locks status --short | sed -ne '/^M /p' -e '/A /p'", + ], postProcess: postProcessTrackedFiles, }, @@ -457,9 +460,17 @@ export const gitGenerators: Record = { getChangedTrackedFiles: { script: function (context) { if (context.includes("--staged") || context.includes("--cached")) { - return `git --no-optional-locks status --short | sed -ne '/^M /p' -e '/A /p'`; + return [ + "bash", + "-c", + `git --no-optional-locks status --short | sed -ne '/^M /p' -e '/A /p'`, + ]; } else { - return `git --no-optional-locks status --short | sed -ne '/M /p' -e '/A /p'`; + return [ + "bash", + "-c", + `git --no-optional-locks status --short | sed -ne '/M /p' -e '/A /p'`, + ]; } }, postProcess: postProcessTrackedFiles, diff --git a/src/goto.ts b/src/goto.ts index 10e0e2b19b24..81d30584a527 100644 --- a/src/goto.ts +++ b/src/goto.ts @@ -1,10 +1,14 @@ const listTargets: Fig.Generator = { - custom: async (tokens, executeShellCommand) => { - const targets = await executeShellCommand("command cat ~/.config/goto"); + custom: async (tokens, executeShellCommand, context) => { + const { stdout } = await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [`${context.environmentVariables["HOME"]}/.config/goto`], + }); const targetSuggestions = new Map(); - for (const target of targets.split("\n")) { + for (const target of stdout.split("\n")) { const splits = target.split(" "); targetSuggestions.set(target, { name: splits[0], diff --git a/src/hexo.ts b/src/hexo.ts index 47d56cf0dd66..b3e2d4b6a903 100644 --- a/src/hexo.ts +++ b/src/hexo.ts @@ -1,5 +1,5 @@ const draftGenerator: Fig.Generator = { - script: "hexo list post | grep -E ^Draft", + script: ["bash", "-c", "hexo list post | grep -E ^Draft"], postProcess: (out) => { return out.split("\n").map(function (file) { const title = file diff --git a/src/hugo.ts b/src/hugo.ts index 4c9bfc490011..f1488beb9b27 100644 --- a/src/hugo.ts +++ b/src/hugo.ts @@ -874,7 +874,7 @@ const completionSpec: Fig.Spec = { { name: "archetype|default", generators: { - script: ["ls","./archetypes/"], + script: ["ls", "./archetypes/"], postProcess: (output) => output.split("\n").map((fileName) => ({ name: fileName.slice(0, fileName.lastIndexOf(".")), diff --git a/src/iconv.ts b/src/iconv.ts index 69e571eef1bf..abcd0b314cc2 100644 --- a/src/iconv.ts +++ b/src/iconv.ts @@ -1,5 +1,5 @@ const encodingGenerator: Fig.Generator = { - script: "iconv -l | command tr ' ' '\\n' | sort", + script: ["bash", "-c", "iconv -l | command tr ' ' '\\n' | sort"], postProcess: (out) => out.split("\n").map((encoding) => ({ name: encoding, diff --git a/src/id.ts b/src/id.ts index 7540a484071f..48e98373e8ea 100644 --- a/src/id.ts +++ b/src/id.ts @@ -59,7 +59,7 @@ const completionSpec: Fig.Spec = { name: "user", isOptional: true, generators: { - script: "dscl . -list /Users | grep -v '^_'", + script: ["bash", "-c", "dscl . -list /Users | grep -v '^_'"], postProcess: (out) => out .trim() diff --git a/src/ignite-cli.ts b/src/ignite-cli.ts index d986e98a5a00..fe42d866a5dc 100644 --- a/src/ignite-cli.ts +++ b/src/ignite-cli.ts @@ -1,5 +1,5 @@ const generatorsGenerator: Fig.Generator = { - script: ["ls","ignite/templates"], + script: ["ls", "ignite/templates"], postProcess: (out) => { if (out.trim() === "") return []; return out.split("\n").map((gen) => ({ diff --git a/src/jenv.ts b/src/jenv.ts index 6cfca182549d..4c24a1ac5ae7 100644 --- a/src/jenv.ts +++ b/src/jenv.ts @@ -1,5 +1,9 @@ const programGenerator: Fig.Generator = { - script: `for i in $(echo $PATH | tr ":" "\\n"); do [[ -d "$i" ]] && find "$i" -maxdepth 1 -type f -perm -111 && find "$i" -maxdepth 1 -type l -perm -111; done`, + script: [ + "bash", + "-c", + `for i in $(echo $PATH | tr ":" "\\n"); do [[ -d "$i" ]] && find "$i" -maxdepth 1 -type f -perm -111 && find "$i" -maxdepth 1 -type l -perm -111; done`, + ], postProcess: (out) => out .split("\n") @@ -54,7 +58,7 @@ const generateAllPlugins: Fig.Generator = { }, }; const generateJEnvVersions: Fig.Generator = { - script: ["jenv","versions","--bare"], + script: ["jenv", "versions", "--bare"], postProcess: function (out) { return out .split("\n") diff --git a/src/kill.ts b/src/kill.ts index 425a35cfcf21..ded07672e2e7 100644 --- a/src/kill.ts +++ b/src/kill.ts @@ -15,7 +15,7 @@ const completionSpec: Fig.Spec = { name: "pid", isVariadic: true, generators: { - script: "ps axo pid,comm | sed 1d", + script: ["bash", "-c", "ps axo pid,comm | sed 1d"], postProcess: (result: string) => { return result.split("\n").map((line) => { const [pid, path] = line.trim().split(/\s+/); diff --git a/src/killall.ts b/src/killall.ts index 8375cf075969..c22e55dd6a4f 100644 --- a/src/killall.ts +++ b/src/killall.ts @@ -43,7 +43,7 @@ const completionSpec: Fig.Spec = { isVariadic: true, generators: { // All processes, only display the path - script: "ps -A -o comm | sort -u", + script: ["bash", "-c", "ps -A -o comm | sort -u"], postProcess: (out) => out .trim() @@ -113,7 +113,7 @@ const completionSpec: Fig.Spec = { args: { name: "user", generators: { - script: "dscl . -list /Users | grep -v '^_'", + script: ["bash", "-c", "dscl . -list /Users | grep -v '^_'"], postProcess: (out) => out .trim() diff --git a/src/kubectx.ts b/src/kubectx.ts index ea5450abe4fc..c1f26d819e68 100644 --- a/src/kubectx.ts +++ b/src/kubectx.ts @@ -59,7 +59,7 @@ const completionSpec: Fig.Spec = { name: "context", generators: [ { - script: `kubectx | grep -v $(kubectx -c)`, + script: ["bash", "-c", "kubectx | grep -v $(kubectx -c)"], postProcess: (out) => out.split("\n").map((item) => ({ name: item, diff --git a/src/kubens.ts b/src/kubens.ts index 17437c6c1949..f3112c2465db 100644 --- a/src/kubens.ts +++ b/src/kubens.ts @@ -26,7 +26,7 @@ const completionSpec: Fig.Spec = { name: "namespace", generators: [ { - script: "kubens | grep -v $(kubens -c)", + script: ["bash", "-c", "kubens | grep -v $(kubens -c)"], postProcess: (out) => out.split("\n").map((item) => ({ name: item, diff --git a/src/login.ts b/src/login.ts index 8055381a3b57..8eaabc4991f0 100644 --- a/src/login.ts +++ b/src/login.ts @@ -25,7 +25,7 @@ const completionSpec: Fig.Spec = { args: { name: "username", generators: { - script: ["cat","/etc/passwd"], + script: ["cat", "/etc/passwd"], postProcess: (out) => { return out.split("\n").map((line) => { const [username] = line.split(":"); diff --git a/src/m.ts b/src/m.ts index d255cb37bb7d..1c18b68d02f1 100644 --- a/src/m.ts +++ b/src/m.ts @@ -27,7 +27,7 @@ const generateVolumes: Fig.Generator = { }; const generateUsers: Fig.Generator = { - script: "m user list | awk '{ print $1 }'", + script: ["bash", "-c", "m user list | awk '{ print $1 }'"], postProcess: (out) => out .trim() @@ -40,7 +40,7 @@ const generateUsers: Fig.Generator = { }; const generateGroups: Fig.Generator = { - script: "m group list | awk '{ print $1 }'", + script: ["bash", "-c", "m group list | awk '{ print $1 }'"], postProcess: (out) => out .trim() @@ -53,12 +53,12 @@ const generateGroups: Fig.Generator = { }; const generateNetworkLocations: Fig.Generator = { - script: "m network location list | tail -n +2", + script: ["bash", "-c", "m network location list | tail -n +2"], splitOn: "\n", }; const generateServices: Fig.Generator = { - script: "launchctl list | awk '{ print $3 }'", + script: ["bash", "-c", "launchctl list | awk '{ print $3 }'"], splitOn: "\n", }; @@ -70,7 +70,7 @@ function getPidIcon(path: string): string { return "fig://" + path.slice(0, idx + 4); } const generatePids: Fig.Generator = { - script: "ps axo pid,comm | sed 1d", + script: ["bash", "-c", "ps axo pid,comm | sed 1d"], postProcess: (result) => { return result.split("\n").map((line) => { const [pid, path] = line.trim().split(/\s+/); @@ -86,8 +86,11 @@ const generatePids: Fig.Generator = { }; const generateWifiNetworks: Fig.Generator = { - script: + script: [ + "bash", + "-c", "networksetup -listallhardwareports | awk '/Wi-Fi/{getline; print $2}' | xargs networksetup -listpreferredwirelessnetworks | tail -n +2", + ], postProcess: (out) => out .trim() diff --git a/src/magento.ts b/src/magento.ts index 40a4d5cd2256..ae4f35c30898 100644 --- a/src/magento.ts +++ b/src/magento.ts @@ -49,9 +49,11 @@ const completionSpec: Fig.Spec = { name: "magento", description: "Open-source E-commerce", generateSpec: async (tokens, executeShellCommand) => { - const command = "bin/magento list --format=json --raw"; - const out = await executeShellCommand(command); - const magento = JSON.parse(out) as BinConsoleJSON; + const { stdout } = await executeShellCommand({ + command: "bin/magento", + args: ["list", "--format=json", "--raw"], + }); + const magento = JSON.parse(stdout) as BinConsoleJSON; const cacheTypes = await getCacheTypes(executeShellCommand); return { diff --git a/src/make.ts b/src/make.ts index 6200bd72a96b..a53ee1b94ca1 100644 --- a/src/make.ts +++ b/src/make.ts @@ -1,13 +1,17 @@ const listTargets: Fig.Generator = { custom: async (tokens, executeShellCommand) => { // Plain target suggestions. These will be overridden if we can find a description for them. - const targets = await executeShellCommand( - "make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\\/\\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}' | sort -u" - ); + const { stdout } = await executeShellCommand({ + command: "bash", + args: [ + "-c", + "make -qp | awk -F':' '/^[a-zA-Z0-9][^$#\\/\\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}' | sort -u", + ], + }); const targetSuggestions = new Map(); - for (const target of targets.split("\n")) { + for (const target of stdout.split("\n")) { if (target === "Makefile") continue; targetSuggestions.set(target, { name: target.trim(), @@ -17,9 +21,12 @@ const listTargets: Fig.Generator = { }); } - const out = await executeShellCommand("cat [Mm]akefile"); + const { stdout: makefile } = await executeShellCommand({ + command: "cat", + args: ["Makefile", "makefile"], + }); - const matches = out.matchAll( + const matches = makefile.matchAll( /((?:^#.*\n)*)(?:^\.[A-Z_]+:.*\n)*(^\S*?):.*?(?:\s#+[ \t]*(.+))?$/gm ); const specialTargets = new Set([ diff --git a/src/mask.ts b/src/mask.ts index 0b164225d7d4..660321b75d5e 100644 --- a/src/mask.ts +++ b/src/mask.ts @@ -9,17 +9,25 @@ const completionSpec: Fig.Spec = { var maskfileLocationIdx = tokens.indexOf("--maskfile"); - var out; + var out: string; // mask --maskfile path/tp/thing build if (maskfileLocationIdx < 0 || maskfileLocationIdx + 3 > tokens.length) { - out = await executeShellCommand("cat maskfile.md 2> /dev/null"); + const { stdout } = await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["maskfile.md"], + }); + out = stdout; } else { - out = await executeShellCommand( - `\\cat ${tokens[maskfileLocationIdx + 1]} 2> /dev/null` - ); + const { stdout } = await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [tokens[maskfileLocationIdx + 1]], + }); + out = stdout; } - if (!out) return { name: "null" }; + if (out === "") return { name: "null" }; return { name: "mask", diff --git a/src/mkinitcpio.ts b/src/mkinitcpio.ts index f87b1e902a4a..ec9673336fb2 100644 --- a/src/mkinitcpio.ts +++ b/src/mkinitcpio.ts @@ -93,7 +93,7 @@ const completionSpec: Fig.Spec = { args: { name: "preset", generators: { - script: ["ls","/etc/mkinitcpio.d"], + script: ["ls", "/etc/mkinitcpio.d"], postProcess: (out) => out .trim() diff --git a/src/multipass.ts b/src/multipass.ts index 1ef9c36c4cf0..6b7cbffa8f80 100644 --- a/src/multipass.ts +++ b/src/multipass.ts @@ -44,7 +44,7 @@ const sharedOpts: Record = { const multipassGenerators: Record = { allAvailableImages: { - script: ["multipass","find","--format=json"], + script: ["multipass", "find", "--format=json"], postProcess: (out) => { const images = JSON.parse(out).images; return Object.keys(images).map((key) => { @@ -56,7 +56,7 @@ const multipassGenerators: Record = { }, }, allAvailableInstances: { - script: ["multipass","list","--format=json"], + script: ["multipass", "list", "--format=json"], postProcess: (out) => { return JSON.parse(out).list.map((instance) => { if (instance.state !== "Deleted") { @@ -69,7 +69,7 @@ const multipassGenerators: Record = { }, }, allRunningInstances: { - script: ["multipass","list","--format=json"], + script: ["multipass", "list", "--format=json"], postProcess: (out) => { return JSON.parse(out).list.map((instance) => { if (instance.state === "Running") { @@ -82,7 +82,7 @@ const multipassGenerators: Record = { }, }, allStoppedInstances: { - script: ["multipass","list","--format=json"], + script: ["multipass", "list", "--format=json"], postProcess: (out) => { return JSON.parse(out).list.map((instance) => { if (instance.state === "Stopped") { @@ -95,7 +95,7 @@ const multipassGenerators: Record = { }, }, allDeletedInstances: { - script: ["multipass","list","--format=json"], + script: ["multipass", "list", "--format=json"], postProcess: (out) => { return JSON.parse(out).list.map((instance) => { if (instance.state === "Deleted") { diff --git a/src/nextflow.ts b/src/nextflow.ts index 1b993ac28113..4894cee25486 100644 --- a/src/nextflow.ts +++ b/src/nextflow.ts @@ -1,5 +1,5 @@ const sessionid: Fig.Generator = { - script: "cat .nextflow/history | awk '{ print $7 }'", + script: ["bash", "-c", "cat .nextflow/history | awk '{ print $7 }'"], postProcess: (output) => { if (output == "") { return []; @@ -14,7 +14,7 @@ const sessionid: Fig.Generator = { }; const runname: Fig.Generator = { - script: "cat .nextflow/history | awk '{ print $4 }'", + script: ["bash", "-c", "cat .nextflow/history | awk '{ print $4 }'"], postProcess: (output) => { if (output == "") { return []; @@ -26,7 +26,11 @@ const runname: Fig.Generator = { }; const projectname: Fig.Generator = { - script: `/bin/sh -c "{ find * -maxdepth 0 -type f -name '*.nf' 2> /dev/null && find $HOME/.nextflow/assets/* -maxdepth 1 -type d | cut -d/ -f6,7 | grep / | grep -v assets; } 2> /dev/null"`, + script: [ + "bash", + "-c", + `{ find * -maxdepth 0 -type f -name '*.nf' 2> /dev/null && find $HOME/.nextflow/assets/* -maxdepth 1 -type d | cut -d/ -f6,7 | grep / | grep -v assets; } 2> /dev/null`, + ], postProcess: (output) => { if (output == "") { return []; @@ -41,7 +45,7 @@ const projectname: Fig.Generator = { }; const dockerimage: Fig.Generator = { - script: `docker images | cut -w -f 1 | grep -v REPOSITORY`, + script: ["bash", "-c", "docker images | cut -w -f 1 | grep -v REPOSITORY"], postProcess: (output) => { if (output == "") { return []; @@ -56,7 +60,11 @@ const dockerimage: Fig.Generator = { }; const secretname: Fig.Generator = { - script: `grep -o '"name": *"[^"]*"' $HOME/.nextflow/secrets/store.json | grep -o '"[^"]*"$' | tr -d \\"`, + script: [ + "bash", + "-c", + `grep -o '"name": *"[^"]*"' $HOME/.nextflow/secrets/store.json | grep -o '"[^"]*"$' | tr -d \\"`, + ], postProcess: (output) => { if (output == "") { return []; diff --git a/src/node.ts b/src/node.ts index 9ec32fd5785e..48207fa70974 100644 --- a/src/node.ts +++ b/src/node.ts @@ -55,8 +55,15 @@ const completionSpec: Fig.Subcommand = { }, ], generateSpec: async (tokens, executeShellCommand) => { - const isAdonisJsonPresentCommand = "test -f .adonisrc.json && echo '1'"; - if ((await executeShellCommand(isAdonisJsonPresentCommand)) === "1") { + const isAdonisJsonPresentCommand = "test -f .adonisrc.json"; + if ( + ( + await executeShellCommand({ + command: "bash", + args: ["-c", "isAdonisJsonPresentCommand"], + }) + ).status === 0 + ) { return { name: "node", subcommands: [ diff --git a/src/npm.ts b/src/npm.ts index e37eaa3b46e7..0ea2647e9f9e 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -18,7 +18,7 @@ export const createNpmSearchHandler = (keywords?: string[]) => async ( context: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, shellContext: Fig.ShellContext ): Promise => { const searchTerm = context[context.length - 1]; @@ -34,19 +34,26 @@ export const createNpmSearchHandler = : `https://api.npms.io/v2/search/suggestions?q=${searchTerm}&size=20`; // Query the API with the package name - const queryPackages = `curl -s -H "Accept: application/json" "${queryPackagesUrl}"`; + const queryPackages = [ + "-s", + "-H", + "Accept: application/json", + queryPackagesUrl, + ]; // We need to remove the '@' at the end of the searchTerm before querying versions - const queryVersions = `curl -s -H "Accept: application/vnd.npm.install-v1+json" https://registry.npmjs.org/${searchTerm.slice( - 0, - -1 - )}`; + const queryVersions = [ + "-s", + "-H", + "Accept: application/vnd.npm.install-v1+json", + `https://registry.npmjs.org/${searchTerm.slice(0, -1)}`, + ]; // If the end of our token is '@', then we want to generate version suggestions // Otherwise, we want packages const out = (query: string) => - query[query.length - 1] === "@" - ? executeShellCommand(queryVersions) - : executeShellCommand(queryPackages); - + executeShellCommand({ + command: "curl", + args: query[query.length - 1] === "@" ? queryVersions : queryPackages, + }); // If our token starts with '@', then a 2nd '@' tells us we want // versions. // Otherwise, '@' anywhere else in the string will indicate the same. @@ -55,7 +62,7 @@ export const createNpmSearchHandler = : searchTerm.includes("@"); try { - const data = JSON.parse(await out(searchTerm)); + const data = JSON.parse((await out(searchTerm)).stdout); if (shouldGetVersion) { // create dist tags suggestions const versions = Object.entries(data["dist-tags"] || {}).map( @@ -107,10 +114,21 @@ export const npmSearchGenerator: Fig.Generator = { }; const workspaceGenerator: Fig.Generator = { - script: "cat $(npm prefix)/package.json", - postProcess: function (out: string) { - const suggestions = []; + // script: "cat $(npm prefix)/package.json", + custom: async (tokens, executeShellCommand) => { + const { stdout: npmPrefix } = await executeShellCommand({ + command: "npm", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["prefix"], + }); + + const { stdout: out } = await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [`${npmPrefix}/package.json`], + }); + const suggestions = []; try { if (out.trim() == "") { return suggestions; @@ -139,7 +157,16 @@ export const dependenciesGenerator: Fig.Generator = { trigger: (newToken) => newToken === "-g" || newToken === "--global", custom: async function (tokens, executeShellCommand) { if (!tokens.includes("-g") && !tokens.includes("--global")) { - const out = await executeShellCommand("cat $(npm prefix)/package.json"); + const { stdout: npmPrefix } = await executeShellCommand({ + command: "npm", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["prefix"], + }); + const { stdout: out } = await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [`${npmPrefix}/package.json`], + }); const packageContent = JSON.parse(out); const dependencies = packageContent["dependencies"] ?? {}; const devDependencies = packageContent["devDependencies"]; @@ -161,8 +188,11 @@ export const dependenciesGenerator: Fig.Generator = { : "devDependency", })); } else { - const out = await executeShellCommand("ls -1 `npm root -g`"); - return out.split("\n").map((name) => ({ + const { stdout } = await executeShellCommand({ + command: "bash", + args: ["-c", "ls -1 `npm root -g`"], + }); + return stdout.split("\n").map((name) => ({ name, icon: "📦", description: "Global dependency", @@ -177,8 +207,11 @@ export const npmScriptsGenerator: Fig.Generator = { strategy: "stale-while-revalidate", cacheByDirectory: true, }, - script: + script: [ + "bash", + "-c", "until [[ -f package.json ]] || [[ $PWD = '/' ]]; do cd ..; done; cat package.json", + ], postProcess: function (out, [npmClient]) { if (out.trim() == "") { return []; diff --git a/src/nx.ts b/src/nx.ts index 2dd9b1ff73fa..81c56966577b 100644 --- a/src/nx.ts +++ b/src/nx.ts @@ -121,7 +121,7 @@ const fillProjectCaches = (projectJson: NxProject) => { }; const preProcessProjects = async ( - executeShellCommand: Fig.ExecuteShellCommandFunction + executeShellCommand: Fig.ExecuteCommandFunction ) => { if (!nxProjectPathCache.length) { // get project json paths @@ -129,13 +129,24 @@ const preProcessProjects = async ( const { appsDir, libsDir }: { appsDir: string; libsDir: string } = { appsDir: "apps", libsDir: "libs", - ...JSON.parse(await executeShellCommand("cat nx.json")).workspaceLayout, + ...JSON.parse( + ( + await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["nx.json"], + }) + ).stdout + ).workspaceLayout, }; const searchFolders = appsDir === libsDir ? appsDir : `${appsDir} ${libsDir}`; nxProjectPathCache = ( - await executeShellCommand(`find ${searchFolders} -name "project.json"`) - ) + await executeShellCommand({ + command: "find", + args: [searchFolders, "-name", "project.json"], + }) + ).stdout .split("\n") .filter((path) => !!path); } catch (error) { @@ -149,7 +160,13 @@ const preProcessProjects = async ( if (!projectJson) { try { projectJson = JSON.parse( - await executeShellCommand(`cat "${projectJsonPath}"`) + ( + await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [projectJsonPath], + }) + ).stdout ); nxProjectPathWithJsonCache.set(projectJsonPath, projectJson); @@ -167,7 +184,13 @@ const preProcessProjects = async ( if (!nxProjectPathCache.length) { try { nxWorkspaceJsonCache = JSON.parse( - await executeShellCommand(`cat "workspace.json"`) + ( + await executeShellCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["workspace.json"], + }) + ).stdout ); // fill project caches @@ -191,7 +214,7 @@ const listMapKeysGenerator = (map: Map): Fig.Generator => { getQueryTerm: (token) => token.split(",").pop(), custom: async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, generatorContext: Fig.GeneratorContext ) => { const suggestions: Fig.Suggestion[] = []; @@ -214,7 +237,7 @@ const nxGenerators: NxGenerators = { cache: oneDayCache, custom: async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction + executeShellCommand: Fig.ExecuteCommandFunction ) => { const suggestions: Fig.Suggestion[] = []; @@ -245,9 +268,9 @@ const nxGenerators: NxGenerators = { script: (context) => { const argument = context.slice(-1)[0]; if (argument.indexOf(":") > -1) { - return `nx list ${argument.split(":")[0]}`; + return ["nx", "list", argument.split(":")[0]]; } else { - return "nx list"; + return ["nx", "list"]; } }, trigger: (newToken, oldToken) => @@ -295,7 +318,7 @@ const nxGenerators: NxGenerators = { // the custom generator custom: async ( _: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction + executeShellCommand: Fig.ExecuteCommandFunction ) => { // suggestions to be returned const suggestions: Fig.Suggestion[] = []; @@ -323,7 +346,7 @@ const nxGenerators: NxGenerators = { // the custom generator custom: async ( tokens: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction, + executeShellCommand: Fig.ExecuteCommandFunction, generatorContext: Fig.GeneratorContext ) => { // suggestions to be returned @@ -370,7 +393,7 @@ const nxGenerators: NxGenerators = { }, targets: listMapKeysGenerator(nxTargetWithProjectsCache), workspaceGenerator: { - script: "ls -d tools/generators/*/", + script: ["bash", "-c", "ls -d tools/generators/*/"], cache: oneDayCache, postProcess: (out) => out @@ -665,7 +688,7 @@ const RUN_DERIVED_BASE_TARGETS_WITH_CONFIGURATION = ["build", "serve"]; */ const runDerivedSubcommands = async ( _: string[], - executeShellCommand: Fig.ExecuteShellCommandFunction + executeShellCommand: Fig.ExecuteCommandFunction ): Promise => { const subcommands: Fig.Subcommand[] = []; diff --git a/src/passwd.ts b/src/passwd.ts index 83092153268a..92a80dcad1ab 100644 --- a/src/passwd.ts +++ b/src/passwd.ts @@ -1,5 +1,5 @@ const generateUsers: Fig.Generator = { - script: "dscl . -list /Users | grep -E -v '^_'", + script: ["bash", "-c", "dscl . -list /Users | grep -E -v '^_'"], postProcess: (out) => out .trim() diff --git a/src/pnpm.ts b/src/pnpm.ts index 8d57d54e39c6..1278c2f99430 100644 --- a/src/pnpm.ts +++ b/src/pnpm.ts @@ -964,10 +964,17 @@ const completionSpec: Fig.Spec = { }, filterStrategy: "fuzzy", generateSpec: async (tokens, executeShellCommand) => { - const { script, postProcess } = dependenciesGenerator; + const { script, postProcess } = dependenciesGenerator as Fig.Generator & { + script: string[]; + }; const packages = postProcess( - await executeShellCommand(script as string), + ( + await executeShellCommand({ + command: script[0], + args: script.slice(1), + }) + ).stdout, tokens ).map(({ name }) => name as string); diff --git a/src/pre-commit.ts b/src/pre-commit.ts index bfaabcfc3ce3..3d384122a139 100644 --- a/src/pre-commit.ts +++ b/src/pre-commit.ts @@ -2,7 +2,7 @@ import YAML from "yaml"; import { gitGenerators } from "./git"; const hooksInConfig: Fig.Generator = { - script: ["cat",".pre-commit-config.yaml"], + script: ["cat", ".pre-commit-config.yaml"], postProcess: (output) => { const suggestions: Fig.Suggestion[] = []; diff --git a/src/python.ts b/src/python.ts index 555dbfb0c012..e8ad1793d0e3 100644 --- a/src/python.ts +++ b/src/python.ts @@ -4,11 +4,14 @@ const completionSpec: Fig.Spec = { name: "python", description: "Run the python interpreter", generateSpec: async (tokens, executeShellCommand) => { - const isDjangoManagePyFilePresentCommand = - "cat manage.py | grep -q django; echo $?"; - + const isDjangoManagePyFilePresentCommand = "cat manage.py | grep -q django"; if ( - (await executeShellCommand(isDjangoManagePyFilePresentCommand)) === "0" + ( + await executeShellCommand({ + command: "bash", + args: ["-c", isDjangoManagePyFilePresentCommand], + }) + ).status === 0 ) { return { name: "python", diff --git a/src/python3.ts b/src/python3.ts index a898a4545dd4..7e763c3906d2 100644 --- a/src/python3.ts +++ b/src/python3.ts @@ -4,11 +4,15 @@ const completionSpec: Fig.Spec = { name: "python3", description: "Run the python interpreter", generateSpec: async (tokens, executeShellCommand) => { - const isDjangoManagePyFilePresentCommand = - "cat manage.py | grep -q django; echo $?"; + const isDjangoManagePyFilePresentCommand = "cat manage.py | grep -q django"; if ( - (await executeShellCommand(isDjangoManagePyFilePresentCommand)) === "0" + ( + await executeShellCommand({ + command: "bash", + args: ["-c", isDjangoManagePyFilePresentCommand], + }) + ).status === 0 ) { return { name: "python3", diff --git a/src/rails.ts b/src/rails.ts index cef2246569e6..74952848a192 100644 --- a/src/rails.ts +++ b/src/rails.ts @@ -581,7 +581,10 @@ const defaultCommands: Fig.Subcommand[] = [ isOptional: true, }, async generateSpec(_, executeShellCommand) { - const helpText = await executeShellCommand("rails test --help"); + const { stdout: helpText } = await executeShellCommand({ + command: "rails", + args: ["test", "--help"], + }); const argRegex = /(?:(-[a-zA-Z]), )?(--[^ ]+?)[ =]([A-Z_]+)?[ \r\n]+([^\n]+)/g; @@ -606,7 +609,11 @@ export const railsCommandsGenerator: Fig.Generator = { // parse help text to find more commands let commands: Fig.Subcommand[] = []; try { - const helpText = await executeShellCommand("rails --tasks"); + const { stdout: helpText } = await executeShellCommand({ + command: "rails", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["--tasks"], + }); const defaultCommandNames = defaultCommands.map((c) => c.name); const matches = Array.from(helpText.matchAll(/rails ([^ ]+)/g)); @@ -631,9 +638,16 @@ const completionSpec: Fig.Spec = { description: "Ruby on Rails CLI", icon: "https://avatars.githubusercontent.com/u/4223?s=48&v=4", generateSpec: async (_, executeShellCommand) => { - const isRailsDirectory = !!(await executeShellCommand( - `until [[ -f Gemfile ]] || [[ $PWD = '/' ]]; do cd ..; done; if [ -f Gemfile ]; then cat Gemfile | \\grep "gem ['\\"]rails['\\"]"; fi` - )); + const isRailsDirectory = + ( + await executeShellCommand({ + command: "bash", + args: [ + "-c", + `until [[ -f Gemfile ]] || [[ $PWD = '/' ]]; do cd ..; done; if [ -f Gemfile ]; then cat Gemfile | \\grep "gem ['\\"]rails['\\"]"; fi`, + ], + }) + ).status === 0; if (!isRailsDirectory) { return { diff --git a/src/react-native.ts b/src/react-native.ts index 997e9b9fc3c0..666f76d1915d 100644 --- a/src/react-native.ts +++ b/src/react-native.ts @@ -14,7 +14,7 @@ const getJsFilesAndFolders = filepaths({ }); const workerGenerator = { - script: ["sysctl","-n","hw.ncpu"], + script: ["sysctl", "-n", "hw.ncpu"], postProcess: (scriptOutput: string) => { return Array.from({ length: Number(scriptOutput) }, (_x, i) => ({ name: `${i}`, @@ -22,7 +22,7 @@ const workerGenerator = { }, }; const xcodeConfigGenerator = { - script: "xcodebuild -project ios/*.xcodeproj -list -json", + script: ["bash", "-c", "xcodebuild -project ios/*.xcodeproj -list -json"], postProcess: (scriptOutput: string) => { const configurations = JSON.parse(scriptOutput).project.configurations; @@ -31,7 +31,7 @@ const xcodeConfigGenerator = { }; const xcodeSchemeGenerator = { - script: "xcodebuild -project ios/*.xcodeproj -list -json", + script: ["bash", "-c", "xcodebuild -project ios/*.xcodeproj -list -json"], postProcess: (scriptOutput: string) => { const configurations = JSON.parse(scriptOutput).project.schemes; @@ -90,7 +90,7 @@ const iosGetDevicesGenerator = { }; const iosGetDevicesUdidGenerator = { - script: "xcrun xctrace list devices", + script: ["bash", "-c", "xcrun xctrace list devices"], postProcess: (scriptOutput: string) => { const devices = scriptOutput .split("\n") @@ -106,8 +106,8 @@ const iosGetDevicesUdidGenerator = { }, }; -const gradleTasksGenerator = { - script: "cd android/ && ./gradlew tasks", +const gradleTasksGenerator: Fig.Generator = { + script: ["bash", "-c", "cd android/ && ./gradlew tasks"], postProcess: (scriptOutput: string) => { const tasks = scriptOutput .split("\n") diff --git a/src/robot.ts b/src/robot.ts index 426a2cf40cd5..a0c42ec02a88 100644 --- a/src/robot.ts +++ b/src/robot.ts @@ -1,8 +1,11 @@ import { filepaths } from "@fig/autocomplete-generators"; const tagsGenerator: Fig.Generator = { - script: + script: [ + "bash", + "-c", 'for i in $(find -E . -regex ".*.robot" -type f); do cat -s $i ; done', + ], postProcess: (out) => { // find all lines with tags // regex: line that starts with 2+ spaces, than '[Tags] ' and words @@ -34,10 +37,14 @@ const variablesGenerator: Fig.Generator = { const finalToken = tokens[tokens.length - 1]; const isKey = !finalToken.includes(":"); if (!isKey) return []; - const out = await executeShellCommand( - 'for i in $(find -E . -regex ".*.(robot|resource)" -type f); do cat -s $i ; done' - ); - const iter = out.matchAll(/^\$\{(.*?)\}/gm); + const { stdout } = await executeShellCommand({ + command: "bash", + args: [ + "-c", + 'for i in $(find -E . -regex ".*.(robot|resource)" -type f); do cat -s $i ; done', + ], + }); + const iter = stdout.matchAll(/^\$\{(.*?)\}/gm); return [...iter] .map((item) => item[1]) .map((variable) => ({ @@ -48,8 +55,11 @@ const variablesGenerator: Fig.Generator = { }; const testCasesGenerator: Fig.Generator = { - script: + script: [ + "bash", + "-c", 'for i in $(find -E . -regex ".*.robot" -type f); do cat -s $i ; done', + ], postProcess: (out) => { // find all parts of the code with test cases // regex: everything after '***Test Cases***' until '***???***') diff --git a/src/rugby.ts b/src/rugby.ts index cb7bab9d6f1b..93c70824c3d3 100644 --- a/src/rugby.ts +++ b/src/rugby.ts @@ -21,14 +21,17 @@ const completionSpec: Fig.Spec = { "Cache Cocoa 🌱 pods for faster rebuild and indexing Xcode project. https://github.com/swiftyfinch/Rugby", name: "rugby", generateSpec: async (tokens, executeShellCommand) => { - const output = await executeShellCommand("rugby plan list"); - if (output === "") { + const { stdout } = await executeShellCommand({ + command: "rugby", + args: ["plan", "list"], + }); + if (stdout === "") { return null; } // Handle `rugby umbrella` command return { name: "plan", - subcommands: output.split("\n").map((plan) => { + subcommands: stdout.split("\n").map((plan) => { return { name: plan, description: `Run plan \"${plan}\"`, diff --git a/src/rustup.ts b/src/rustup.ts index 78a28aa437fb..5f861d56e8ae 100644 --- a/src/rustup.ts +++ b/src/rustup.ts @@ -887,10 +887,10 @@ const completionSpec: Fig.Spec = { isOptional: true, }, generateSpec: async (_tokens, executeShellCommand) => { - const [toolchainOutput] = await Promise.all([ - executeShellCommand("rustup toolchain list"), - ]); - + const { stdout: toolchainOutput } = await executeShellCommand({ + command: "rustup", + args: ["toolchain", "list"], + }); const toolchains: Fig.Option[] = toolchainOutput .split("\n") .map((toolchain) => { diff --git a/src/scarb.ts b/src/scarb.ts index 498c8efbf137..fdff1751d98d 100644 --- a/src/scarb.ts +++ b/src/scarb.ts @@ -331,7 +331,13 @@ const completionSpec: Fig.Spec = { description: "Packages to run this command on, can be a concrete package name (`foobar`) or a prefix glob (`foo*`) [default: *]", generators: { - script: ["scarb","metadata","--format-version","1","--no-deps"], + script: [ + "scarb", + "metadata", + "--format-version", + "1", + "--no-deps", + ], postProcess: function (out) { const jsonOut = JSON.parse(out); const members = jsonOut.workspace.members; @@ -574,7 +580,13 @@ const completionSpec: Fig.Spec = { description: "Packages to run this command on, can be a concrete package name (`foobar`) or a prefix glob (`foo*`) [default: *]", generators: { - script: ["scarb","metadata","--format-version","1","--no-deps"], + script: [ + "scarb", + "metadata", + "--format-version", + "1", + "--no-deps", + ], postProcess: function (out) { const jsonOut = JSON.parse(out); const members = jsonOut.workspace.members; diff --git a/src/shopify/index.ts b/src/shopify/index.ts index 749b10b9a5b3..b5b7afa2def9 100644 --- a/src/shopify/index.ts +++ b/src/shopify/index.ts @@ -6,7 +6,7 @@ export const getVersionCommand: Fig.GetVersionCommand = async ( const versionRegex = /\d+\.\d+\.\d+/; const { stdout } = await executeShellCommand({ command: "shopify", - args: ["version"], + args: "version", }); return stdout.match(versionRegex)?.[0] ?? ""; }; diff --git a/src/stepzen.ts b/src/stepzen.ts index 7d6789beb8a9..4ce381dd6cb1 100644 --- a/src/stepzen.ts +++ b/src/stepzen.ts @@ -19,7 +19,10 @@ const endpointsGenerator: Fig.Generator = { }; const importSchemasGenerator: Fig.Generator = { - script: ["curl","https://api.github.com/repos/steprz/stepzen-schemas/contents"], + script: [ + "curl", + "https://api.github.com/repos/steprz/stepzen-schemas/contents", + ], postProcess: (output) => { try { return JSON.parse(output) diff --git a/src/tldr.ts b/src/tldr.ts index e938aee2bcb6..2d960b59e473 100644 --- a/src/tldr.ts +++ b/src/tldr.ts @@ -9,11 +9,19 @@ const windows = `${tldrRc}/pages/windows/`; const isMarkDownRegex = new RegExp(/^.*\.md$/); const wholeTldrPages: Fig.Generator = { - script: () => { - return `command ls -Al ${android} ${common} ${linux} ${osx} ${sunos} ${windows} 2>/dev/null`; - }, - postProcess: (out) => { - return out + custom: async (tokens, executeShellCommand, context) => { + const { stdout } = await executeShellCommand({ + command: "ls", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [ + "-Al", + ...[android, common, linux, osx, sunos, windows].map((path) => + path.replace(/^~/, context.environmentVariables["HOME"]) + ), + ], + }); + + return stdout .split("\n") .filter((line) => isMarkDownRegex.test(line)) .map((line) => { @@ -27,9 +35,7 @@ const wholeTldrPages: Fig.Generator = { }; const linuxTldrPages: Fig.Generator = { - script: () => { - return `command ls -Al ${linux} 2>/dev/null`; - }, + script: ["bash", "-c", `command ls -Al ${linux} 2>/dev/null`], postProcess: (out) => { return out .split("\n") @@ -45,9 +51,7 @@ const linuxTldrPages: Fig.Generator = { }; const osxTldrPages: Fig.Generator = { - script: () => { - return `command ls -l ${osx} 2>/dev/null`; - }, + script: ["bash", "-c", `command ls -l ${osx} 2>/dev/null`], postProcess: (out) => { return out .split("\n") @@ -63,9 +67,7 @@ const osxTldrPages: Fig.Generator = { }; const sunosTldrPages: Fig.Generator = { - script: () => { - return `command ls -l ${sunos} 2>/dev/null`; - }, + script: ["bash", "-c", `command ls -l ${sunos} 2>/dev/null`], postProcess: (out) => { return out .split("\n") diff --git a/src/tmux.ts b/src/tmux.ts index ed3a9f639b5c..6dad63129303 100644 --- a/src/tmux.ts +++ b/src/tmux.ts @@ -1,7 +1,7 @@ const lsArg = (name: string, command: string): Fig.Arg => ({ name, generators: { - script: `tmux ${command}`, + script: ["tmux", command], postProcess: (out) => { return out.split("\n").map((line) => { const content = line.split(":"); diff --git a/src/trex.ts b/src/trex.ts index 8a8b1a648328..af7e6c752a6a 100644 --- a/src/trex.ts +++ b/src/trex.ts @@ -1,5 +1,5 @@ const dependenciesGenerator: Fig.Generator = { - script: ["cat","import_map.json"], + script: ["cat", "import_map.json"], postProcess: function (out) { if (out) { try { @@ -24,7 +24,7 @@ const dependenciesGenerator: Fig.Generator = { }, }; const scriptsGenerator: Fig.Generator = { - script: ["cat","run.json"], + script: ["cat", "run.json"], postProcess: function (out) { if (out) { try { diff --git a/src/tsh.ts b/src/tsh.ts index 1a801110db3d..7efc7e047527 100644 --- a/src/tsh.ts +++ b/src/tsh.ts @@ -93,7 +93,7 @@ const completionSpec: Fig.Spec = { name: "user@hostname", description: "Address of remote machine to log into", generators: { - script: ["tsh","ls","--format=json"], + script: ["tsh", "ls", "--format=json"], postProcess: (out) => { return JSON.parse(out).map((elm) => { return { diff --git a/src/vite.ts b/src/vite.ts index 48b8f1c84c19..6f9950834bf6 100644 --- a/src/vite.ts +++ b/src/vite.ts @@ -109,8 +109,14 @@ const completionSpec: Fig.Spec = { args: { name: "mode", generators: { - script: "\\ls -l1A.env.*", - splitOn: "\n", + script: ["ls", "-l1A"], + postProcess: (out) => + out + .split("\n") + .filter((line) => line.startsWith(".env.")) + .map((name) => ({ + name, + })), }, }, }, diff --git a/src/which.ts b/src/which.ts index 29b872c0c991..a32154a4f2b7 100644 --- a/src/which.ts +++ b/src/which.ts @@ -1,5 +1,9 @@ const programGenerator: Fig.Generator = { - script: `for i in $(echo $PATH | tr ":" "\n"); do find $i -maxdepth 1 -perm -111 -type f; done`, + script: [ + "bash", + "-c", + `for i in $(echo $PATH | tr ":" "\n"); do find $i -maxdepth 1 -perm -111 -type f; done`, + ], postProcess: (out) => out .split("\n") diff --git a/src/wifi-password.ts b/src/wifi-password.ts index ac1adbd98aaf..d0a27a0981f4 100644 --- a/src/wifi-password.ts +++ b/src/wifi-password.ts @@ -4,8 +4,11 @@ const completionSpec: Fig.Spec = { name: "SSID", description: "The name for a Wi-Fi network", generators: { - script: + script: [ + "bash", + "-c", "networksetup -listallhardwareports | awk '/Wi-Fi/{getline; print $2}' | xargs networksetup -listpreferredwirelessnetworks", + ], postProcess: (out) => out .split("\n") diff --git a/src/yalc.ts b/src/yalc.ts index 796957a15ca4..0a0430beef7f 100644 --- a/src/yalc.ts +++ b/src/yalc.ts @@ -1,6 +1,10 @@ const generatePackages: Fig.Generator = { // TODO: use the same as for npm and yarn package.json reverse lookup - script: "command find ~/.yalc/packages -maxdepth 4 -iname 'package.json'", + script: [ + "bash", + "-c", + "command find ~/.yalc/packages -maxdepth 4 -iname 'package.json'", + ], postProcess: (out) => out .split("\n") diff --git a/src/yarn.ts b/src/yarn.ts index 81eef2be0947..0d667bfda0a4 100644 --- a/src/yarn.ts +++ b/src/yarn.ts @@ -51,12 +51,22 @@ export const nodeClis = new Set([ // generate global package list from global package.json file const getGlobalPackagesGenerator: Fig.Generator = { - script: 'cat "$(yarn global dir)/package.json"', - postProcess: (out, tokens) => { - if (out.trim() == "") return []; + custom: async (tokens, executeCommand, generatorContext) => { + const { stdout: yarnGlobalDir } = await executeCommand({ + command: "yarn", + args: ["global", "dir"], + }); + + const { stdout } = await executeCommand({ + command: "cat", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: [`${yarnGlobalDir.trim()}/package.json`], + }); + + if (stdout.trim() == "") return []; try { - const packageContent = JSON.parse(out); + const packageContent = JSON.parse(stdout); const dependencyScripts = packageContent["dependencies"] || {}; const devDependencyScripts = packageContent["devDependencies"] || {}; const dependencies = [ @@ -341,9 +351,15 @@ const commonOptions: Fig.Option[] = [ export const createCLIsGenerator: Fig.Generator = { script: function (context) { - if (context[context.length - 1] === "") return ""; + if (context[context.length - 1] === "") return undefined; const searchTerm = "create-" + context[context.length - 1]; - return `curl -s -H "Accept: application/json" "https://api.npms.io/v2/search?q=${searchTerm}&size=20"`; + return [ + "curl", + "-s", + "-H", + "Accept: application/json", + `https://api.npms.io/v2/search?q=${searchTerm}&size=20`, + ]; }, cache: { ttl: 100 * 24 * 60 * 60 * 3, // 3 days @@ -368,10 +384,14 @@ const completionSpec: Fig.Spec = { description: "Manage packages and run scripts", generateSpec: async (tokens, executeShellCommand) => { const binaries = ( - await executeShellCommand( - `until [[ -d node_modules/ ]] || [[ $PWD = '/' ]]; do cd ..; done; ls -1 node_modules/.bin/` - ) - ).split("\n"); + await executeShellCommand({ + command: "bash", + args: [ + "-c", + `until [[ -d node_modules/ ]] || [[ $PWD = '/' ]]; do cd ..; done; ls -1 node_modules/.bin/`, + ], + }) + ).stdout.split("\n"); const subcommands = binaries .filter((name) => nodeClis.has(name)) @@ -1486,17 +1506,26 @@ const completionSpec: Fig.Spec = { description: "Manage workspace", filterStrategy: "fuzzy", generateSpec: async (_tokens, executeShellCommand) => { - const version = await executeShellCommand("yarn --version"); + const version = ( + await executeShellCommand({ + command: "yarn", + // eslint-disable-next-line @withfig/fig-linter/no-useless-arrays + args: ["--version"], + }) + ).stdout; const isYarnV1 = version.startsWith("1."); const getWorkspacesDefinitionsV1 = async () => { - const out = await executeShellCommand(`yarn workspaces info`); + const { stdout } = await executeShellCommand({ + command: "yarn", + args: ["workspaces", "info"], + }); - const startJson = out.indexOf("{"); - const endJson = out.lastIndexOf("}"); + const startJson = stdout.indexOf("{"); + const endJson = stdout.lastIndexOf("}"); return Object.entries( - JSON.parse(out.slice(startJson, endJson + 1)) as Record< + JSON.parse(stdout.slice(startJson, endJson + 1)) as Record< string, { location: string } > @@ -1508,7 +1537,13 @@ const completionSpec: Fig.Spec = { // For yarn >= 2.0.0 const getWorkspacesDefinitionsVOther = async () => { - const out = await executeShellCommand(`yarn workspaces list --json`); + // yarn workspaces list --json + const out = ( + await executeShellCommand({ + command: "yarn", + args: ["workspaces", "list", "--json"], + }) + ).stdout; return out.split("\n").map((line) => JSON.parse(line.trim())); }; @@ -1530,7 +1565,7 @@ const completionSpec: Fig.Spec = { strategy: "stale-while-revalidate", ttl: 60_000, // 60s }, - script: `\\cat ${location}/package.json`, + script: ["cat", `${location}/package.json`], postProcess: function (out: string) { if (out.trim() == "") { return []; diff --git a/src/ykman.ts b/src/ykman.ts index 4c32e821a0c2..813eedfee972 100644 --- a/src/ykman.ts +++ b/src/ykman.ts @@ -2439,7 +2439,11 @@ const completionSpec: Fig.Spec = { args: { name: "SERIAL", generators: { - script: "ykman list | sed -rn 's/.*Serial: (.*)/\\1/p'", + script: [ + "bash", + "-c", + "ykman list | sed -rn 's/.*Serial: (.*)/\\1/p'", + ], postProcess: function (out) { return out.split("\n").map((serial) => { return { name: serial, description: "Yubikey serial" }; diff --git a/src/youtube-dl.ts b/src/youtube-dl.ts index f8427a349f6c..fc574665f4ab 100644 --- a/src/youtube-dl.ts +++ b/src/youtube-dl.ts @@ -943,20 +943,27 @@ const completionSpec: Fig.Spec = { args: { name: "MSO", generators: { - script: (context) => - `youtube-dl ${context.filter((token) => - token.includes("youtube.") - )} --simulate --ap-list-mso | tail -n +3 | tr -s " "`, + custom: async (tokens, executeCommand, context) => { + const { stdout } = await executeCommand({ + command: "youtube-dl", + args: [ + ...tokens.filter((token) => token.includes("youtube.")), + "--simulate", + "--ap-list-mso", + ], + }); - postProcess: (out) => - out.split("\n").map((line) => { - const [name, ...description] = line.split(" "); - - return { - name, - description: description.join(" "), - }; - }), + return stdout + .split("\n") + .slice(3) + .map((line) => { + const [name, ...description] = line.split(" "); + return { + name, + description: description.join(" "), + }; + }); + }, }, }, }, diff --git a/src/z.ts b/src/z.ts index 4487a35f8bfa..e170aaa30d6a 100644 --- a/src/z.ts +++ b/src/z.ts @@ -30,10 +30,13 @@ async function getZHistory( async function getCurrentDirectoryFolders( currentWorkingDirectory: string, - execute: Fig.ExecuteShellCommandFunction + execute: Fig.ExecuteCommandFunction ): Promise { - const out = await execute("ls -d */"); - return out.split("\n").map((line) => { + const { stdout } = await execute({ + command: "bash", + args: ["-c", "ls -d */"], + }); + return stdout.split("\n").map((line) => { const name = line.replace("/", ""); return { name, @@ -143,7 +146,7 @@ const zoxideCompletionSpec: Fig.Spec = { args, }); - return out.split("\n").map((line) => { + return stdout.split("\n").map((line) => { const trimmedLine = line.trim(); const spaceIndex = trimmedLine.indexOf(" "); const score = Number(trimmedLine.slice(0, spaceIndex));