Skip to content

Commit 8ee48ca

Browse files
committed
add new CLI commands for settings appdata and logs
1 parent 3d5ff24 commit 8ee48ca

File tree

4 files changed

+361
-23
lines changed

4 files changed

+361
-23
lines changed

src/main/cli.ts

Lines changed: 319 additions & 3 deletions
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,97 @@ 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'],
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+
default:
232+
console.log('Invalid input for "config" command.');
233+
break;
234+
}
235+
}
236+
)
237+
.command(
238+
'appdata <action>',
239+
'Manage JupyterLab Desktop app data',
240+
yargs => {
241+
yargs.positional('action', {
242+
describe: 'App data action',
243+
choices: ['list'],
244+
default: 'list'
245+
});
246+
},
247+
async argv => {
248+
console.log('Note: This is an experimental feature.');
249+
250+
const action = argv.action;
251+
switch (action) {
252+
case 'list':
253+
handleAppDataListCommand(argv);
254+
break;
255+
default:
256+
console.log('Invalid input for "appdata" command.');
257+
break;
258+
}
259+
}
260+
)
261+
.command(
262+
'logs <action>',
263+
'Manage JupyterLab Desktop logs',
264+
yargs => {
265+
yargs.positional('action', {
266+
describe: 'Logs action',
267+
choices: ['show', 'open'],
268+
default: 'show'
269+
});
270+
},
271+
async argv => {
272+
console.log('Note: This is an experimental feature.');
273+
274+
const action = argv.action;
275+
switch (action) {
276+
case 'show':
277+
handleLogsShowCommand(argv);
278+
break;
279+
case 'open':
280+
handleLogsOpenCommand(argv);
281+
break;
282+
default:
283+
console.log('Invalid input for "logs" command.');
284+
break;
285+
}
286+
}
287+
)
191288
.parseAsync();
192289
}
193290

@@ -816,6 +913,225 @@ export async function handleEnvSetSystemPythonPathCommand(argv: any) {
816913
userSettings.save();
817914
}
818915

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

src/main/config/appdata.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class ApplicationData {
5454
}
5555

5656
read() {
57-
const appDataPath = this._getAppDataPath();
57+
const appDataPath = ApplicationData.getAppDataPath();
5858
if (!fs.existsSync(appDataPath)) {
5959
return;
6060
}
@@ -176,7 +176,7 @@ export class ApplicationData {
176176
}
177177

178178
save() {
179-
const appDataPath = this._getAppDataPath();
179+
const appDataPath = ApplicationData.getAppDataPath();
180180
const appDataJSON: { [key: string]: any } = {};
181181

182182
if (this.pythonPath !== '') {
@@ -373,7 +373,7 @@ export class ApplicationData {
373373
return this._recentSessionsChanged;
374374
}
375375

376-
private _getAppDataPath(): string {
376+
static getAppDataPath(): string {
377377
const userDataDir = getUserDataDir();
378378
return path.join(userDataDir, 'app-data.json');
379379
}

0 commit comments

Comments
 (0)