Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new CLI commands for settings appdata and logs #772

Merged
merged 3 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
369 changes: 366 additions & 3 deletions src/main/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getBundledEnvInstallerPath,
getBundledPythonEnvPath,
getBundledPythonPath,
getLogFilePath,
installCondaPackEnvironment,
isBaseCondaEnv,
isEnvInstalledByDesktopApp,
Expand All @@ -16,11 +17,16 @@ import {
import yargs from 'yargs/yargs';
import * as fs from 'fs';
import * as path from 'path';
import { appData } from './config/appdata';
import { appData, ApplicationData } from './config/appdata';
import { IEnvironmentType, IPythonEnvironment } from './tokens';
import { SettingType, userSettings } from './config/settings';
import {
SettingType,
UserSettings,
userSettings,
WorkspaceSettings
} from './config/settings';
import { Registry } from './registry';
import { app } from 'electron';
import { app, shell } from 'electron';
import {
condaEnvPathForCondaExePath,
getCondaChannels,
Expand Down Expand Up @@ -188,6 +194,103 @@ export function parseCLIArgs(argv: string[]) {
}
}
)
.command(
'config <action>',
'Manage JupyterLab Desktop settings',
yargs => {
yargs
.positional('action', {
describe: 'Setting action',
choices: ['list', 'set', 'unset', 'open-file'],
default: 'list'
})
.option('project', {
describe: 'Set config for project at current working directory',
type: 'boolean',
default: false
})
.option('project-path', {
describe: 'Set / list config for project at specified path',
type: 'string'
});
},
async argv => {
console.log('Note: This is an experimental feature.');

const action = argv.action;
switch (action) {
case 'list':
handleConfigListCommand(argv);
break;
case 'set':
handleConfigSetCommand(argv);
break;
case 'unset':
handleConfigUnsetCommand(argv);
break;
case 'open-file':
handleConfigOpenFileCommand(argv);
break;
default:
console.log('Invalid input for "config" command.');
break;
}
}
)
.command(
'appdata <action>',
'Manage JupyterLab Desktop app data',
yargs => {
yargs.positional('action', {
describe: 'App data action',
choices: ['list', 'open-file'],
default: 'list'
});
},
async argv => {
console.log('Note: This is an experimental feature.');

const action = argv.action;
switch (action) {
case 'list':
handleAppDataListCommand(argv);
break;
case 'open-file':
handleAppDataOpenFileCommand(argv);
break;
default:
console.log('Invalid input for "appdata" command.');
break;
}
}
)
.command(
'logs <action>',
'Manage JupyterLab Desktop logs',
yargs => {
yargs.positional('action', {
describe: 'Logs action',
choices: ['show', 'open-file'],
default: 'show'
});
},
async argv => {
console.log('Note: This is an experimental feature.');

const action = argv.action;
switch (action) {
case 'show':
handleLogsShowCommand(argv);
break;
case 'open-file':
handleLogsOpenFileCommand(argv);
break;
default:
console.log('Invalid input for "logs" command.');
break;
}
}
)
.parseAsync();
}

Expand Down Expand Up @@ -816,6 +919,266 @@ export async function handleEnvSetSystemPythonPathCommand(argv: any) {
userSettings.save();
}

function getProjectPathForConfigCommand(argv: any): string | undefined {
let projectPath = undefined;
if (argv.project || argv.projectPath) {
projectPath = argv.projectPath
? path.resolve(argv.projectPath)
: process.cwd();
if (
argv.projectPath &&
!(fs.existsSync(projectPath) && fs.statSync(projectPath).isDirectory())
) {
console.error(`Invalid project path! "${projectPath}"`);
process.exit(1);
}
}

return projectPath;
}

function handleConfigListCommand(argv: any) {
const listLines: string[] = [];

const projectPath = argv.projectPath
? path.resolve(argv.projectPath)
: process.cwd();

listLines.push('Project / Workspace settings');
listLines.push('============================');
listLines.push(`[Project path: ${projectPath}]`);
listLines.push(
`[Source file: ${WorkspaceSettings.getWorkspaceSettingsPath(projectPath)}]`
);
listLines.push('\nSettings');
listLines.push('========');

const wsSettings = new WorkspaceSettings(projectPath).settings;
const wsSettingKeys = Object.keys(wsSettings).sort();
if (wsSettingKeys.length > 0) {
for (let key of wsSettingKeys) {
const value = wsSettings[key].value;
listLines.push(`${key}: ${JSON.stringify(value)}`);
}
} else {
listLines.push('No setting overrides found in project directory.');
}
listLines.push('\n');

listLines.push('Global settings');
listLines.push('===============');
listLines.push(`[Source file: ${UserSettings.getUserSettingsPath()}]`);
listLines.push('\nSettings');
listLines.push('========');

const settingKeys = Object.values(SettingType).sort();
const settings = userSettings.settings;

for (let key of settingKeys) {
const setting = settings[key];
listLines.push(
`${key}: ${JSON.stringify(setting.value)} [${
setting.differentThanDefault ? 'modified' : 'set to default'
}${setting.wsOverridable ? ', project overridable' : ''}]`
);
}

console.log(listLines.join('\n'));
}

function handleConfigSetCommand(argv: any) {
const parseSetting = (): { key: string; value: string } => {
if (argv._.length !== 3) {
console.error(`Invalid setting. Use "set <settingKey> <value>" format.`);
return { key: undefined, value: undefined };
}

let value;

// boolean, arrays, objects
try {
value = JSON.parse(argv._[2]);
} catch (error) {
try {
// string without quotes
value = JSON.parse(`"${argv._[2]}"`);
} catch (error) {
console.error(error.message);
}
}

return { key: argv._[1], value: value };
};

const projectPath = getProjectPathForConfigCommand(argv);

let key, value;
try {
const keyVal = parseSetting();
key = keyVal.key;
value = keyVal.value;
} catch (error) {
console.error('Failed to parse setting!');
return;
}

if (key === undefined || value === undefined) {
console.error('Failed to parse key value pair!');
return;
}

if (!(key in SettingType)) {
console.error(`Invalid setting key! "${key}"`);
return;
}

if (projectPath) {
const setting = userSettings.settings[key];
if (!setting.wsOverridable) {
console.error(`Setting "${key}" is not overridable by project.`);
return;
}

const wsSettings = new WorkspaceSettings(projectPath);
wsSettings.setValue(key as SettingType, value);
wsSettings.save();
} else {
userSettings.setValue(key as SettingType, value);
userSettings.save();
}

console.log(
`${
projectPath ? 'Project' : 'Global'
} setting "${key}" set to "${value}" successfully.`
);
}

function handleConfigUnsetCommand(argv: any) {
const parseKey = (): string => {
if (argv._.length !== 2) {
console.error(`Invalid setting. Use "unset <settingKey>" format.`);
return undefined;
}

return argv._[1];
};

const projectPath = getProjectPathForConfigCommand(argv);

let key = parseKey();

if (!key) {
return;
}

if (!(key in SettingType)) {
console.error(`Invalid setting key! "${key}"`);
return;
}

if (projectPath) {
const setting = userSettings.settings[key];
if (!setting.wsOverridable) {
console.error(`Setting "${key}" is not overridable by project.`);
return;
}

const wsSettings = new WorkspaceSettings(projectPath);
wsSettings.unsetValue(key as SettingType);
wsSettings.save();
} else {
userSettings.unsetValue(key as SettingType);
userSettings.save();
}

console.log(
`${projectPath ? 'Project' : 'Global'} setting "${key}" reset to ${
projectPath ? 'global ' : ''
}default successfully.`
);
}

function handleConfigOpenFileCommand(argv: any) {
const projectPath = getProjectPathForConfigCommand(argv);
const settingsFilePath = projectPath
? WorkspaceSettings.getWorkspaceSettingsPath(projectPath)
: UserSettings.getUserSettingsPath();

console.log(`Settings file path: ${settingsFilePath}`);

if (
!(fs.existsSync(settingsFilePath) && fs.statSync(settingsFilePath).isFile())
) {
console.log('Settings file does not exist!');
return;
}

shell.openPath(settingsFilePath);
}

function handleAppDataListCommand(argv: any) {
const listLines: string[] = [];

listLines.push('Application data');
listLines.push('================');
listLines.push(`[Source file: ${ApplicationData.getAppDataPath()}]`);
listLines.push('\nData');
listLines.push('====');

const skippedKeys = new Set(['newsList']);
const appDataKeys = Object.keys(appData).sort();

for (let key of appDataKeys) {
if (key.startsWith('_') || skippedKeys.has(key)) {
continue;
}
const data = (appData as any)[key];
listLines.push(`${key}: ${JSON.stringify(data)}`);
}

console.log(listLines.join('\n'));
}

function handleAppDataOpenFileCommand(argv: any) {
const appDataFilePath = ApplicationData.getAppDataPath();
console.log(`App data file path: ${appDataFilePath}`);

if (
!(fs.existsSync(appDataFilePath) && fs.statSync(appDataFilePath).isFile())
) {
console.log('App data file does not exist!');
return;
}

shell.openPath(appDataFilePath);
}

function handleLogsShowCommand(argv: any) {
const logFilePath = getLogFilePath();
console.log(`Log file path: ${logFilePath}`);

if (!(fs.existsSync(logFilePath) && fs.statSync(logFilePath).isFile())) {
console.log('Log file does not exist!');
return;
}

const logs = fs.readFileSync(logFilePath);
console.log(logs.toString());
}

function handleLogsOpenFileCommand(argv: any) {
const logFilePath = getLogFilePath();
console.log(`Log file path: ${logFilePath}`);

if (!(fs.existsSync(logFilePath) && fs.statSync(logFilePath).isFile())) {
console.log('Log file does not exist!');
return;
}

shell.openPath(logFilePath);
}

export async function launchCLIinEnvironment(
envPath: string
): Promise<boolean> {
Expand Down
Loading
Loading