Skip to content

Commit eb6f06f

Browse files
authored
Merge pull request #772 from mbektas/settings-and-logs-cli-commands
add new CLI commands for settings appdata and logs
2 parents 3d5ff24 + 5aa6e49 commit eb6f06f

File tree

4 files changed

+408
-23
lines changed

4 files changed

+408
-23
lines changed

src/main/cli.ts

+366-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getBundledEnvInstallerPath,
88
getBundledPythonEnvPath,
99
getBundledPythonPath,
10+
getLogFilePath,
1011
installCondaPackEnvironment,
1112
isBaseCondaEnv,
1213
isEnvInstalledByDesktopApp,
@@ -16,11 +17,16 @@ import {
1617
import yargs from 'yargs/yargs';
1718
import * as fs from 'fs';
1819
import * as path from 'path';
19-
import { appData } from './config/appdata';
20+
import { appData, ApplicationData } from './config/appdata';
2021
import { IEnvironmentType, IPythonEnvironment } from './tokens';
21-
import { SettingType, userSettings } from './config/settings';
22+
import {
23+
SettingType,
24+
UserSettings,
25+
userSettings,
26+
WorkspaceSettings
27+
} from './config/settings';
2228
import { Registry } from './registry';
23-
import { app } from 'electron';
29+
import { app, shell } from 'electron';
2430
import {
2531
condaEnvPathForCondaExePath,
2632
getCondaChannels,
@@ -188,6 +194,103 @@ export function parseCLIArgs(argv: string[]) {
188194
}
189195
}
190196
)
197+
.command(
198+
'config <action>',
199+
'Manage JupyterLab Desktop settings',
200+
yargs => {
201+
yargs
202+
.positional('action', {
203+
describe: 'Setting action',
204+
choices: ['list', 'set', 'unset', 'open-file'],
205+
default: 'list'
206+
})
207+
.option('project', {
208+
describe: 'Set config for project at current working directory',
209+
type: 'boolean',
210+
default: false
211+
})
212+
.option('project-path', {
213+
describe: 'Set / list config for project at specified path',
214+
type: 'string'
215+
});
216+
},
217+
async argv => {
218+
console.log('Note: This is an experimental feature.');
219+
220+
const action = argv.action;
221+
switch (action) {
222+
case 'list':
223+
handleConfigListCommand(argv);
224+
break;
225+
case 'set':
226+
handleConfigSetCommand(argv);
227+
break;
228+
case 'unset':
229+
handleConfigUnsetCommand(argv);
230+
break;
231+
case 'open-file':
232+
handleConfigOpenFileCommand(argv);
233+
break;
234+
default:
235+
console.log('Invalid input for "config" command.');
236+
break;
237+
}
238+
}
239+
)
240+
.command(
241+
'appdata <action>',
242+
'Manage JupyterLab Desktop app data',
243+
yargs => {
244+
yargs.positional('action', {
245+
describe: 'App data action',
246+
choices: ['list', 'open-file'],
247+
default: 'list'
248+
});
249+
},
250+
async argv => {
251+
console.log('Note: This is an experimental feature.');
252+
253+
const action = argv.action;
254+
switch (action) {
255+
case 'list':
256+
handleAppDataListCommand(argv);
257+
break;
258+
case 'open-file':
259+
handleAppDataOpenFileCommand(argv);
260+
break;
261+
default:
262+
console.log('Invalid input for "appdata" command.');
263+
break;
264+
}
265+
}
266+
)
267+
.command(
268+
'logs <action>',
269+
'Manage JupyterLab Desktop logs',
270+
yargs => {
271+
yargs.positional('action', {
272+
describe: 'Logs action',
273+
choices: ['show', 'open-file'],
274+
default: 'show'
275+
});
276+
},
277+
async argv => {
278+
console.log('Note: This is an experimental feature.');
279+
280+
const action = argv.action;
281+
switch (action) {
282+
case 'show':
283+
handleLogsShowCommand(argv);
284+
break;
285+
case 'open-file':
286+
handleLogsOpenFileCommand(argv);
287+
break;
288+
default:
289+
console.log('Invalid input for "logs" command.');
290+
break;
291+
}
292+
}
293+
)
191294
.parseAsync();
192295
}
193296

@@ -816,6 +919,266 @@ export async function handleEnvSetSystemPythonPathCommand(argv: any) {
816919
userSettings.save();
817920
}
818921

922+
function getProjectPathForConfigCommand(argv: any): string | undefined {
923+
let projectPath = undefined;
924+
if (argv.project || argv.projectPath) {
925+
projectPath = argv.projectPath
926+
? path.resolve(argv.projectPath)
927+
: process.cwd();
928+
if (
929+
argv.projectPath &&
930+
!(fs.existsSync(projectPath) && fs.statSync(projectPath).isDirectory())
931+
) {
932+
console.error(`Invalid project path! "${projectPath}"`);
933+
process.exit(1);
934+
}
935+
}
936+
937+
return projectPath;
938+
}
939+
940+
function handleConfigListCommand(argv: any) {
941+
const listLines: string[] = [];
942+
943+
const projectPath = argv.projectPath
944+
? path.resolve(argv.projectPath)
945+
: process.cwd();
946+
947+
listLines.push('Project / Workspace settings');
948+
listLines.push('============================');
949+
listLines.push(`[Project path: ${projectPath}]`);
950+
listLines.push(
951+
`[Source file: ${WorkspaceSettings.getWorkspaceSettingsPath(projectPath)}]`
952+
);
953+
listLines.push('\nSettings');
954+
listLines.push('========');
955+
956+
const wsSettings = new WorkspaceSettings(projectPath).settings;
957+
const wsSettingKeys = Object.keys(wsSettings).sort();
958+
if (wsSettingKeys.length > 0) {
959+
for (let key of wsSettingKeys) {
960+
const value = wsSettings[key].value;
961+
listLines.push(`${key}: ${JSON.stringify(value)}`);
962+
}
963+
} else {
964+
listLines.push('No setting overrides found in project directory.');
965+
}
966+
listLines.push('\n');
967+
968+
listLines.push('Global settings');
969+
listLines.push('===============');
970+
listLines.push(`[Source file: ${UserSettings.getUserSettingsPath()}]`);
971+
listLines.push('\nSettings');
972+
listLines.push('========');
973+
974+
const settingKeys = Object.values(SettingType).sort();
975+
const settings = userSettings.settings;
976+
977+
for (let key of settingKeys) {
978+
const setting = settings[key];
979+
listLines.push(
980+
`${key}: ${JSON.stringify(setting.value)} [${
981+
setting.differentThanDefault ? 'modified' : 'set to default'
982+
}${setting.wsOverridable ? ', project overridable' : ''}]`
983+
);
984+
}
985+
986+
console.log(listLines.join('\n'));
987+
}
988+
989+
function handleConfigSetCommand(argv: any) {
990+
const parseSetting = (): { key: string; value: string } => {
991+
if (argv._.length !== 3) {
992+
console.error(`Invalid setting. Use "set <settingKey> <value>" format.`);
993+
return { key: undefined, value: undefined };
994+
}
995+
996+
let value;
997+
998+
// boolean, arrays, objects
999+
try {
1000+
value = JSON.parse(argv._[2]);
1001+
} catch (error) {
1002+
try {
1003+
// string without quotes
1004+
value = JSON.parse(`"${argv._[2]}"`);
1005+
} catch (error) {
1006+
console.error(error.message);
1007+
}
1008+
}
1009+
1010+
return { key: argv._[1], value: value };
1011+
};
1012+
1013+
const projectPath = getProjectPathForConfigCommand(argv);
1014+
1015+
let key, value;
1016+
try {
1017+
const keyVal = parseSetting();
1018+
key = keyVal.key;
1019+
value = keyVal.value;
1020+
} catch (error) {
1021+
console.error('Failed to parse setting!');
1022+
return;
1023+
}
1024+
1025+
if (key === undefined || value === undefined) {
1026+
console.error('Failed to parse key value pair!');
1027+
return;
1028+
}
1029+
1030+
if (!(key in SettingType)) {
1031+
console.error(`Invalid setting key! "${key}"`);
1032+
return;
1033+
}
1034+
1035+
if (projectPath) {
1036+
const setting = userSettings.settings[key];
1037+
if (!setting.wsOverridable) {
1038+
console.error(`Setting "${key}" is not overridable by project.`);
1039+
return;
1040+
}
1041+
1042+
const wsSettings = new WorkspaceSettings(projectPath);
1043+
wsSettings.setValue(key as SettingType, value);
1044+
wsSettings.save();
1045+
} else {
1046+
userSettings.setValue(key as SettingType, value);
1047+
userSettings.save();
1048+
}
1049+
1050+
console.log(
1051+
`${
1052+
projectPath ? 'Project' : 'Global'
1053+
} setting "${key}" set to "${value}" successfully.`
1054+
);
1055+
}
1056+
1057+
function handleConfigUnsetCommand(argv: any) {
1058+
const parseKey = (): string => {
1059+
if (argv._.length !== 2) {
1060+
console.error(`Invalid setting. Use "unset <settingKey>" format.`);
1061+
return undefined;
1062+
}
1063+
1064+
return argv._[1];
1065+
};
1066+
1067+
const projectPath = getProjectPathForConfigCommand(argv);
1068+
1069+
let key = parseKey();
1070+
1071+
if (!key) {
1072+
return;
1073+
}
1074+
1075+
if (!(key in SettingType)) {
1076+
console.error(`Invalid setting key! "${key}"`);
1077+
return;
1078+
}
1079+
1080+
if (projectPath) {
1081+
const setting = userSettings.settings[key];
1082+
if (!setting.wsOverridable) {
1083+
console.error(`Setting "${key}" is not overridable by project.`);
1084+
return;
1085+
}
1086+
1087+
const wsSettings = new WorkspaceSettings(projectPath);
1088+
wsSettings.unsetValue(key as SettingType);
1089+
wsSettings.save();
1090+
} else {
1091+
userSettings.unsetValue(key as SettingType);
1092+
userSettings.save();
1093+
}
1094+
1095+
console.log(
1096+
`${projectPath ? 'Project' : 'Global'} setting "${key}" reset to ${
1097+
projectPath ? 'global ' : ''
1098+
}default successfully.`
1099+
);
1100+
}
1101+
1102+
function handleConfigOpenFileCommand(argv: any) {
1103+
const projectPath = getProjectPathForConfigCommand(argv);
1104+
const settingsFilePath = projectPath
1105+
? WorkspaceSettings.getWorkspaceSettingsPath(projectPath)
1106+
: UserSettings.getUserSettingsPath();
1107+
1108+
console.log(`Settings file path: ${settingsFilePath}`);
1109+
1110+
if (
1111+
!(fs.existsSync(settingsFilePath) && fs.statSync(settingsFilePath).isFile())
1112+
) {
1113+
console.log('Settings file does not exist!');
1114+
return;
1115+
}
1116+
1117+
shell.openPath(settingsFilePath);
1118+
}
1119+
1120+
function handleAppDataListCommand(argv: any) {
1121+
const listLines: string[] = [];
1122+
1123+
listLines.push('Application data');
1124+
listLines.push('================');
1125+
listLines.push(`[Source file: ${ApplicationData.getAppDataPath()}]`);
1126+
listLines.push('\nData');
1127+
listLines.push('====');
1128+
1129+
const skippedKeys = new Set(['newsList']);
1130+
const appDataKeys = Object.keys(appData).sort();
1131+
1132+
for (let key of appDataKeys) {
1133+
if (key.startsWith('_') || skippedKeys.has(key)) {
1134+
continue;
1135+
}
1136+
const data = (appData as any)[key];
1137+
listLines.push(`${key}: ${JSON.stringify(data)}`);
1138+
}
1139+
1140+
console.log(listLines.join('\n'));
1141+
}
1142+
1143+
function handleAppDataOpenFileCommand(argv: any) {
1144+
const appDataFilePath = ApplicationData.getAppDataPath();
1145+
console.log(`App data file path: ${appDataFilePath}`);
1146+
1147+
if (
1148+
!(fs.existsSync(appDataFilePath) && fs.statSync(appDataFilePath).isFile())
1149+
) {
1150+
console.log('App data file does not exist!');
1151+
return;
1152+
}
1153+
1154+
shell.openPath(appDataFilePath);
1155+
}
1156+
1157+
function handleLogsShowCommand(argv: any) {
1158+
const logFilePath = getLogFilePath();
1159+
console.log(`Log file path: ${logFilePath}`);
1160+
1161+
if (!(fs.existsSync(logFilePath) && fs.statSync(logFilePath).isFile())) {
1162+
console.log('Log file does not exist!');
1163+
return;
1164+
}
1165+
1166+
const logs = fs.readFileSync(logFilePath);
1167+
console.log(logs.toString());
1168+
}
1169+
1170+
function handleLogsOpenFileCommand(argv: any) {
1171+
const logFilePath = getLogFilePath();
1172+
console.log(`Log file path: ${logFilePath}`);
1173+
1174+
if (!(fs.existsSync(logFilePath) && fs.statSync(logFilePath).isFile())) {
1175+
console.log('Log file does not exist!');
1176+
return;
1177+
}
1178+
1179+
shell.openPath(logFilePath);
1180+
}
1181+
8191182
export async function launchCLIinEnvironment(
8201183
envPath: string
8211184
): Promise<boolean> {

0 commit comments

Comments
 (0)