Move flag system to separate file and fix import quotes

pull/373/head
Kelvin Schoofs 2 years ago
parent 5721f1ca09
commit 11b8f052db

@ -25,5 +25,6 @@
"**/.pnp.*": true
},
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.preferences.quoteStyle": "single",
"prettier.prettierPath": ".yarn/sdks/prettier/index.js"
}

@ -24,6 +24,9 @@
- Since `readDirectory`, `readFile` and `stat` are disabled by default, it should prevent extension detection spam (see #341)
- Added the `SHELL_CONFIG` flag to force a specific remote shell configuration (#331)
### Development changes
- Move the whole flag system from config.ts to flags.ts
## v1.25.0 (2022-06-01)
### Major change

@ -342,170 +342,3 @@ vscode.workspace.onDidChangeConfiguration(async (e) => {
// if (!e.affectsConfiguration('sshfs.configs')) return;
return loadConfigs();
});
function parseFlagList(list: string[] | undefined, origin: string): Record<string, FlagCombo> {
if (list === undefined) return {};
if (!Array.isArray(list)) throw new Error(`Expected string array for flags, but got: ${list}`);
const scope: Record<string, FlagCombo> = {};
for (const flag of list) {
let name: string = flag;
let value: FlagValue = null;
const eq = flag.indexOf('=');
if (eq !== -1) {
name = flag.substring(0, eq);
value = flag.substring(eq + 1);
} else if (flag.startsWith('+')) {
name = flag.substring(1);
value = true;
} else if (flag.startsWith('-')) {
name = flag.substring(1);
value = false;
}
name = name.toLocaleLowerCase();
if (name in scope) continue;
scope[name] = [value, origin];
}
return scope;
}
/* List of flags
DF-GE (boolean) (default=false)
- Disables the 'diffie-hellman-group-exchange' kex algorithm as a default option
- Originally for issue #239
- Automatically enabled for Electron v11.0, v11.1 and v11.2
DEBUG_SSH2 (boolean) (default=false)
- Enables debug logging in the ssh2 library (set at the start of each connection)
WINDOWS_COMMAND_SEPARATOR (boolean) (default=false)
- Makes it that commands are joined together using ` && ` instead of `; `
- Automatically enabled when the remote shell is detected to be PowerShell or Command Prompt (cmd.exe)
CHECK_HOME (boolean) (default=true)
- Determines whether we check if the home directory exists during `createFileSystem` in the Manager
- If `tryGetHome` fails while creating the connection, throw an error if this flag is set, otherwise default to `/`
REMOTE_COMMANDS (boolean) (default=false)
- Enables automatically launching a background command terminal during connection setup
- Enables attempting to inject a file to be sourced by the remote shells (which adds the `code` alias)
DEBUG_REMOTE_COMMANDS (boolean) (default=false)
- Enables debug logging for the remote command terminal (thus useless if REMOTE_COMMANDS isn't true)
DEBUG_FS (string) (default='')
- A comma-separated list of debug flags for logging errors in the sshFileSystem
- The presence of `showignored` will log `FileNotFound` that got ignored
- The presence of `disableignored` will make the code ignore nothing (making `showignored` useless)
- The presence of `minimal` will log all errors as single lines, but not `FileNotFound`
- The presence of `full` is the same as `minimal` but with full stacktraces
- The presence of `converted` will log the resulting converted errors (if required and successful)
- The presence of `all` enables all of the above except `disableignored` (similar to `showignored,full,converted`)
DEBUG_FSR (string) (default='', global)
- A comma-separated list of method names to enable logging for in the FileSystemRouter
- The presence of `all` is equal to `stat,readDirectory,createDirectory,readFile,writeFile,delete,rename`
- The router logs handles `ssh://`, and will even log operations to non-existing configurations/connections
FS_NOTIFY_ERRORS (string) (default='')
- A comma-separated list of operations to display notifications for should they error
- Mind that `FileNotFound` errors for ignored paths are always ignored, except with `DEBUG_FS=showignored`
- The presence of `all` will show notification for every operation
- The presence of `write` is equal to `createDirectory,writeFile,delete,rename`
- Besides those provided by `write`, there's also `readDirectory`, `readFile` and `stat`
- Automatically set to `write` for VS Code 1.56 and later (see issue #282)
SHELL_CONFIG (string)
- Forces the use of a specific shell configuration. Check shellConfig.ts for possible values
- By default, when this flag is absent (or an empty or not a string), the extension will try to detect the correct type to use
*/
export type FlagValue = string | boolean | null;
export type FlagCombo<V extends FlagValue = FlagValue> = [value: V, origin: string];
const globalFlagsSubscribers = new Set<() => void>();
export function subscribeToGlobalFlags(listener: () => void): vscode.Disposable {
listener();
globalFlagsSubscribers.add(listener);
return new vscode.Disposable(() => globalFlagsSubscribers.delete(listener));
}
export const DEFAULT_FLAGS: string[] = [];
let cachedFlags: Record<string, FlagCombo> = {};
function calculateFlags(): Record<string, FlagCombo> {
const flags: Record<string, FlagCombo> = {};
const config = vscode.workspace.getConfiguration('sshfs').inspect<string[]>('flags');
if (!config) throw new Error(`Could not inspect "sshfs.flags" config field`);
const applyList = (list: string[] | undefined, origin: string) => Object.assign(flags, parseFlagList(list, origin));
applyList(DEFAULT_FLAGS, 'Built-in Default');
applyList(config.defaultValue, 'Default Settings');
// Electron v11 crashes for DiffieHellman GroupExchange, although it's fixed in 11.3.0
if ((process.versions as { electron?: string }).electron?.match(/^11\.(0|1|2)\./)) {
applyList(['+DF-GE'], 'Fix for issue #239')
}
// Starting with 1.56, FileSystemProvider errors aren't shown to the user and just silently fail
// https://github.com/SchoofsKelvin/vscode-sshfs/issues/282
if (semver.gte(vscode.version, '1.56.0')) {
applyList(['+FS_NOTIFY_ERRORS'], 'Fix for issue #282');
}
applyList(config.globalValue, 'Global Settings');
applyList(config.workspaceValue, 'Workspace Settings');
applyList(config.workspaceFolderValue, 'WorkspaceFolder Settings');
Logging.info`Calculated config flags: ${flags}`;
for (const listener of globalFlagsSubscribers) {
catchingPromise(listener).catch(e => Logging.error`onGlobalFlagsChanged listener errored: ${e}`);
}
return cachedFlags = flags;
}
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('sshfs.flags')) calculateFlags();
});
calculateFlags();
/**
* Returns (a copy of the) global flags. Gets updated by ConfigurationChangeEvent events.
* In case `flags` is given, flags specified in this array will override global ones in the returned result.
* @param flags An optional array of flags to check before the global ones
*/
export function getFlags(flags?: string[]): Record<string, FlagCombo> {
return {
...cachedFlags,
...parseFlagList(flags, 'Override'),
};
}
/**
* Checks the `sshfs.flags` config (overridable by e.g. workspace settings).
* - Flag names are case-insensitive
* - If a flag appears twice, the first mention of it is used
* - If a flag appears as "NAME", `null` is returned
* - If a flag appears as "FLAG=VALUE", `VALUE` is returned as a string
* - If a flag appears as `+FLAG` (and no `=`), `true` is returned (as a boolean)
* - If a flag appears as `-FLAG` (and no `=`), `false` is returned (as a boolean)
* - If a flag is missing, `undefined` is returned (different from `null`!)
*
* For `undefined`, an actual `undefined` is returned. For all other cases, a FlagCombo
* is returned, e.g. "NAME" returns `[null, "someOrigin"]` and `"+F"` returns `[true, "someOrigin"]`
* @param target The name of the flag to look for
* @param flags An optional array of flags to check before the global ones
*/
export function getFlag(target: string, flags?: string[]): FlagCombo | undefined {
return getFlags(flags)[target.toLowerCase()];
}
/**
* Built on top of getFlag. Tries to convert the flag value to a boolean using these rules:
* - If the flag isn't present, `missingValue` is returned
* Although this probably means I'm using a flag that I never added to `DEFAULT_FLAGS`
* - Booleans are kept
* - `null` is counted as `true` (means a flag like "NAME" was present without any value or prefix)
* - Strings try to get converted in a case-insensitive way:
* - `true/t/yes/y` becomes true
* - `false/f/no/n` becomes false
* - All other strings result in an error
* @param target The name of the flag to look for
* @param defaultValue The value to return when no flag with the given name is present
* @param flags An optional array of flags to check before the global ones
* @returns The matching FlagCombo or `[missingValue, 'missing']` instead
*/
export function getFlagBoolean(target: string, missingValue: boolean, flags?: string[]): FlagCombo<boolean> {
const combo = getFlag(target, flags);
if (!combo) return [missingValue, 'missing'];
const [value, reason] = combo;
if (value == null) return [true, reason];
if (typeof value === 'boolean') return [value, reason];
const lower = value.toLowerCase();
if (lower === 'true' || lower === 't' || lower === 'yes' || lower === 'y') return [true, reason];
if (lower === 'false' || lower === 'f' || lower === 'no' || lower === 'n') return [false, reason];
throw new Error(`Could not convert '${value}' for flag '${target}' to a boolean!`);
}

@ -5,7 +5,8 @@ import { userInfo } from 'os';
import { Client, ClientChannel, ConnectConfig } from 'ssh2';
import { SFTP } from 'ssh2/lib/protocol/SFTP';
import * as vscode from 'vscode';
import { getConfig, getFlagBoolean } from './config';
import { getConfig } from './config';
import { getFlagBoolean } from './flags';
import { Logging } from './logging';
import type { PuttySession } from './putty';
import { toPromise, validatePort } from './utils';

@ -3,7 +3,8 @@ import { posix as path } from 'path';
import * as readline from 'readline';
import type { Client, ClientChannel } from 'ssh2';
import * as vscode from 'vscode';
import { configMatches, getFlag, getFlagBoolean, loadConfigs } from './config';
import { configMatches, loadConfigs } from './config';
import { getFlag, getFlagBoolean } from './flags';
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
import type { SSHPseudoTerminal } from './pseudoTerminal';
import { calculateShellConfig, KNOWN_SHELL_CONFIGS, ShellConfig, tryCommand, tryEcho } from './shellConfig';

@ -1,5 +1,5 @@
import * as vscode from 'vscode';
import { getFlag, subscribeToGlobalFlags } from './config';
import { getFlag, subscribeToGlobalFlags } from './flags';
import { Logging } from './logging';
import type { Manager } from './manager';

@ -0,0 +1,180 @@
import * as semver from 'semver';
import * as vscode from 'vscode';
import { Logging } from './logging';
import { catchingPromise } from './utils';
/* List of flags
DF-GE (boolean) (default=false)
- Disables the 'diffie-hellman-group-exchange' kex algorithm as a default option
- Originally for issue #239
- Automatically enabled for Electron v11.0, v11.1 and v11.2
DEBUG_SSH2 (boolean) (default=false)
- Enables debug logging in the ssh2 library (set at the start of each connection)
WINDOWS_COMMAND_SEPARATOR (boolean) (default=false)
- Makes it that commands are joined together using ` && ` instead of `; `
- Automatically enabled when the remote shell is detected to be PowerShell or Command Prompt (cmd.exe)
CHECK_HOME (boolean) (default=true)
- Determines whether we check if the home directory exists during `createFileSystem` in the Manager
- If `tryGetHome` fails while creating the connection, throw an error if this flag is set, otherwise default to `/`
REMOTE_COMMANDS (boolean) (default=false)
- Enables automatically launching a background command terminal during connection setup
- Enables attempting to inject a file to be sourced by the remote shells (which adds the `code` alias)
DEBUG_REMOTE_COMMANDS (boolean) (default=false)
- Enables debug logging for the remote command terminal (thus useless if REMOTE_COMMANDS isn't true)
DEBUG_FS (string) (default='')
- A comma-separated list of debug flags for logging errors in the sshFileSystem
- The presence of `showignored` will log `FileNotFound` that got ignored
- The presence of `disableignored` will make the code ignore nothing (making `showignored` useless)
- The presence of `minimal` will log all errors as single lines, but not `FileNotFound`
- The presence of `full` is the same as `minimal` but with full stacktraces
- The presence of `converted` will log the resulting converted errors (if required and successful)
- The presence of `all` enables all of the above except `disableignored` (similar to `showignored,full,converted`)
DEBUG_FSR (string) (default='', global)
- A comma-separated list of method names to enable logging for in the FileSystemRouter
- The presence of `all` is equal to `stat,readDirectory,createDirectory,readFile,writeFile,delete,rename`
- The router logs handles `ssh://`, and will even log operations to non-existing configurations/connections
FS_NOTIFY_ERRORS (string) (default='')
- A comma-separated list of operations to display notifications for should they error
- Mind that `FileNotFound` errors for ignored paths are always ignored, except with `DEBUG_FS=showignored`
- The presence of `all` will show notification for every operation
- The presence of `write` is equal to `createDirectory,writeFile,delete,rename`
- Besides those provided by `write`, there's also `readDirectory`, `readFile` and `stat`
- Automatically set to `write` for VS Code 1.56 and later (see issue #282)
SHELL_CONFIG (string)
- Forces the use of a specific shell configuration. Check shellConfig.ts for possible values
- By default, when this flag is absent (or an empty or not a string), the extension will try to detect the correct type to use
*/
function parseFlagList(list: string[] | undefined, origin: string): Record<string, FlagCombo> {
if (list === undefined)
return {};
if (!Array.isArray(list))
throw new Error(`Expected string array for flags, but got: ${list}`);
const scope: Record<string, FlagCombo> = {};
for (const flag of list) {
let name: string = flag;
let value: FlagValue = null;
const eq = flag.indexOf('=');
if (eq !== -1) {
name = flag.substring(0, eq);
value = flag.substring(eq + 1);
} else if (flag.startsWith('+')) {
name = flag.substring(1);
value = true;
} else if (flag.startsWith('-')) {
name = flag.substring(1);
value = false;
}
name = name.toLocaleLowerCase();
if (name in scope)
continue;
scope[name] = [value, origin];
}
return scope;
}
export type FlagValue = string | boolean | null;
export type FlagCombo<V extends FlagValue = FlagValue> = [value: V, origin: string];
const globalFlagsSubscribers = new Set<() => void>();
export function subscribeToGlobalFlags(listener: () => void): vscode.Disposable {
listener();
globalFlagsSubscribers.add(listener);
return new vscode.Disposable(() => globalFlagsSubscribers.delete(listener));
}
const DEFAULT_FLAGS: string[] = [];
let cachedFlags: Record<string, FlagCombo> = {};
function calculateFlags(): Record<string, FlagCombo> {
const flags: Record<string, FlagCombo> = {};
const config = vscode.workspace.getConfiguration('sshfs').inspect<string[]>('flags');
if (!config)
throw new Error(`Could not inspect "sshfs.flags" config field`);
const applyList = (list: string[] | undefined, origin: string) => Object.assign(flags, parseFlagList(list, origin));
applyList(DEFAULT_FLAGS, 'Built-in Default');
applyList(config.defaultValue, 'Default Settings');
// Electron v11 crashes for DiffieHellman GroupExchange, although it's fixed in 11.3.0
if ((process.versions as { electron?: string; }).electron?.match(/^11\.(0|1|2)\./)) {
applyList(['+DF-GE'], 'Fix for issue #239');
}
// Starting with 1.56, FileSystemProvider errors aren't shown to the user and just silently fail
// https://github.com/SchoofsKelvin/vscode-sshfs/issues/282
if (semver.gte(vscode.version, '1.56.0')) {
applyList(['+FS_NOTIFY_ERRORS'], 'Fix for issue #282');
}
applyList(config.globalValue, 'Global Settings');
applyList(config.workspaceValue, 'Workspace Settings');
applyList(config.workspaceFolderValue, 'WorkspaceFolder Settings');
Logging.info`Calculated config flags: ${flags}`;
for (const listener of globalFlagsSubscribers) {
catchingPromise(listener).catch(e => Logging.error`onGlobalFlagsChanged listener errored: ${e}`);
}
return cachedFlags = flags;
}
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration('sshfs.flags'))
calculateFlags();
});
calculateFlags();
/**
* Returns (a copy of the) global flags. Gets updated by ConfigurationChangeEvent events.
* In case `flags` is given, flags specified in this array will override global ones in the returned result.
* @param flags An optional array of flags to check before the global ones
*/
function getFlags(flags?: string[]): Record<string, FlagCombo> {
return {
...cachedFlags,
...parseFlagList(flags, 'Override'),
};
}
/**
* Checks the `sshfs.flags` config (overridable by e.g. workspace settings).
* - Flag names are case-insensitive
* - If a flag appears twice, the first mention of it is used
* - If a flag appears as "NAME", `null` is returned
* - If a flag appears as "FLAG=VALUE", `VALUE` is returned as a string
* - If a flag appears as `+FLAG` (and no `=`), `true` is returned (as a boolean)
* - If a flag appears as `-FLAG` (and no `=`), `false` is returned (as a boolean)
* - If a flag is missing, `undefined` is returned (different from `null`!)
*
* For `undefined`, an actual `undefined` is returned. For all other cases, a FlagCombo
* is returned, e.g. "NAME" returns `[null, "someOrigin"]` and `"+F"` returns `[true, "someOrigin"]`
* @param target The name of the flag to look for
* @param flags An optional array of flags to check before the global ones
*/
export function getFlag(target: string, flags?: string[]): FlagCombo | undefined {
return getFlags(flags)[target.toLowerCase()];
}
/**
* Built on top of getFlag. Tries to convert the flag value to a boolean using these rules:
* - If the flag isn't present, `missingValue` is returned
* Although this probably means I'm using a flag that I never added to `DEFAULT_FLAGS`
* - Booleans are kept
* - `null` is counted as `true` (means a flag like "NAME" was present without any value or prefix)
* - Strings try to get converted in a case-insensitive way:
* - `true/t/yes/y` becomes true
* - `false/f/no/n` becomes false
* - All other strings result in an error
* @param target The name of the flag to look for
* @param defaultValue The value to return when no flag with the given name is present
* @param flags An optional array of flags to check before the global ones
* @returns The matching FlagCombo or `[missingValue, 'missing']` instead
*/
export function getFlagBoolean(target: string, missingValue: boolean, flags?: string[]): FlagCombo<boolean> {
const combo = getFlag(target, flags);
if (!combo)
return [missingValue, 'missing'];
const [value, reason] = combo;
if (value == null)
return [true, reason];
if (typeof value === 'boolean')
return [value, reason];
const lower = value.toLowerCase();
if (lower === 'true' || lower === 't' || lower === 'yes' || lower === 'y')
return [true, reason];
if (lower === 'false' || lower === 'f' || lower === 'no' || lower === 'n')
return [false, reason];
throw new Error(`Could not convert '${value}' for flag '${target}' to a boolean!`);
}

@ -2,7 +2,8 @@
import type { FileSystemConfig } from 'common/fileSystemConfig';
import type { Navigation } from 'common/webviewMessages';
import * as vscode from 'vscode';
import { getConfig, getFlagBoolean, loadConfigsRaw } from './config';
import { getConfig, loadConfigs, LOADING_CONFIGS } from './config';
import { getFlagBoolean } from './flags';
import { Connection, ConnectionManager } from './connection';
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
import { isSSHPseudoTerminal, replaceVariables, replaceVariablesRecursive } from './pseudoTerminal';

@ -1,10 +1,10 @@
import type { EnvironmentVariable, FileSystemConfig } from "common/fileSystemConfig";
import type { EnvironmentVariable, FileSystemConfig } from 'common/fileSystemConfig';
import * as path from 'path';
import type { ClientChannel, PseudoTtyOptions } from "ssh2";
import * as vscode from "vscode";
import { getFlagBoolean } from './config';
import type { ClientChannel, PseudoTtyOptions } from 'ssh2';
import * as vscode from 'vscode';
import { getFlagBoolean } from './flags';
import type { Connection } from './connection';
import { Logging, LOGGING_NO_STACKTRACE } from "./logging";
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
import { environmentToExportString, joinCommands, mergeEnvironment, toPromise } from './utils';
const [HEIGHT, WIDTH] = [480, 640];

@ -1,8 +1,8 @@
import { posix as path } from 'path';
import type { Client, ClientChannel, SFTP } from "ssh2";
import type { Client, ClientChannel, SFTP } from 'ssh2';
import type { Connection } from './connection';
import { Logger, Logging, LOGGING_NO_STACKTRACE } from "./logging";
import { toPromise } from "./utils";
import { Logger, Logging, LOGGING_NO_STACKTRACE } from './logging';
import { toPromise } from './utils';
const SCRIPT_COMMAND_CODE = `#!/bin/sh
if [ "$#" -ne 1 ] || [ $1 = "help" ] || [ $1 = "--help" ] || [ $1 = "-h" ] || [ $1 = "-?" ]; then

@ -3,7 +3,7 @@ import type { FileSystemConfig } from 'common/fileSystemConfig';
import * as path from 'path';
import type * as ssh2 from 'ssh2';
import * as vscode from 'vscode';
import { FlagValue, getFlag, subscribeToGlobalFlags } from './config';
import { FlagValue, getFlag, subscribeToGlobalFlags } from './flags';
import { Logger, Logging, LOGGING_NO_STACKTRACE, LOGGING_SINGLE_LINE_STACKTRACE, withStacktraceOffset } from './logging';
import { toPromise } from './utils';

@ -1,5 +1,5 @@
import * as React from 'react';
import { connect } from "../redux";
import { connect } from '../redux';
function nop(...args: any) {
return;

Loading…
Cancel
Save