@wpmoo/toolkit 0.9.3 → 0.9.5
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 +81 -23
- package/dist/cockpit/menu.js +52 -12
- package/dist/local-cockpit.js +15 -0
- package/dist/prompts/index.js +10 -5
- package/dist/service-runtime-status.js +48 -0
- package/dist/templates.js +41 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isDailyActionCommand, runDailyAction } from './daily-actions.js';
|
|
|
14
14
|
import { getDoctorReport, runDoctor } from './doctor.js';
|
|
15
15
|
import { getOriginUrl, realGit } from './git.js';
|
|
16
16
|
import { renderHelp } from './help.js';
|
|
17
|
+
import { runLocalCockpit } from './local-cockpit.js';
|
|
17
18
|
import { addModuleToSourceRepo, listModulesInSourceRepo, removeModuleFromSourceRepo, } from './module-actions.js';
|
|
18
19
|
import { supportedOdooVersions } from './odoo-versions.js';
|
|
19
20
|
import { renderRepositorySetupNote } from './prompt-copy.js';
|
|
@@ -21,6 +22,7 @@ import { promptRepositoryUrl } from './prompt-repositories.js';
|
|
|
21
22
|
import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-url.js';
|
|
22
23
|
import { addModuleRepo, listModuleRepos, removeModuleRepo } from './repo-actions.js';
|
|
23
24
|
import { renderSafeResetPreview, safeResetEnvironment } from './safe-reset.js';
|
|
25
|
+
import { getServiceRuntimeStatus, renderServiceRuntimeStatusLine, } from './service-runtime-status.js';
|
|
24
26
|
import { listSources, renderSourceList, sourceListJson, sourceSyncJson, syncSources, } from './source-actions.js';
|
|
25
27
|
import { backupTargetPath, expectedTargetConfirmation, inspectEnvironmentTarget, renderExistingEnvironmentSummary, renderForeignEnvironmentTargetWarning, } from './environment-target-preflight.js';
|
|
26
28
|
import { getGitHubPrerequisiteStatus, renderGitHubPrerequisiteGuidance, } from './github-prerequisites.js';
|
|
@@ -114,10 +116,25 @@ function jsonOption(values) {
|
|
|
114
116
|
function printJson(value) {
|
|
115
117
|
console.log(JSON.stringify(value));
|
|
116
118
|
}
|
|
117
|
-
function
|
|
118
|
-
|
|
119
|
+
function supportsAnsi() {
|
|
120
|
+
return Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined;
|
|
121
|
+
}
|
|
122
|
+
function ansi(value, open, close) {
|
|
123
|
+
if (!supportsAnsi())
|
|
119
124
|
return value;
|
|
120
|
-
return
|
|
125
|
+
return `${open}${value}${close}`;
|
|
126
|
+
}
|
|
127
|
+
function yellow(value) {
|
|
128
|
+
return ansi(value, '\u001B[33m', '\u001B[39m');
|
|
129
|
+
}
|
|
130
|
+
function cyan(value) {
|
|
131
|
+
return ansi(value, '\u001B[36m', '\u001B[39m');
|
|
132
|
+
}
|
|
133
|
+
function boldGreen(value) {
|
|
134
|
+
return ansi(value, '\u001B[1m\u001B[32m', '\u001B[39m\u001B[22m');
|
|
135
|
+
}
|
|
136
|
+
function dim(value) {
|
|
137
|
+
return ansi(value, '\u001B[2m', '\u001B[22m');
|
|
121
138
|
}
|
|
122
139
|
function shellQuote(value) {
|
|
123
140
|
if (/^[A-Za-z0-9_./:-]+$/.test(value))
|
|
@@ -132,12 +149,22 @@ function renderedSourceRepoPath(target, sourceType, repoPath) {
|
|
|
132
149
|
}
|
|
133
150
|
function renderPostCreateGuidance(target, cwd) {
|
|
134
151
|
const relativeTarget = relative(cwd, target) || '.';
|
|
135
|
-
|
|
136
|
-
|
|
152
|
+
const cdCommand = `cd ${shellQuote(relativeTarget)}`;
|
|
153
|
+
if (!supportsAnsi()) {
|
|
154
|
+
return [
|
|
155
|
+
'Environment is ready. Open it now, or copy these commands:',
|
|
156
|
+
'',
|
|
157
|
+
cdCommand,
|
|
158
|
+
'./moo',
|
|
159
|
+
].join('\n');
|
|
160
|
+
}
|
|
161
|
+
return [
|
|
162
|
+
boldGreen('✓ Environment is ready.'),
|
|
163
|
+
cyan('Open it now, or copy these commands:'),
|
|
137
164
|
'',
|
|
138
|
-
|
|
139
|
-
'./moo',
|
|
140
|
-
].join('\n')
|
|
165
|
+
yellow(cdCommand),
|
|
166
|
+
yellow('./moo'),
|
|
167
|
+
].join('\n');
|
|
141
168
|
}
|
|
142
169
|
function validateRepoName(value) {
|
|
143
170
|
const normalized = value.trim();
|
|
@@ -169,12 +196,16 @@ function renderStartupBanner(details, latestVersion) {
|
|
|
169
196
|
const versionLine = startupVersionLine(latestVersion);
|
|
170
197
|
return renderBanner(details?.(versionLine), details ? { version: versionLine } : undefined);
|
|
171
198
|
}
|
|
172
|
-
function renderCockpitStatusLines(status, lastStatus) {
|
|
173
|
-
return [renderStartupEnvironmentLine(status), lastStatus];
|
|
199
|
+
function renderCockpitStatusLines(status, serviceStatus, lastStatus) {
|
|
200
|
+
return [renderStartupEnvironmentLine(status), renderServiceRuntimeStatusLine(serviceStatus), lastStatus];
|
|
174
201
|
}
|
|
175
202
|
function renderLastCommandStatus(command) {
|
|
176
203
|
return `Last: ${command.label} ✓ completed`;
|
|
177
204
|
}
|
|
205
|
+
function renderLastCommandError(command, error) {
|
|
206
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
207
|
+
return `Last: ${command.label} ✗ Error: ${message}`;
|
|
208
|
+
}
|
|
178
209
|
function clearCockpitScreen() {
|
|
179
210
|
if (process.stdout.isTTY) {
|
|
180
211
|
process.stdout.write('\u001B[2J\u001B[H');
|
|
@@ -219,8 +250,8 @@ async function showStartup(argv, skipUpdateCheck, details) {
|
|
|
219
250
|
}
|
|
220
251
|
console.log();
|
|
221
252
|
}
|
|
222
|
-
async function selectCockpitCommandFromMenu() {
|
|
223
|
-
const selection = await selectCockpitTopLevelMenu();
|
|
253
|
+
async function selectCockpitCommandFromMenu(serviceStatus) {
|
|
254
|
+
const selection = await selectCockpitTopLevelMenu({ serviceStatus });
|
|
224
255
|
if (selection.kind === 'exit') {
|
|
225
256
|
return 'exit';
|
|
226
257
|
}
|
|
@@ -260,7 +291,7 @@ async function resolveEnvironmentTargetFromPrompts(product, cancelAction) {
|
|
|
260
291
|
message: 'This environment folder already exists. What do you want to do?',
|
|
261
292
|
options: [
|
|
262
293
|
{ value: 'update-existing', label: 'Update existing environment' },
|
|
263
|
-
{ value: 'reinstall-environment', label: '
|
|
294
|
+
{ value: 'reinstall-environment', label: 'Back up existing environment folder and create a new one' },
|
|
264
295
|
{ value: 'delete-environment', label: 'Delete environment' },
|
|
265
296
|
{ value: 'cancel', label: 'Cancel' },
|
|
266
297
|
],
|
|
@@ -837,11 +868,11 @@ async function ensureGitHubRepositories(options, interactive) {
|
|
|
837
868
|
throw new Error(`Dev environment repository is non-empty or could not be verified: ${blocked.map((repo) => repo.slug).join(', ')}`);
|
|
838
869
|
}
|
|
839
870
|
if (interactive && accessible.length > 0) {
|
|
840
|
-
notePrompt([
|
|
871
|
+
notePrompt(dim([
|
|
841
872
|
'These GitHub repositories already exist and are accessible:',
|
|
842
873
|
'',
|
|
843
874
|
...accessible.map((repository) => `- ${repository.label}: ${repository.slug}`),
|
|
844
|
-
].join('\n'), 'Repository check');
|
|
875
|
+
].join('\n')), 'Repository check');
|
|
845
876
|
}
|
|
846
877
|
if (missing.length === 0) {
|
|
847
878
|
return;
|
|
@@ -922,7 +953,19 @@ async function finishCreateFlow(result, cwd, interactive) {
|
|
|
922
953
|
console.log(`- ${command}`);
|
|
923
954
|
return;
|
|
924
955
|
}
|
|
925
|
-
notePrompt(renderPostCreateGuidance(options.target, cwd), 'Next steps');
|
|
956
|
+
notePrompt(renderPostCreateGuidance(options.target, cwd), 'Next steps', { indent: false });
|
|
957
|
+
if (interactive) {
|
|
958
|
+
const shouldOpenCockpit = await confirmPrompt({
|
|
959
|
+
message: 'Open the local WPMoo cockpit now?',
|
|
960
|
+
active: 'Y',
|
|
961
|
+
inactive: 'n',
|
|
962
|
+
initialValue: true,
|
|
963
|
+
});
|
|
964
|
+
if (shouldOpenCockpit === true) {
|
|
965
|
+
await runLocalCockpit(options.target);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
926
969
|
outroPrompt(`Created Odoo dev overlay in ${options.target}. Review staged changes, then commit.`);
|
|
927
970
|
}
|
|
928
971
|
async function runCockpitCommand(command, cwd) {
|
|
@@ -1012,22 +1055,37 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
1012
1055
|
return;
|
|
1013
1056
|
}
|
|
1014
1057
|
let lastStatus = 'Last: Ready';
|
|
1015
|
-
|
|
1016
|
-
|
|
1058
|
+
let status = await getEnvironmentStatus(cwd);
|
|
1059
|
+
let serviceStatus = await getServiceRuntimeStatus(cwd, status);
|
|
1060
|
+
await showStartup(argv, skipUpdateCheck, () => renderCockpitStatusLines(status, serviceStatus, lastStatus));
|
|
1017
1061
|
while (true) {
|
|
1018
1062
|
try {
|
|
1019
|
-
const command = await selectCockpitCommandFromMenu();
|
|
1063
|
+
const command = await selectCockpitCommandFromMenu(serviceStatus);
|
|
1020
1064
|
if (command === 'exit') {
|
|
1021
1065
|
return;
|
|
1022
1066
|
}
|
|
1023
|
-
|
|
1067
|
+
let outcome = 'continue';
|
|
1068
|
+
let commandFailed = false;
|
|
1069
|
+
try {
|
|
1070
|
+
outcome = await runCockpitCommand(command, cwd);
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
if (isMenuBackSignal(error)) {
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
commandFailed = true;
|
|
1077
|
+
lastStatus = renderLastCommandError(command, error);
|
|
1078
|
+
}
|
|
1024
1079
|
if (outcome === 'exit') {
|
|
1025
1080
|
return;
|
|
1026
1081
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1082
|
+
if (!commandFailed) {
|
|
1083
|
+
lastStatus = renderLastCommandStatus(command);
|
|
1084
|
+
}
|
|
1085
|
+
status = await getEnvironmentStatus(cwd);
|
|
1086
|
+
serviceStatus = await getServiceRuntimeStatus(cwd, status);
|
|
1029
1087
|
clearCockpitScreen();
|
|
1030
|
-
console.log(renderBanner(renderCockpitStatusLines(status, lastStatus), { version: startupVersionLine() }));
|
|
1088
|
+
console.log(renderBanner(renderCockpitStatusLines(status, serviceStatus, lastStatus), { version: startupVersionLine() }));
|
|
1031
1089
|
console.log();
|
|
1032
1090
|
}
|
|
1033
1091
|
catch (error) {
|
package/dist/cockpit/menu.js
CHANGED
|
@@ -32,25 +32,51 @@ function categoryHeading(category) {
|
|
|
32
32
|
function commandName(command) {
|
|
33
33
|
return `${rgb(226, 184, 96, ` ${command.label.padEnd(topLevelCommandLabelWidth)}`)}${dim(` ${command.description}`)}`;
|
|
34
34
|
}
|
|
35
|
-
function
|
|
35
|
+
function disabledReason(command, serviceStatus) {
|
|
36
|
+
if (command.category !== 'services' || !serviceStatus)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (serviceStatus.kind === 'docker-not-running')
|
|
39
|
+
return 'Docker not running.';
|
|
40
|
+
if (serviceStatus.kind === 'running' && command.id === 'start')
|
|
41
|
+
return 'Already running.';
|
|
42
|
+
if (serviceStatus.kind === 'stopped' && ['stop', 'restart', 'logs', 'shell'].includes(command.id)) {
|
|
43
|
+
return 'Services stopped.';
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
function disabledMenuReason(serviceStatus) {
|
|
48
|
+
if (serviceStatus?.kind === 'docker-not-running')
|
|
49
|
+
return 'Docker not running.';
|
|
50
|
+
if (serviceStatus?.kind === 'running')
|
|
51
|
+
return 'Already running.';
|
|
52
|
+
if (serviceStatus?.kind === 'stopped')
|
|
53
|
+
return 'Services stopped.';
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function disabledError(serviceStatus) {
|
|
57
|
+
const reason = disabledMenuReason(serviceStatus);
|
|
58
|
+
return reason ? `This option is disabled and cannot be selected.\nReason: ${reason}` : undefined;
|
|
59
|
+
}
|
|
60
|
+
function categoryChoices(category, index, serviceStatus) {
|
|
36
61
|
const choices = [
|
|
37
62
|
promptSeparator(categoryHeading(category)),
|
|
38
63
|
...topLevelCommands
|
|
39
64
|
.filter((command) => command.category === category)
|
|
40
|
-
.map((command) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
.map((command) => {
|
|
66
|
+
const reason = disabledReason(command, serviceStatus);
|
|
67
|
+
return {
|
|
68
|
+
value: command,
|
|
69
|
+
name: commandName(command),
|
|
70
|
+
short: command.label,
|
|
71
|
+
disabled: reason ? true : undefined,
|
|
72
|
+
};
|
|
73
|
+
}),
|
|
45
74
|
];
|
|
46
75
|
if (index < topLevelCategoryOrder.length - 1) {
|
|
47
76
|
choices.push(promptSeparator(' '));
|
|
48
77
|
}
|
|
49
78
|
return choices;
|
|
50
79
|
}
|
|
51
|
-
const topLevelChoices = [
|
|
52
|
-
...topLevelCategoryOrder.flatMap(categoryChoices),
|
|
53
|
-
];
|
|
54
80
|
const minimumTopLevelPageSize = 8;
|
|
55
81
|
const startupViewportReservedRows = 11;
|
|
56
82
|
function topLevelPageSize(choiceCount) {
|
|
@@ -75,15 +101,29 @@ function menuDeps(deps = {}) {
|
|
|
75
101
|
function isCockpitCommand(value) {
|
|
76
102
|
return typeof value === 'object' && value !== null && 'id' in value && 'slashAlias' in value;
|
|
77
103
|
}
|
|
104
|
+
function topLevelChoices(serviceStatus) {
|
|
105
|
+
return topLevelCategoryOrder.flatMap((category, index) => categoryChoices(category, index, serviceStatus));
|
|
106
|
+
}
|
|
107
|
+
function defaultCommand(serviceStatus) {
|
|
108
|
+
if (serviceStatus?.kind === 'running') {
|
|
109
|
+
return cockpitCommands.find((command) => command.id === 'stop') ?? topLevelCommands[0];
|
|
110
|
+
}
|
|
111
|
+
if (serviceStatus?.kind === 'docker-not-running') {
|
|
112
|
+
return cockpitCommands.find((command) => command.id === 'status') ?? topLevelCommands[0];
|
|
113
|
+
}
|
|
114
|
+
return cockpitCommands.find((command) => command.id === 'start') ?? topLevelCommands[0];
|
|
115
|
+
}
|
|
78
116
|
export async function selectCockpitTopLevelMenu(options = {}) {
|
|
79
117
|
const deps = menuDeps(options);
|
|
118
|
+
const choices = topLevelChoices(options.serviceStatus);
|
|
80
119
|
const selected = await deps.select({
|
|
81
120
|
message: '',
|
|
82
|
-
choices: [...
|
|
83
|
-
default:
|
|
84
|
-
pageSize: topLevelPageSize(
|
|
121
|
+
choices: [...choices],
|
|
122
|
+
default: defaultCommand(options.serviceStatus),
|
|
123
|
+
pageSize: topLevelPageSize(choices.length),
|
|
85
124
|
loop: false,
|
|
86
125
|
hideMessage: true,
|
|
126
|
+
disabledError: disabledError(options.serviceStatus),
|
|
87
127
|
});
|
|
88
128
|
deps.handleCancel(selected, 'exit');
|
|
89
129
|
if (selected === 'exit') {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export async function runLocalCockpit(target) {
|
|
4
|
+
const child = spawn(join(target, 'moo'), [], {
|
|
5
|
+
cwd: target,
|
|
6
|
+
stdio: 'inherit',
|
|
7
|
+
});
|
|
8
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
9
|
+
child.on('error', reject);
|
|
10
|
+
child.on('close', resolve);
|
|
11
|
+
});
|
|
12
|
+
if (exitCode !== 0) {
|
|
13
|
+
throw new Error(`Local WPMoo cockpit exited with code ${exitCode ?? 'unknown'}: ${join(target, 'moo')}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
package/dist/prompts/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { styleText } from 'node:util';
|
|
2
3
|
import inquirerSelect, { Separator as InquirerSeparator } from '@inquirer/select';
|
|
3
4
|
import inquirerSearch from '@inquirer/search';
|
|
4
5
|
import { confirm as inquirerConfirm, input as inquirerInput } from '@inquirer/prompts';
|
|
@@ -90,9 +91,10 @@ function asInquirerSelectConfig(options) {
|
|
|
90
91
|
pageSize: options.pageSize,
|
|
91
92
|
loop: options.loop,
|
|
92
93
|
hideMessage: options.hideMessage,
|
|
94
|
+
disabledError: options.disabledError,
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
|
-
function hiddenSelectTheme() {
|
|
97
|
+
function hiddenSelectTheme(disabledError) {
|
|
96
98
|
return {
|
|
97
99
|
prefix: '',
|
|
98
100
|
icon: {
|
|
@@ -101,19 +103,21 @@ function hiddenSelectTheme() {
|
|
|
101
103
|
style: {
|
|
102
104
|
message: () => '',
|
|
103
105
|
highlight: (text) => text,
|
|
106
|
+
disabled: (text) => styleText('dim', text.replace(/ \(disabled\)$/u, ''), { validateStream: false }),
|
|
104
107
|
keysHelpTip: () => '↑↓ navigate • ⏎ select • Ctrl+C exit',
|
|
105
108
|
},
|
|
109
|
+
i18n: disabledError ? { disabledError } : undefined,
|
|
106
110
|
};
|
|
107
111
|
}
|
|
108
112
|
function withHiddenSelectMessage(config) {
|
|
109
113
|
if (!config.hideMessage) {
|
|
110
114
|
return config;
|
|
111
115
|
}
|
|
112
|
-
const { hideMessage: _hideMessage, ...inquirerConfig } = config;
|
|
116
|
+
const { disabledError, hideMessage: _hideMessage, ...inquirerConfig } = config;
|
|
113
117
|
return {
|
|
114
118
|
...inquirerConfig,
|
|
115
119
|
message: '',
|
|
116
|
-
theme: hiddenSelectTheme(),
|
|
120
|
+
theme: hiddenSelectTheme(disabledError),
|
|
117
121
|
};
|
|
118
122
|
}
|
|
119
123
|
function asInquirerConfirmConfig(options) {
|
|
@@ -162,11 +166,12 @@ export function introPrompt(title) {
|
|
|
162
166
|
console.log(title);
|
|
163
167
|
console.log(rule);
|
|
164
168
|
}
|
|
165
|
-
export function notePrompt(message, title = 'Note') {
|
|
169
|
+
export function notePrompt(message, title = 'Note', options = {}) {
|
|
166
170
|
const lines = message.split('\n');
|
|
171
|
+
const prefix = options.indent === false ? '' : ' ';
|
|
167
172
|
console.log(`[${title}]`);
|
|
168
173
|
for (const line of lines) {
|
|
169
|
-
console.log(
|
|
174
|
+
console.log(`${prefix}${line}`);
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
export function outroPrompt(message) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
function run(command, args, options) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
execFile(command, args, { cwd: options.cwd }, (error, stdout) => {
|
|
5
|
+
if (error) {
|
|
6
|
+
reject(error);
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
resolve({ stdout });
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function renderServiceRuntimeStatusLine(status) {
|
|
14
|
+
if (status.kind === 'running')
|
|
15
|
+
return 'Status: ● Services running';
|
|
16
|
+
if (status.kind === 'docker-not-running')
|
|
17
|
+
return 'Status: ● Docker not running';
|
|
18
|
+
return 'Status: ● Services stopped';
|
|
19
|
+
}
|
|
20
|
+
export async function getServiceRuntimeStatus(target, environmentStatus, runner = run) {
|
|
21
|
+
try {
|
|
22
|
+
await runner('docker', ['info', '--format', '{{.ServerVersion}}'], { cwd: target });
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { kind: 'docker-not-running' };
|
|
26
|
+
}
|
|
27
|
+
if (environmentStatus.kind !== 'environment' ||
|
|
28
|
+
environmentStatus.composeFiles.length === 0 ||
|
|
29
|
+
environmentStatus.composeErrors.length > 0) {
|
|
30
|
+
return { kind: 'stopped' };
|
|
31
|
+
}
|
|
32
|
+
const args = [
|
|
33
|
+
'compose',
|
|
34
|
+
...environmentStatus.composeFiles.flatMap((file) => ['-f', file]),
|
|
35
|
+
'ps',
|
|
36
|
+
'--services',
|
|
37
|
+
'--filter',
|
|
38
|
+
'status=running',
|
|
39
|
+
];
|
|
40
|
+
let result;
|
|
41
|
+
try {
|
|
42
|
+
result = await runner('docker', args, { cwd: target });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { kind: 'stopped' };
|
|
46
|
+
}
|
|
47
|
+
return result.stdout.trim() ? { kind: 'running' } : { kind: 'stopped' };
|
|
48
|
+
}
|
package/dist/templates.js
CHANGED
|
@@ -279,6 +279,10 @@ const ANSI_DIM = '\u001B[2m';
|
|
|
279
279
|
const ANSI_INFO = '\u001B[38;2;139;166;190m';
|
|
280
280
|
const ANSI_TAGLINE = '\u001B[38;2;120;157;181m';
|
|
281
281
|
const ANSI_META = '\u001B[38;2;218;236;246m';
|
|
282
|
+
const ANSI_SUCCESS = '\u001B[32m';
|
|
283
|
+
const ANSI_ERROR = '\u001B[31m';
|
|
284
|
+
const ANSI_WARNING = '\u001B[33m';
|
|
285
|
+
const ANSI_DEFAULT_FOREGROUND = '\u001B[39m';
|
|
282
286
|
const ANSI_RESET = '\u001B[0m';
|
|
283
287
|
const BANNER_TAGLINE = 'Development, staging and production workflows for Odoo projects.';
|
|
284
288
|
function gradientColor(column, width) {
|
|
@@ -307,15 +311,50 @@ function renderDimInfo(value) {
|
|
|
307
311
|
function renderMetaInfo(value) {
|
|
308
312
|
return `${ANSI_META}${value}${ANSI_RESET}`;
|
|
309
313
|
}
|
|
314
|
+
function renderSuccessInfo(value) {
|
|
315
|
+
return `${ANSI_SUCCESS}${value}${ANSI_DEFAULT_FOREGROUND}`;
|
|
316
|
+
}
|
|
317
|
+
function renderErrorInfo(value) {
|
|
318
|
+
return `${ANSI_ERROR}${value}${ANSI_DEFAULT_FOREGROUND}`;
|
|
319
|
+
}
|
|
320
|
+
function renderWarningInfo(value) {
|
|
321
|
+
return `${ANSI_WARNING}${value}${ANSI_DEFAULT_FOREGROUND}`;
|
|
322
|
+
}
|
|
310
323
|
function renderTaglineInfo(value) {
|
|
311
324
|
return `${ANSI_TAGLINE}${value}${ANSI_RESET}`;
|
|
312
325
|
}
|
|
313
326
|
function renderBannerDetail(value) {
|
|
314
|
-
const match = /^(Environment|Last):(.*)$/u.exec(value);
|
|
327
|
+
const match = /^(Environment|Status|Last):(.*)$/u.exec(value);
|
|
315
328
|
if (!match) {
|
|
316
329
|
return renderDimInfo(value);
|
|
317
330
|
}
|
|
318
|
-
|
|
331
|
+
const label = match[1];
|
|
332
|
+
const detail = match[2] ?? '';
|
|
333
|
+
if (label === 'Status') {
|
|
334
|
+
const statusMatch = /^ (●) (.*)$/u.exec(detail);
|
|
335
|
+
if (statusMatch) {
|
|
336
|
+
const marker = statusMatch[1] ?? '';
|
|
337
|
+
const message = statusMatch[2] ?? '';
|
|
338
|
+
const renderMarker = message === 'Services running' ? renderSuccessInfo : renderWarningInfo;
|
|
339
|
+
return `${renderMetaInfo(`${label}:`)} ${renderMarker(marker)}${renderTaglineInfo(` ${message}`)}`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (label === 'Last') {
|
|
343
|
+
const completedMatch = /^(.*?)( ✓ completed)$/u.exec(detail);
|
|
344
|
+
if (completedMatch) {
|
|
345
|
+
return `${renderMetaInfo(`${label}:`)}${renderDimInfo(completedMatch[1] ?? '')}${renderSuccessInfo(completedMatch[2] ?? '')}`;
|
|
346
|
+
}
|
|
347
|
+
const errorMatch = /^(.*?)( ✗ Error)(: .*)?$/u.exec(detail);
|
|
348
|
+
if (errorMatch) {
|
|
349
|
+
return [
|
|
350
|
+
renderMetaInfo(`${label}:`),
|
|
351
|
+
renderDimInfo(errorMatch[1] ?? ''),
|
|
352
|
+
renderErrorInfo(errorMatch[2] ?? ''),
|
|
353
|
+
renderTaglineInfo(errorMatch[3] ?? ''),
|
|
354
|
+
].join('');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return `${renderMetaInfo(`${label}:`)}${renderDimInfo(detail)}`;
|
|
319
358
|
}
|
|
320
359
|
export function renderBanner(details = [], options = {}) {
|
|
321
360
|
const title = `${applyBannerGradient('WPMoo Toolkit')}${options.version ? ` ${renderDimInfo(options.version)}` : ''}`;
|