sshya 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/index.ts +15 -1
- package/package.json +1 -1
- package/src/connection/add.ts +6 -0
- package/src/connection/connect.ts +10 -2
- package/src/connection/copy.ts +119 -0
- package/src/connection/import.ts +6 -0
- package/src/connection/index.ts +1 -0
- package/src/connection/list.ts +4 -50
- package/src/connection/update.ts +6 -0
- package/src/helpers/selectAlias.ts +49 -28
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ This project is a command-line interface (CLI) tool named `sshya` for managing S
|
|
|
5
5
|
## Core Functionality
|
|
6
6
|
- Add, remove, update, and list SSH connection configurations (alias, user, host, port, key path, remote path).
|
|
7
7
|
- Connect to saved connections with `sshya connect` (aliases: `ssh`, `go`).
|
|
8
|
+
- Copy files through saved connections with `sshya copy` (aliases: `sc`, `scp`).
|
|
8
9
|
- Generate SSH command strings for scripting with `sshya print`.
|
|
9
10
|
- Import and export connections from/to a JSON file.
|
|
10
11
|
- fzf-based launcher for fast, keyboard-driven connection selection.
|
|
@@ -63,6 +64,28 @@ sshya connect my-server
|
|
|
63
64
|
sshya connect # interactive picker
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
### Copy files
|
|
68
|
+
|
|
69
|
+
Use stored connections for `scp` uploads and downloads:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
sshya copy my-server ./local.txt /tmp/remote.txt
|
|
73
|
+
sshya copy ./local.txt /tmp/remote.txt # interactive connection picker
|
|
74
|
+
sshya copy --download my-server /tmp/remote.txt ./
|
|
75
|
+
sshya copy --recursive my-server ./dist /var/www
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The `copy` command also works as `sshya sc` or `sshya scp`. For a short no-fzf wrapper:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
sc() {
|
|
82
|
+
sshya copy "$@"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Relative remote paths are resolved from the connection's saved remote working directory when one is configured.
|
|
87
|
+
If an exact alias is not found, `connect` and `copy` use the first fuzzy match from the same search used by the interactive picker.
|
|
88
|
+
|
|
66
89
|
### Troubleshooting
|
|
67
90
|
|
|
68
91
|
- If fzf filtering looks wrong, reload your shell after updating the snippet. The launcher should call `sshya connect`, not parse `sshya print` output.
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import pkg from './package.json' assert { type: "json" };
|
|
6
|
-
import { addConnectionPrompt, connectConnectionPrompt, exportConnectionsPrompt, importConnectionsPrompt, listConnectionsPrompt, removeConnectionPrompt, testConnectionPrompt, updateConnectionPrompt } from './src/connection';
|
|
6
|
+
import { addConnectionPrompt, connectConnectionPrompt, copyConnectionPrompt, exportConnectionsPrompt, importConnectionsPrompt, listConnectionsPrompt, removeConnectionPrompt, testConnectionPrompt, updateConnectionPrompt } from './src/connection';
|
|
7
7
|
import { initDB } from './src/database';
|
|
8
8
|
import { printConnectionsPrompt } from './src/helpers/connection';
|
|
9
9
|
import { enableEscapeExit } from './src/helpers/escExit';
|
|
@@ -30,6 +30,20 @@ program
|
|
|
30
30
|
.description('Connect to an SSH connection by alias')
|
|
31
31
|
.action(connectConnectionPrompt);
|
|
32
32
|
|
|
33
|
+
program
|
|
34
|
+
.command('copy [alias] [source] [destination]')
|
|
35
|
+
.aliases(['sc', 'scp'])
|
|
36
|
+
.description('Copy files using a stored SSH connection')
|
|
37
|
+
.option('-d, --download', 'Copy from the remote host to a local destination')
|
|
38
|
+
.option('-r, --recursive', 'Copy directories recursively')
|
|
39
|
+
.option('-p, --preserve', 'Preserve modification times, access times, and modes')
|
|
40
|
+
.action((alias?: string, source?: string, destination?: string, options?: { download?: boolean; recursive?: boolean; preserve?: boolean }) => {
|
|
41
|
+
return copyConnectionPrompt(alias, source, destination, {
|
|
42
|
+
download: !!options?.download,
|
|
43
|
+
recursive: !!options?.recursive,
|
|
44
|
+
preserve: !!options?.preserve,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
33
47
|
|
|
34
48
|
program
|
|
35
49
|
.command('print [alias]')
|
package/package.json
CHANGED
package/src/connection/add.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
3
|
import { addConnection, connectionSchema } from "../database";
|
|
4
|
+
import { enableEscapeExit } from "../helpers/escExit";
|
|
4
5
|
import { testConnectionPrompt } from "./test";
|
|
5
6
|
|
|
6
7
|
export async function addConnectionPrompt() {
|
|
8
|
+
const cleanup = enableEscapeExit();
|
|
9
|
+
try {
|
|
7
10
|
const answers = await inquirer.prompt([
|
|
8
11
|
{
|
|
9
12
|
type: 'input',
|
|
@@ -64,4 +67,7 @@ export async function addConnectionPrompt() {
|
|
|
64
67
|
} catch (err: any) {
|
|
65
68
|
console.error(chalk.red(err.message));
|
|
66
69
|
}
|
|
70
|
+
} finally {
|
|
71
|
+
cleanup();
|
|
72
|
+
}
|
|
67
73
|
}
|
|
@@ -2,14 +2,22 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import { expandHomePath, getConnectionByAlias, recordConnectionUsage } from '../database';
|
|
4
4
|
import { buildRemoteCommand } from '../helpers/shell';
|
|
5
|
-
import { selectAlias } from '../helpers/selectAlias';
|
|
5
|
+
import { findAliasBySearchTerm, selectAlias } from '../helpers/selectAlias';
|
|
6
6
|
|
|
7
7
|
export async function connectConnectionPrompt(alias?: string): Promise<void> {
|
|
8
8
|
if (!alias) {
|
|
9
9
|
alias = await selectAlias('Select a connection to connect');
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
let connection = getConnectionByAlias(alias);
|
|
13
|
+
if (!connection) {
|
|
14
|
+
const matchedAlias = findAliasBySearchTerm(alias);
|
|
15
|
+
if (matchedAlias) {
|
|
16
|
+
alias = matchedAlias;
|
|
17
|
+
connection = getConnectionByAlias(alias);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
if (!connection) {
|
|
14
22
|
console.error(chalk.red('Alias not found'));
|
|
15
23
|
process.exit(1);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { expandHomePath, getConnectionByAlias, recordConnectionUsage, type Connection } from '../database';
|
|
5
|
+
import { findAliasBySearchTerm, selectAlias } from '../helpers/selectAlias';
|
|
6
|
+
|
|
7
|
+
interface CopyOptions {
|
|
8
|
+
download?: boolean;
|
|
9
|
+
recursive?: boolean;
|
|
10
|
+
preserve?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface NormalizedCopyArgs {
|
|
14
|
+
alias?: string;
|
|
15
|
+
source?: string;
|
|
16
|
+
destination?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isAbsoluteRemotePath = (value: string): boolean => {
|
|
20
|
+
return value.startsWith('/') || value.startsWith('~/') || value === '~';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const resolveRemotePath = (connection: Connection, value: string): string => {
|
|
24
|
+
const remotePath = value.trim();
|
|
25
|
+
const remoteBase = connection.remote_path?.trim();
|
|
26
|
+
|
|
27
|
+
if (!remoteBase || isAbsoluteRemotePath(remotePath)) {
|
|
28
|
+
return remotePath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return path.posix.join(remoteBase, remotePath);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const buildRemoteSpec = (connection: Connection, remotePath: string): string => {
|
|
35
|
+
return `${connection.user}@${connection.host}:${resolveRemotePath(connection, remotePath)}`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const normalizeCopyArgs = (first?: string, second?: string, third?: string): NormalizedCopyArgs => {
|
|
39
|
+
if (third) {
|
|
40
|
+
return { alias: first, source: second, destination: third };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (first && second) {
|
|
44
|
+
const maybeConnection = getConnectionByAlias(first);
|
|
45
|
+
if (maybeConnection) {
|
|
46
|
+
return { alias: first, source: second };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { source: first, destination: second };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { alias: first, source: second, destination: third };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export async function copyConnectionPrompt(
|
|
56
|
+
first?: string,
|
|
57
|
+
second?: string,
|
|
58
|
+
third?: string,
|
|
59
|
+
options: CopyOptions = {},
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const { download = false, recursive = false, preserve = false } = options;
|
|
62
|
+
let { alias, source, destination } = normalizeCopyArgs(first, second, third);
|
|
63
|
+
|
|
64
|
+
if (!source || !destination) {
|
|
65
|
+
console.error(chalk.red('Source and destination are required'));
|
|
66
|
+
console.error(chalk.grey('Usage: sshya copy [alias] <source> <destination>'));
|
|
67
|
+
console.error(chalk.grey(' sshya copy --download [alias] <remote-source> <local-destination>'));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!alias) {
|
|
72
|
+
alias = await selectAlias(download ? 'Select a connection to copy from' : 'Select a connection to copy to');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let connection = getConnectionByAlias(alias);
|
|
76
|
+
if (!connection) {
|
|
77
|
+
const matchedAlias = findAliasBySearchTerm(alias);
|
|
78
|
+
if (matchedAlias) {
|
|
79
|
+
alias = matchedAlias;
|
|
80
|
+
connection = getConnectionByAlias(alias);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!connection) {
|
|
85
|
+
console.error(chalk.red('Alias not found'));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const args: string[] = [];
|
|
90
|
+
if (connection.key_path) {
|
|
91
|
+
args.push('-i', expandHomePath(connection.key_path) ?? connection.key_path);
|
|
92
|
+
}
|
|
93
|
+
if (connection.port) {
|
|
94
|
+
args.push('-P', String(connection.port));
|
|
95
|
+
}
|
|
96
|
+
if (recursive) {
|
|
97
|
+
args.push('-r');
|
|
98
|
+
}
|
|
99
|
+
if (preserve) {
|
|
100
|
+
args.push('-p');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (download) {
|
|
104
|
+
args.push(buildRemoteSpec(connection, source), destination);
|
|
105
|
+
} else {
|
|
106
|
+
args.push(source, buildRemoteSpec(connection, destination));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
recordConnectionUsage(alias);
|
|
110
|
+
|
|
111
|
+
const child = spawn('scp', args, { stdio: 'inherit' });
|
|
112
|
+
child.on('error', (error) => {
|
|
113
|
+
console.error(chalk.red(`Failed to start scp: ${error.message}`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
child.on('close', (code) => {
|
|
117
|
+
process.exit(code ?? 1);
|
|
118
|
+
});
|
|
119
|
+
}
|
package/src/connection/import.ts
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { addConnection, connectionSchema, getConnectionByAlias, updateConnection } from '../database';
|
|
5
|
+
import { enableEscapeExit } from '../helpers/escExit';
|
|
5
6
|
|
|
6
7
|
export const importConnectionsPrompt = async (file: string) => {
|
|
7
8
|
if (!fs.existsSync(file)) {
|
|
@@ -24,6 +25,8 @@ export const importConnectionsPrompt = async (file: string) => {
|
|
|
24
25
|
|
|
25
26
|
console.log(`Found ${connectionsToImport.length} connections to import.`);
|
|
26
27
|
|
|
28
|
+
const cleanup = enableEscapeExit();
|
|
29
|
+
try {
|
|
27
30
|
for (const conn of connectionsToImport) {
|
|
28
31
|
const validation = connectionSchema.safeParse(conn);
|
|
29
32
|
if (!validation.success) {
|
|
@@ -77,6 +80,9 @@ export const importConnectionsPrompt = async (file: string) => {
|
|
|
77
80
|
console.log(`- ${importedCount} new connections added.`);
|
|
78
81
|
console.log(`- ${updatedCount} connections overwritten.`);
|
|
79
82
|
console.log(`- ${skippedCount} connections skipped.`);
|
|
83
|
+
} finally {
|
|
84
|
+
cleanup();
|
|
85
|
+
}
|
|
80
86
|
} catch (err: any) {
|
|
81
87
|
console.error(chalk.red(`Error reading or parsing file: ${err instanceof Error ? err.message : String(err)}`));
|
|
82
88
|
}
|
package/src/connection/index.ts
CHANGED
package/src/connection/list.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import os from "os";
|
|
3
|
-
import {
|
|
3
|
+
import { getConnections } from "../database";
|
|
4
4
|
import { buildRemoteCommand } from "../helpers/shell";
|
|
5
5
|
|
|
6
6
|
export async function listConnectionsPrompt(options?: { oneline?: boolean; names?: boolean }) {
|
|
7
7
|
let connections = getConnections();
|
|
8
8
|
if (options?.oneline) {
|
|
9
|
-
// Sort by lastUsed
|
|
9
|
+
// Sort by lastUsed descending so most recently used is first in the pipe;
|
|
10
|
+
// fzf's default layout places the first line at the bottom near the cursor.
|
|
10
11
|
if (options?.names) {
|
|
11
12
|
connections = connections.sort((a, b) => (b.lastUsed ?? 0) - (a.lastUsed ?? 0));
|
|
12
13
|
}
|
|
@@ -34,54 +35,7 @@ export async function listConnectionsPrompt(options?: { oneline?: boolean; names
|
|
|
34
35
|
const userHost = userHostCache.get(c.alias)!;
|
|
35
36
|
|
|
36
37
|
if (options?.names) {
|
|
37
|
-
|
|
38
|
-
let output = c.alias + TAB + userHost + TAB;
|
|
39
|
-
|
|
40
|
-
// Build SSH args array more efficiently
|
|
41
|
-
const args: string[] = [];
|
|
42
|
-
|
|
43
|
-
if (c.key_path) {
|
|
44
|
-
// Fast path expansion - only expand if it starts with ~
|
|
45
|
-
let expandedKeyPath = c.key_path;
|
|
46
|
-
if (expandedKeyPath.startsWith('~')) {
|
|
47
|
-
if (expandedKeyPath === '~') {
|
|
48
|
-
expandedKeyPath = homeDir;
|
|
49
|
-
} else if (expandedKeyPath.startsWith('~/')) {
|
|
50
|
-
expandedKeyPath = homeDir + expandedKeyPath.slice(1);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Fast quote escaping - only escape single quotes
|
|
55
|
-
const needsEscaping = expandedKeyPath.includes(SINGLE_QUOTE);
|
|
56
|
-
const quotedKey = needsEscaping
|
|
57
|
-
? SINGLE_QUOTE + expandedKeyPath.replace(/'/g, ESCAPED_QUOTE) + SINGLE_QUOTE
|
|
58
|
-
: SINGLE_QUOTE + expandedKeyPath + SINGLE_QUOTE;
|
|
59
|
-
|
|
60
|
-
args.push(DASH_I, quotedKey);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (c.port) {
|
|
64
|
-
args.push(DASH_P, String(c.port));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Always add -t for interactive terminal allocation
|
|
68
|
-
args.push(DASH_T);
|
|
69
|
-
args.push(userHost);
|
|
70
|
-
|
|
71
|
-
if (c.remote_path) {
|
|
72
|
-
// Only build remote command if needed
|
|
73
|
-
const remoteCommand = buildRemoteCommand({
|
|
74
|
-
remotePath: String(c.remote_path),
|
|
75
|
-
postCommand: 'exec "$SHELL" -l',
|
|
76
|
-
});
|
|
77
|
-
if (remoteCommand) {
|
|
78
|
-
args.push(remoteCommand);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Join args with spaces (faster than array join for small arrays)
|
|
83
|
-
output += args.join(SPACE);
|
|
84
|
-
console.log(output);
|
|
38
|
+
console.log(c.alias + TAB + userHost);
|
|
85
39
|
} else {
|
|
86
40
|
// Simple args-only format (even faster)
|
|
87
41
|
const args: string[] = [];
|
package/src/connection/update.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import inquirer from "inquirer";
|
|
3
3
|
import { getConnectionByAlias, updateConnection } from "../database";
|
|
4
|
+
import { enableEscapeExit } from "../helpers/escExit";
|
|
4
5
|
import { selectAlias } from "../helpers/selectAlias";
|
|
5
6
|
import { testConnectionPrompt } from "./test";
|
|
6
7
|
|
|
@@ -12,6 +13,8 @@ export async function updateConnectionPrompt(alias?: string) {
|
|
|
12
13
|
|
|
13
14
|
const connection = getConnectionByAlias(alias);
|
|
14
15
|
if (connection) {
|
|
16
|
+
const cleanup = enableEscapeExit();
|
|
17
|
+
try {
|
|
15
18
|
const answers = await inquirer.prompt([
|
|
16
19
|
{
|
|
17
20
|
type: 'input',
|
|
@@ -59,6 +62,9 @@ export async function updateConnectionPrompt(alias?: string) {
|
|
|
59
62
|
if (test) {
|
|
60
63
|
await testConnectionPrompt(alias);
|
|
61
64
|
}
|
|
65
|
+
} finally {
|
|
66
|
+
cleanup();
|
|
67
|
+
}
|
|
62
68
|
} else {
|
|
63
69
|
console.error(chalk.red('Alias not found'));
|
|
64
70
|
}
|
|
@@ -2,6 +2,31 @@ import chalk from "chalk";
|
|
|
2
2
|
import Fuse from "fuse.js";
|
|
3
3
|
import inquirer from "inquirer";
|
|
4
4
|
import { getConnections } from "../database";
|
|
5
|
+
import { enableEscapeExit } from "./escExit";
|
|
6
|
+
|
|
7
|
+
const getAliasChoices = () => {
|
|
8
|
+
return getConnections()
|
|
9
|
+
.sort((a, b) => (b.lastUsed ?? 0) - (a.lastUsed ?? 0))
|
|
10
|
+
.map(c => ({ name: c.alias, value: c.alias }));
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const searchAliasChoices = (term: string) => {
|
|
14
|
+
const choices = getAliasChoices();
|
|
15
|
+
if (term.trim().length === 0) {
|
|
16
|
+
return choices;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const fuse = new Fuse(choices, {
|
|
20
|
+
keys: ['name'],
|
|
21
|
+
threshold: 0.4,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return fuse.search(term).map(r => r.item);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const findAliasBySearchTerm = (term: string): string | undefined => {
|
|
28
|
+
return searchAliasChoices(term)[0]?.value;
|
|
29
|
+
};
|
|
5
30
|
|
|
6
31
|
/**
|
|
7
32
|
* Prompt user to choose a connection alias using fuzzy search.
|
|
@@ -10,38 +35,34 @@ import { getConnections } from "../database";
|
|
|
10
35
|
* @returns selected alias as string
|
|
11
36
|
*/
|
|
12
37
|
export async function selectAlias(message: string = "Select a connection"): Promise<string> {
|
|
13
|
-
|
|
14
|
-
if (connections.length === 0) {
|
|
38
|
+
if (getConnections().length === 0) {
|
|
15
39
|
console.log(chalk.yellow('No connections found. Add one with "sshya add"'));
|
|
16
40
|
process.exit(0);
|
|
17
41
|
}
|
|
18
42
|
|
|
19
43
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
keys: ['name'],
|
|
39
|
-
threshold: 0.4,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return fuse.search(term).map(r => r.item);
|
|
44
|
+
const cleanup = enableEscapeExit();
|
|
45
|
+
try {
|
|
46
|
+
const { alias } = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: 'search',
|
|
49
|
+
name: 'alias',
|
|
50
|
+
message,
|
|
51
|
+
// Fuzzy-search the aliases using Fuse.js as the user types.
|
|
52
|
+
// The signature matches the @inquirer/search documentation:
|
|
53
|
+
// (term: string | void, { signal }: { signal: AbortSignal }) => Promise<Choice[]>
|
|
54
|
+
source: async (term: unknown) => {
|
|
55
|
+
// When term is undefined or empty, return the full, recently-used-sorted list.
|
|
56
|
+
if (typeof term !== 'string' || term.trim().length === 0) {
|
|
57
|
+
return searchAliasChoices('');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return searchAliasChoices(term);
|
|
61
|
+
},
|
|
43
62
|
},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
]);
|
|
64
|
+
return alias as string;
|
|
65
|
+
} finally {
|
|
66
|
+
cleanup();
|
|
67
|
+
}
|
|
47
68
|
}
|