@wpmoo/toolkit 0.9.4 → 0.9.6
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/dist/cli.js +393 -41
- package/dist/cockpit/command-registry.js +4 -0
- package/dist/cockpit/daily-prompts.js +29 -11
- package/dist/cockpit/menu.js +56 -13
- package/dist/cockpit/module-action-menu.js +40 -0
- package/dist/cockpit/module-browser.js +117 -0
- package/dist/daily-actions.js +40 -3
- package/dist/databases.js +46 -0
- package/dist/help.js +2 -2
- package/dist/menu-navigation.js +2 -2
- package/dist/module-actions.js +50 -1
- package/dist/prompts/index.js +65 -12
- package/dist/service-runtime-status.js +48 -0
- package/dist/templates.js +44 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,26 +2,32 @@
|
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { rm, rename } from 'node:fs/promises';
|
|
4
4
|
import { basename, relative, resolve } from 'node:path';
|
|
5
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
5
6
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
7
|
import { commandFromArgs, isHelpRequested, isVersionRequested, optionsFromArgs, parseArgs, stripInternalFlags, } from './args.js';
|
|
8
|
+
import { cockpitCommands } from './cockpit/command-registry.js';
|
|
7
9
|
import { collectDailyActionArgs } from './cockpit/daily-prompts.js';
|
|
10
|
+
import { selectModuleAction } from './cockpit/module-action-menu.js';
|
|
11
|
+
import { selectModuleFromBrowser } from './cockpit/module-browser.js';
|
|
8
12
|
import { selectCockpitTopLevelMenu } from './cockpit/menu.js';
|
|
9
13
|
import { confirmCockpitCommandRisk } from './cockpit/safety.js';
|
|
10
14
|
import { detectDevelopmentEnvironment } from './environment.js';
|
|
11
15
|
import { commandOdooVersion } from './environment-version.js';
|
|
12
16
|
import { defaultAgentSkillsTemplateUrl } from './external-templates.js';
|
|
13
|
-
import {
|
|
17
|
+
import { listEnvironmentDatabases } from './databases.js';
|
|
18
|
+
import { isDailyActionCommand, runDailyAction, runDailyActionWithStyledOutput } from './daily-actions.js';
|
|
14
19
|
import { getDoctorReport, runDoctor } from './doctor.js';
|
|
15
20
|
import { getOriginUrl, realGit } from './git.js';
|
|
16
21
|
import { renderHelp } from './help.js';
|
|
17
22
|
import { runLocalCockpit } from './local-cockpit.js';
|
|
18
|
-
import { addModuleToSourceRepo,
|
|
23
|
+
import { addModuleToSourceRepo, removeModuleFromSourceRepo, } from './module-actions.js';
|
|
19
24
|
import { supportedOdooVersions } from './odoo-versions.js';
|
|
20
25
|
import { renderRepositorySetupNote } from './prompt-copy.js';
|
|
21
26
|
import { promptRepositoryUrl } from './prompt-repositories.js';
|
|
22
27
|
import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-url.js';
|
|
23
28
|
import { addModuleRepo, listModuleRepos, removeModuleRepo } from './repo-actions.js';
|
|
24
29
|
import { renderSafeResetPreview, safeResetEnvironment } from './safe-reset.js';
|
|
30
|
+
import { getServiceRuntimeStatus, renderServiceRuntimeStatusLine, } from './service-runtime-status.js';
|
|
25
31
|
import { listSources, renderSourceList, sourceListJson, sourceSyncJson, syncSources, } from './source-actions.js';
|
|
26
32
|
import { backupTargetPath, expectedTargetConfirmation, inspectEnvironmentTarget, renderExistingEnvironmentSummary, renderForeignEnvironmentTargetWarning, } from './environment-target-preflight.js';
|
|
27
33
|
import { getGitHubPrerequisiteStatus, renderGitHubPrerequisiteGuidance, } from './github-prerequisites.js';
|
|
@@ -195,17 +201,39 @@ function renderStartupBanner(details, latestVersion) {
|
|
|
195
201
|
const versionLine = startupVersionLine(latestVersion);
|
|
196
202
|
return renderBanner(details?.(versionLine), details ? { version: versionLine } : undefined);
|
|
197
203
|
}
|
|
198
|
-
function renderCockpitStatusLines(status, lastStatus) {
|
|
199
|
-
return [renderStartupEnvironmentLine(status), lastStatus];
|
|
204
|
+
function renderCockpitStatusLines(status, serviceStatus, lastStatus) {
|
|
205
|
+
return [renderStartupEnvironmentLine(status), renderServiceRuntimeStatusLine(serviceStatus), lastStatus];
|
|
200
206
|
}
|
|
201
207
|
function renderLastCommandStatus(command) {
|
|
202
208
|
return `Last: ${command.label} ✓ completed`;
|
|
203
209
|
}
|
|
210
|
+
function renderLastCommandError(command, error) {
|
|
211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
212
|
+
return `Last: ${command.label} ✗ Error: ${message}`;
|
|
213
|
+
}
|
|
204
214
|
function clearCockpitScreen() {
|
|
205
215
|
if (process.stdout.isTTY) {
|
|
206
216
|
process.stdout.write('\u001B[2J\u001B[H');
|
|
207
217
|
}
|
|
208
218
|
}
|
|
219
|
+
const ANSI_ACTION = '\u001B[38;2;226;184;96m';
|
|
220
|
+
const ANSI_SUCCESS = '\u001B[32m';
|
|
221
|
+
const ANSI_DEFAULT_FOREGROUND = '\u001B[39m';
|
|
222
|
+
const ANSI_DIM_INFO = '\u001B[2m\u001B[38;2;120;157;181m';
|
|
223
|
+
const ANSI_RESET = '\u001B[0m';
|
|
224
|
+
function renderActionText(value) {
|
|
225
|
+
return ansi(value, ANSI_ACTION, ANSI_DEFAULT_FOREGROUND);
|
|
226
|
+
}
|
|
227
|
+
function renderCompletedText(action) {
|
|
228
|
+
if (!supportsAnsi()) {
|
|
229
|
+
return `✓ ${action} completed.`;
|
|
230
|
+
}
|
|
231
|
+
return `${ANSI_SUCCESS}✓${ANSI_DEFAULT_FOREGROUND} ${action} ${ANSI_SUCCESS}completed${ANSI_DEFAULT_FOREGROUND}.`;
|
|
232
|
+
}
|
|
233
|
+
function renderBackHelp() {
|
|
234
|
+
return ansi('Esc to go back', ANSI_DIM_INFO, ANSI_RESET);
|
|
235
|
+
}
|
|
236
|
+
const manualDatabaseValue = '__wpmoo_manual_database_entry__';
|
|
209
237
|
async function showStartup(argv, skipUpdateCheck, details) {
|
|
210
238
|
if (skipUpdateCheck) {
|
|
211
239
|
console.log(renderStartupBanner(details));
|
|
@@ -245,8 +273,8 @@ async function showStartup(argv, skipUpdateCheck, details) {
|
|
|
245
273
|
}
|
|
246
274
|
console.log();
|
|
247
275
|
}
|
|
248
|
-
async function selectCockpitCommandFromMenu() {
|
|
249
|
-
const selection = await selectCockpitTopLevelMenu();
|
|
276
|
+
async function selectCockpitCommandFromMenu(serviceStatus) {
|
|
277
|
+
const selection = await selectCockpitTopLevelMenu({ serviceStatus });
|
|
250
278
|
if (selection.kind === 'exit') {
|
|
251
279
|
return 'exit';
|
|
252
280
|
}
|
|
@@ -598,9 +626,6 @@ async function selectSourceRepo(target, cancelAction = 'exit') {
|
|
|
598
626
|
}
|
|
599
627
|
return { repoPath: String(selected), sourceType: 'private' };
|
|
600
628
|
}
|
|
601
|
-
function formatSourceRepoPromptPath(target, selected) {
|
|
602
|
-
return renderedSourceRepoPath(target, selected.sourceType, selected.repoPath);
|
|
603
|
-
}
|
|
604
629
|
function suggestedModuleName(repoPath) {
|
|
605
630
|
return 'odoo_sample_module';
|
|
606
631
|
}
|
|
@@ -796,23 +821,18 @@ function removeModuleOptionsFromArgs(argv) {
|
|
|
796
821
|
};
|
|
797
822
|
}
|
|
798
823
|
async function removeModuleOptionsFromPrompts(showIntro = true, cancelAction = 'exit') {
|
|
799
|
-
|
|
824
|
+
if (showIntro) {
|
|
825
|
+
introPrompt('Remove module');
|
|
826
|
+
}
|
|
800
827
|
const target = process.cwd();
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
if (modules.length === 0) {
|
|
828
|
+
const selectedModule = await selectModuleFromBrowser(target, { cancelAction });
|
|
829
|
+
if (!selectedModule) {
|
|
804
830
|
if (cancelAction === 'back') {
|
|
805
|
-
notePrompt(
|
|
831
|
+
notePrompt('No Odoo modules found.\nNext: choose "Add module" or "Add source repo" first.', 'Nothing to remove');
|
|
806
832
|
handleUnavailableMenuChoice(cancelAction);
|
|
807
833
|
}
|
|
808
|
-
throw new Error(
|
|
834
|
+
throw new Error('No Odoo modules found');
|
|
809
835
|
}
|
|
810
|
-
const moduleName = await selectPrompt({
|
|
811
|
-
message: menuPromptMessage('Module to remove', cancelAction),
|
|
812
|
-
options: modules.map((module) => ({ value: module, label: module })),
|
|
813
|
-
initialValue: modules[0],
|
|
814
|
-
});
|
|
815
|
-
handleCancel(moduleName, cancelAction);
|
|
816
836
|
const deleteFiles = await confirmPrompt({
|
|
817
837
|
message: menuPromptMessage('Delete module files too? (y/N)', cancelAction),
|
|
818
838
|
active: 'Y',
|
|
@@ -822,9 +842,9 @@ async function removeModuleOptionsFromPrompts(showIntro = true, cancelAction = '
|
|
|
822
842
|
handleCancel(deleteFiles, cancelAction);
|
|
823
843
|
return {
|
|
824
844
|
target,
|
|
825
|
-
repoPath:
|
|
826
|
-
sourceType:
|
|
827
|
-
moduleName:
|
|
845
|
+
repoPath: selectedModule.repoPath,
|
|
846
|
+
sourceType: selectedModule.sourceType,
|
|
847
|
+
moduleName: selectedModule.moduleName,
|
|
828
848
|
deleteFiles: Boolean(deleteFiles),
|
|
829
849
|
stage: true,
|
|
830
850
|
};
|
|
@@ -963,18 +983,327 @@ async function finishCreateFlow(result, cwd, interactive) {
|
|
|
963
983
|
}
|
|
964
984
|
outroPrompt(`Created Odoo dev overlay in ${options.target}. Review staged changes, then commit.`);
|
|
965
985
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
986
|
+
function selectedModuleRemovalOptions(module, cwd, deleteFiles) {
|
|
987
|
+
return {
|
|
988
|
+
target: cwd,
|
|
989
|
+
repoPath: module.repoPath,
|
|
990
|
+
sourceType: module.sourceType,
|
|
991
|
+
moduleName: module.moduleName,
|
|
992
|
+
deleteFiles,
|
|
993
|
+
stage: true,
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function moduleDailyAction(action) {
|
|
997
|
+
if (action === 'update')
|
|
998
|
+
return 'update';
|
|
999
|
+
if (action === 'test')
|
|
1000
|
+
return 'test';
|
|
1001
|
+
if (action === 'lint')
|
|
1002
|
+
return 'lint';
|
|
1003
|
+
return undefined;
|
|
1004
|
+
}
|
|
1005
|
+
function moduleDailyActionArgs(action, module) {
|
|
1006
|
+
if (action === 'update' || action === 'test') {
|
|
1007
|
+
return [module.moduleName];
|
|
969
1008
|
}
|
|
970
|
-
|
|
971
|
-
|
|
1009
|
+
return [];
|
|
1010
|
+
}
|
|
1011
|
+
function moduleActionTitle(action) {
|
|
1012
|
+
if (action === 'update')
|
|
1013
|
+
return 'Update module';
|
|
1014
|
+
if (action === 'test')
|
|
1015
|
+
return 'Test module';
|
|
1016
|
+
if (action === 'lint')
|
|
1017
|
+
return 'Run lint';
|
|
1018
|
+
if (action === 'delete')
|
|
1019
|
+
return 'Delete module';
|
|
1020
|
+
return 'Module action';
|
|
1021
|
+
}
|
|
1022
|
+
function moduleActionCompletedLabel(action) {
|
|
1023
|
+
if (action === 'update')
|
|
1024
|
+
return 'Update';
|
|
1025
|
+
if (action === 'test')
|
|
1026
|
+
return 'Test';
|
|
1027
|
+
if (action === 'lint')
|
|
1028
|
+
return 'Lint';
|
|
1029
|
+
return 'Action';
|
|
1030
|
+
}
|
|
1031
|
+
function commandActionTitle(command) {
|
|
1032
|
+
if (command === 'update')
|
|
1033
|
+
return 'Update module';
|
|
1034
|
+
if (command === 'test')
|
|
1035
|
+
return 'Test module';
|
|
1036
|
+
if (command === 'lint')
|
|
1037
|
+
return 'Run lint';
|
|
1038
|
+
if (command === 'pot')
|
|
1039
|
+
return 'Generate POT';
|
|
1040
|
+
return command;
|
|
1041
|
+
}
|
|
1042
|
+
function commandCompletedLabel(command) {
|
|
1043
|
+
if (command === 'install')
|
|
1044
|
+
return 'Install';
|
|
1045
|
+
if (command === 'update')
|
|
1046
|
+
return 'Update';
|
|
1047
|
+
if (command === 'test')
|
|
1048
|
+
return 'Test';
|
|
1049
|
+
if (command === 'lint')
|
|
1050
|
+
return 'Lint';
|
|
1051
|
+
if (command === 'pot')
|
|
1052
|
+
return 'Generate POT';
|
|
1053
|
+
return command;
|
|
1054
|
+
}
|
|
1055
|
+
function shouldReturnToDailySelection(command) {
|
|
1056
|
+
return ['install', 'update', 'test', 'pot'].includes(command);
|
|
1057
|
+
}
|
|
1058
|
+
function shouldUseModuleBrowserForDailySelection(command) {
|
|
1059
|
+
return ['update', 'test', 'lint', 'pot'].includes(command);
|
|
1060
|
+
}
|
|
1061
|
+
function dailyActionSelectedLabel(command, argv) {
|
|
1062
|
+
if (['install', 'update', 'test', 'pot'].includes(command)) {
|
|
1063
|
+
return argv[0];
|
|
1064
|
+
}
|
|
1065
|
+
return undefined;
|
|
1066
|
+
}
|
|
1067
|
+
async function selectDatabaseArg(cwd, message, fallback, options = {}) {
|
|
1068
|
+
const databases = await listEnvironmentDatabases(cwd, options);
|
|
1069
|
+
if (databases.length > 0) {
|
|
1070
|
+
const selected = await selectPrompt({
|
|
1071
|
+
message: menuPromptMessage(message, 'back'),
|
|
1072
|
+
options: [
|
|
1073
|
+
...databases.map((database) => ({ value: database, label: database })),
|
|
1074
|
+
{ value: manualDatabaseValue, label: 'Manual entry' },
|
|
1075
|
+
],
|
|
1076
|
+
initialValue: databases.includes(fallback) ? fallback : databases[0],
|
|
1077
|
+
});
|
|
1078
|
+
handleCancel(selected, 'back');
|
|
1079
|
+
if (selected !== manualDatabaseValue) {
|
|
1080
|
+
return String(selected);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return asString(await textPrompt({
|
|
1084
|
+
message: menuPromptMessage(message, 'back'),
|
|
1085
|
+
defaultValue: fallback,
|
|
1086
|
+
placeholder: fallback,
|
|
1087
|
+
}), fallback, 'back');
|
|
1088
|
+
}
|
|
1089
|
+
async function collectCockpitModuleDailyActionArgs(command, module, cwd) {
|
|
1090
|
+
const moduleName = module.moduleName;
|
|
1091
|
+
if (command === 'update') {
|
|
1092
|
+
return [moduleName];
|
|
1093
|
+
}
|
|
1094
|
+
if (command === 'test') {
|
|
1095
|
+
return [moduleName];
|
|
1096
|
+
}
|
|
1097
|
+
if (command === 'lint') {
|
|
1098
|
+
return [];
|
|
1099
|
+
}
|
|
1100
|
+
if (command === 'pot') {
|
|
1101
|
+
return [moduleName];
|
|
1102
|
+
}
|
|
1103
|
+
throw new Error(`Unsupported module action command: ${command}`);
|
|
1104
|
+
}
|
|
1105
|
+
async function renderDailyActionResultPageHeader(title, selectedLabel, cwd) {
|
|
1106
|
+
await renderCockpitSubmenuPage(title, cwd);
|
|
1107
|
+
if (selectedLabel) {
|
|
1108
|
+
console.log(renderActionText(selectedLabel));
|
|
1109
|
+
console.log('');
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
async function renderCockpitSubmenuPage(title, cwd) {
|
|
1113
|
+
const status = await getEnvironmentStatus(cwd);
|
|
1114
|
+
const serviceStatus = await getServiceRuntimeStatus(cwd, status);
|
|
1115
|
+
clearCockpitScreen();
|
|
1116
|
+
console.log(renderBanner(renderCockpitStatusLines(status, serviceStatus, `Last: ${title}`), { version: startupVersionLine() }));
|
|
1117
|
+
console.log();
|
|
1118
|
+
introPrompt(title);
|
|
1119
|
+
}
|
|
1120
|
+
async function waitForModuleActionBack() {
|
|
1121
|
+
console.log(renderBackHelp());
|
|
1122
|
+
if (!process.stdin.isTTY) {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
await new Promise((resolve) => {
|
|
1126
|
+
emitKeypressEvents(process.stdin);
|
|
1127
|
+
const input = process.stdin;
|
|
1128
|
+
const wasRaw = input.isRaw;
|
|
1129
|
+
const listener = (_value, key) => {
|
|
1130
|
+
if (key.ctrl && key.name === 'c') {
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
1133
|
+
if (key.name === 'escape' || key.sequence === '\u001B') {
|
|
1134
|
+
cleanup();
|
|
1135
|
+
resolve();
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
const cleanup = () => {
|
|
1139
|
+
input.off('keypress', listener);
|
|
1140
|
+
if (typeof input.setRawMode === 'function') {
|
|
1141
|
+
input.setRawMode(Boolean(wasRaw));
|
|
1142
|
+
}
|
|
1143
|
+
input.pause();
|
|
1144
|
+
};
|
|
1145
|
+
if (typeof input.setRawMode === 'function') {
|
|
1146
|
+
input.setRawMode(true);
|
|
1147
|
+
}
|
|
1148
|
+
input.resume();
|
|
1149
|
+
input.on('keypress', listener);
|
|
1150
|
+
});
|
|
1151
|
+
return true;
|
|
1152
|
+
}
|
|
1153
|
+
async function runDailyActionResultPage(command, argv, cwd, title = commandActionTitle(command), selectedLabel = dailyActionSelectedLabel(command, argv), completedLabel = commandCompletedLabel(command)) {
|
|
1154
|
+
await renderDailyActionResultPageHeader(title, selectedLabel, cwd);
|
|
1155
|
+
try {
|
|
1156
|
+
await runDailyActionWithStyledOutput(command, argv, cwd);
|
|
1157
|
+
notePrompt(renderCompletedText(completedLabel), 'Done');
|
|
1158
|
+
}
|
|
1159
|
+
catch (error) {
|
|
1160
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1161
|
+
notePrompt(message, 'Error');
|
|
1162
|
+
await waitForModuleActionBack();
|
|
1163
|
+
throw error;
|
|
1164
|
+
}
|
|
1165
|
+
return waitForModuleActionBack();
|
|
1166
|
+
}
|
|
1167
|
+
async function runSelectedModuleDailyAction(action, module, cwd) {
|
|
1168
|
+
const command = moduleDailyAction(action);
|
|
1169
|
+
if (!command) {
|
|
1170
|
+
return false;
|
|
1171
|
+
}
|
|
1172
|
+
return runDailyActionResultPage(command, moduleDailyActionArgs(action, module), cwd, moduleActionTitle(action), module.moduleName, moduleActionCompletedLabel(action));
|
|
1173
|
+
}
|
|
1174
|
+
async function runSelectedModuleAction(action, module, cwd) {
|
|
1175
|
+
if (action === 'delete') {
|
|
1176
|
+
const deleteFiles = await confirmPrompt({
|
|
1177
|
+
message: menuPromptMessage('Delete module files too? (y/N)', 'back'),
|
|
1178
|
+
active: 'Y',
|
|
1179
|
+
inactive: 'n',
|
|
1180
|
+
initialValue: false,
|
|
1181
|
+
});
|
|
1182
|
+
handleCancel(deleteFiles, 'back');
|
|
1183
|
+
const removeCommand = cockpitCommands.find((entry) => entry.id === 'remove-module');
|
|
1184
|
+
if (removeCommand && !(await confirmCockpitCommandRisk(removeCommand))) {
|
|
1185
|
+
notePrompt(`Module ${module.moduleName} was not removed.`, 'Action skipped');
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
await removeModuleFromSourceRepo(selectedModuleRemovalOptions(module, cwd, Boolean(deleteFiles)));
|
|
1189
|
+
notePrompt(`Removed module ${module.moduleName} from source repo ${module.repoPath}.`, 'Done');
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
return runSelectedModuleDailyAction(action, module, cwd);
|
|
1193
|
+
}
|
|
1194
|
+
async function runCockpitModuleDailyCommand(command, cwd) {
|
|
1195
|
+
if (command.target.kind !== 'daily') {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const dailyCommand = command.target.command;
|
|
1199
|
+
while (true) {
|
|
1200
|
+
let selectedModule;
|
|
1201
|
+
let argv;
|
|
1202
|
+
try {
|
|
1203
|
+
await renderCockpitSubmenuPage(command.label, cwd);
|
|
1204
|
+
const module = await selectModuleFromBrowser(cwd);
|
|
1205
|
+
if (!module) {
|
|
1206
|
+
notePrompt('No Odoo modules found.\nNext: choose "Add module" or "Add source repo" first.', command.label);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
selectedModule = module;
|
|
1210
|
+
argv = await collectCockpitModuleDailyActionArgs(dailyCommand, selectedModule, cwd);
|
|
1211
|
+
}
|
|
1212
|
+
catch (error) {
|
|
1213
|
+
if (isMenuBackSignal(error)) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
972
1218
|
if (!(await confirmCockpitCommandRisk(command))) {
|
|
973
1219
|
notePrompt(`${command.slashAlias} was not run.`, 'Action skipped');
|
|
974
|
-
return
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const returnedByBack = await runDailyActionResultPage(dailyCommand, argv, cwd, command.label, selectedModule.moduleName, commandCompletedLabel(dailyCommand));
|
|
1223
|
+
if (!returnedByBack) {
|
|
1224
|
+
return;
|
|
975
1225
|
}
|
|
976
|
-
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async function runCockpitDailyCommand(command, cwd) {
|
|
1229
|
+
if (command.target.kind !== 'daily') {
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const dailyCommand = command.target.command;
|
|
1233
|
+
if (shouldUseModuleBrowserForDailySelection(dailyCommand)) {
|
|
1234
|
+
await runCockpitModuleDailyCommand(command, cwd);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (!shouldReturnToDailySelection(dailyCommand)) {
|
|
1238
|
+
const argv = await collectDailyActionArgs(dailyCommand, cwd);
|
|
1239
|
+
if (!(await confirmCockpitCommandRisk(command))) {
|
|
1240
|
+
notePrompt(`${command.slashAlias} was not run.`, 'Action skipped');
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
await runDailyAction(dailyCommand, argv, cwd);
|
|
977
1244
|
notePrompt(`${command.slashAlias} completed.`, 'Done');
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
while (true) {
|
|
1248
|
+
let argv;
|
|
1249
|
+
try {
|
|
1250
|
+
await renderCockpitSubmenuPage(command.label, cwd);
|
|
1251
|
+
argv = await collectDailyActionArgs(dailyCommand, cwd);
|
|
1252
|
+
}
|
|
1253
|
+
catch (error) {
|
|
1254
|
+
if (isMenuBackSignal(error)) {
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
throw error;
|
|
1258
|
+
}
|
|
1259
|
+
if (!(await confirmCockpitCommandRisk(command))) {
|
|
1260
|
+
notePrompt(`${command.slashAlias} was not run.`, 'Action skipped');
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const returnedByBack = await runDailyActionResultPage(dailyCommand, argv, cwd, command.label);
|
|
1264
|
+
if (!returnedByBack) {
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
async function runListModulesCommand(cwd) {
|
|
1270
|
+
while (true) {
|
|
1271
|
+
await renderCockpitSubmenuPage('List modules', cwd);
|
|
1272
|
+
const selectedModule = await selectModuleFromBrowser(cwd);
|
|
1273
|
+
if (!selectedModule) {
|
|
1274
|
+
notePrompt('No Odoo modules found.\nNext: choose "Add module" or "Add source repo" first.', 'List modules');
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
while (true) {
|
|
1278
|
+
let moduleAction;
|
|
1279
|
+
try {
|
|
1280
|
+
await renderCockpitSubmenuPage('List modules', cwd);
|
|
1281
|
+
console.log(renderActionText(selectedModule.moduleName));
|
|
1282
|
+
console.log('');
|
|
1283
|
+
moduleAction = await selectModuleAction(selectedModule);
|
|
1284
|
+
}
|
|
1285
|
+
catch (error) {
|
|
1286
|
+
if (isMenuBackSignal(error)) {
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
throw error;
|
|
1290
|
+
}
|
|
1291
|
+
if (!moduleAction) {
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
const returnedByBack = await runSelectedModuleAction(moduleAction, selectedModule, cwd);
|
|
1295
|
+
if (!returnedByBack) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
async function runCockpitCommand(command, cwd) {
|
|
1302
|
+
if (command.id === 'exit') {
|
|
1303
|
+
return 'exit';
|
|
1304
|
+
}
|
|
1305
|
+
if (command.target.kind === 'daily') {
|
|
1306
|
+
await runCockpitDailyCommand(command, cwd);
|
|
978
1307
|
return 'continue';
|
|
979
1308
|
}
|
|
980
1309
|
if (command.id === 'status') {
|
|
@@ -985,6 +1314,10 @@ async function runCockpitCommand(command, cwd) {
|
|
|
985
1314
|
notePrompt(await runDoctor(cwd), 'Doctor');
|
|
986
1315
|
return 'continue';
|
|
987
1316
|
}
|
|
1317
|
+
if (command.id === 'list-modules') {
|
|
1318
|
+
await runListModulesCommand(cwd);
|
|
1319
|
+
return 'continue';
|
|
1320
|
+
}
|
|
988
1321
|
if (command.id === 'add-repo') {
|
|
989
1322
|
const options = await addRepoOptionsFromPrompts(false, 'back');
|
|
990
1323
|
await ensureAddRepoGitHubRepository(options, 'back');
|
|
@@ -1009,7 +1342,7 @@ async function runCockpitCommand(command, cwd) {
|
|
|
1009
1342
|
return 'continue';
|
|
1010
1343
|
}
|
|
1011
1344
|
if (command.id === 'remove-module') {
|
|
1012
|
-
const options = await removeModuleOptionsFromPrompts(
|
|
1345
|
+
const options = await removeModuleOptionsFromPrompts(true, 'back');
|
|
1013
1346
|
if (!(await confirmCockpitCommandRisk(command))) {
|
|
1014
1347
|
notePrompt(`Module ${options.moduleName} was not removed.`, 'Action skipped');
|
|
1015
1348
|
return 'continue';
|
|
@@ -1050,23 +1383,42 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
1050
1383
|
return;
|
|
1051
1384
|
}
|
|
1052
1385
|
let lastStatus = 'Last: Ready';
|
|
1053
|
-
|
|
1054
|
-
|
|
1386
|
+
let status = await getEnvironmentStatus(cwd);
|
|
1387
|
+
let serviceStatus = await getServiceRuntimeStatus(cwd, status);
|
|
1388
|
+
await showStartup(argv, skipUpdateCheck, () => renderCockpitStatusLines(status, serviceStatus, lastStatus));
|
|
1389
|
+
const renderCockpitMenuShell = () => {
|
|
1390
|
+
clearCockpitScreen();
|
|
1391
|
+
console.log(renderBanner(renderCockpitStatusLines(status, serviceStatus, lastStatus), { version: startupVersionLine() }));
|
|
1392
|
+
console.log();
|
|
1393
|
+
};
|
|
1055
1394
|
while (true) {
|
|
1056
1395
|
try {
|
|
1057
|
-
const command = await selectCockpitCommandFromMenu();
|
|
1396
|
+
const command = await selectCockpitCommandFromMenu(serviceStatus);
|
|
1058
1397
|
if (command === 'exit') {
|
|
1059
1398
|
return;
|
|
1060
1399
|
}
|
|
1061
|
-
|
|
1400
|
+
let outcome = 'continue';
|
|
1401
|
+
let commandFailed = false;
|
|
1402
|
+
try {
|
|
1403
|
+
outcome = await runCockpitCommand(command, cwd);
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
if (isMenuBackSignal(error)) {
|
|
1407
|
+
renderCockpitMenuShell();
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
commandFailed = true;
|
|
1411
|
+
lastStatus = renderLastCommandError(command, error);
|
|
1412
|
+
}
|
|
1062
1413
|
if (outcome === 'exit') {
|
|
1063
1414
|
return;
|
|
1064
1415
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1416
|
+
if (!commandFailed) {
|
|
1417
|
+
lastStatus = renderLastCommandStatus(command);
|
|
1418
|
+
}
|
|
1419
|
+
status = await getEnvironmentStatus(cwd);
|
|
1420
|
+
serviceStatus = await getServiceRuntimeStatus(cwd, status);
|
|
1421
|
+
renderCockpitMenuShell();
|
|
1070
1422
|
}
|
|
1071
1423
|
catch (error) {
|
|
1072
1424
|
if (isMenuBackSignal(error)) {
|
|
@@ -34,6 +34,10 @@ export const cockpitCommands = [
|
|
|
34
34
|
dailyCommand('restart', 'services', 'Restart services', 'Restart the Odoo development services.', ['reload']),
|
|
35
35
|
dailyCommand('logs', 'services', 'View logs', 'Stream logs for an Odoo environment service.', ['log', 'tail']),
|
|
36
36
|
dailyCommand('shell', 'services', 'Open shell', 'Open a shell inside the Odoo service container.', ['bash', 'terminal']),
|
|
37
|
+
internalCommand('list-modules', 'modules', 'List modules', 'Browse detected Odoo modules by source category.', [
|
|
38
|
+
'modules list',
|
|
39
|
+
'browse modules',
|
|
40
|
+
]),
|
|
37
41
|
dailyCommand('install', 'modules', 'Install module', 'Install one or more Odoo modules into a database.', ['install module']),
|
|
38
42
|
dailyCommand('update', 'modules', 'Update module', 'Update one or more Odoo modules in a database.', ['upgrade']),
|
|
39
43
|
dailyCommand('test', 'modules', 'Run tests', 'Run Odoo tests for one or more modules.', ['tests', 'pytest']),
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { listEnvironmentDatabases } from '../databases.js';
|
|
1
2
|
import { listModulesInSourceRepo } from '../module-actions.js';
|
|
2
3
|
import { listModuleRepos } from '../repo-actions.js';
|
|
3
4
|
import { listSources } from '../source-actions.js';
|
|
4
5
|
import { handlePromptCancel, menuPromptMessage, } from '../menu-navigation.js';
|
|
5
6
|
import { isPromptCancel, selectPrompt, textPrompt } from '../prompts/index.js';
|
|
6
7
|
const manualModuleValue = '__wpmoo_manual_module_entry__';
|
|
8
|
+
const manualDatabaseValue = '__wpmoo_manual_database_entry__';
|
|
7
9
|
function defaultCancelHandler(value, action) {
|
|
8
10
|
handlePromptCancel(isPromptCancel(value), action);
|
|
9
11
|
}
|
|
@@ -12,6 +14,7 @@ function promptDeps(deps = {}) {
|
|
|
12
14
|
select: deps.select ?? ((options) => selectPrompt(options)),
|
|
13
15
|
text: deps.text ?? ((options) => textPrompt(options)),
|
|
14
16
|
list: deps.list ?? ((options) => selectPrompt(options)),
|
|
17
|
+
databases: deps.databases ?? ((cwd, options) => listEnvironmentDatabases(cwd, options)),
|
|
15
18
|
handleCancel: deps.handleCancel ?? defaultCancelHandler,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
@@ -80,6 +83,24 @@ async function optionalTextArg(deps, message, fallback) {
|
|
|
80
83
|
placeholder: fallback,
|
|
81
84
|
}), fallback, deps);
|
|
82
85
|
}
|
|
86
|
+
async function databaseArg(cwd, deps, message, fallback, options = {}) {
|
|
87
|
+
const databases = await deps.databases(cwd, options);
|
|
88
|
+
if (databases.length > 0) {
|
|
89
|
+
const selected = await deps.list({
|
|
90
|
+
message: menuPromptMessage(message, 'back'),
|
|
91
|
+
options: [
|
|
92
|
+
...databases.map((database) => ({ value: database, label: database })),
|
|
93
|
+
{ value: manualDatabaseValue, label: 'Manual entry' },
|
|
94
|
+
],
|
|
95
|
+
initialValue: databases.includes(fallback) ? fallback : databases[0],
|
|
96
|
+
});
|
|
97
|
+
deps.handleCancel(selected, 'back');
|
|
98
|
+
if (selected !== manualDatabaseValue) {
|
|
99
|
+
return String(selected);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return optionalTextArg(deps, message, fallback);
|
|
103
|
+
}
|
|
83
104
|
async function optionalModules(cwd, deps) {
|
|
84
105
|
const modules = await detectedModules(cwd);
|
|
85
106
|
if (modules.length === 0) {
|
|
@@ -120,19 +141,16 @@ export async function collectDailyActionArgs(command, cwd, promptDepsArg = {}) {
|
|
|
120
141
|
return [await optionalTextArg(deps, 'Service', 'odoo')];
|
|
121
142
|
}
|
|
122
143
|
if (command === 'psql') {
|
|
123
|
-
return [await
|
|
144
|
+
return [await databaseArg(cwd, deps, 'Database', 'postgres', { includeMaintenance: true })];
|
|
124
145
|
}
|
|
125
146
|
if (command === 'install' || command === 'update') {
|
|
126
147
|
const modules = await moduleArg(cwd, deps);
|
|
127
|
-
const db =
|
|
128
|
-
|
|
129
|
-
placeholder: 'devel',
|
|
130
|
-
}), '', deps);
|
|
131
|
-
return db ? [modules, db] : [modules];
|
|
148
|
+
const db = await databaseArg(cwd, deps, 'Odoo database', 'devel');
|
|
149
|
+
return [modules, db];
|
|
132
150
|
}
|
|
133
151
|
if (command === 'test') {
|
|
134
152
|
const modules = await moduleArg(cwd, deps);
|
|
135
|
-
const db = await
|
|
153
|
+
const db = await databaseArg(cwd, deps, 'Odoo database', 'devel');
|
|
136
154
|
const mode = asString(await deps.list({
|
|
137
155
|
message: menuPromptMessage('Mode', 'back'),
|
|
138
156
|
options: [
|
|
@@ -151,17 +169,17 @@ export async function collectDailyActionArgs(command, cwd, promptDepsArg = {}) {
|
|
|
151
169
|
}
|
|
152
170
|
if (command === 'pot') {
|
|
153
171
|
const modules = await moduleArg(cwd, deps);
|
|
154
|
-
const db = await
|
|
172
|
+
const db = await databaseArg(cwd, deps, 'Odoo database', 'devel');
|
|
155
173
|
const output = await optionalTextArg(deps, 'Output file', `i18n/${modules}.pot`);
|
|
156
174
|
return [modules, db, output];
|
|
157
175
|
}
|
|
158
176
|
if (command === 'resetdb') {
|
|
159
|
-
const db = await
|
|
177
|
+
const db = await databaseArg(cwd, deps, 'Odoo database', 'devel');
|
|
160
178
|
const modules = await optionalModules(cwd, deps);
|
|
161
179
|
return modules ? [db, modules] : [db];
|
|
162
180
|
}
|
|
163
181
|
if (command === 'snapshot') {
|
|
164
|
-
const db = await
|
|
182
|
+
const db = await databaseArg(cwd, deps, 'Odoo database', 'devel');
|
|
165
183
|
const snapshotName = await optionalTextArg(deps, 'Snapshot name', 'before-update');
|
|
166
184
|
return [db, snapshotName];
|
|
167
185
|
}
|
|
@@ -170,7 +188,7 @@ export async function collectDailyActionArgs(command, cwd, promptDepsArg = {}) {
|
|
|
170
188
|
message: menuPromptMessage('Snapshot name', 'back'),
|
|
171
189
|
validate: (value) => (value.trim() ? undefined : 'Enter the snapshot name.'),
|
|
172
190
|
}), 'Snapshot name is required.', deps);
|
|
173
|
-
const db = await
|
|
191
|
+
const db = await databaseArg(cwd, deps, 'Odoo database', 'devel');
|
|
174
192
|
return [snapshotName, db];
|
|
175
193
|
}
|
|
176
194
|
return [];
|