@wpmoo/toolkit 0.9.17 → 0.9.19

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 CHANGED
@@ -280,8 +280,8 @@ async function showStartup(argv, skipUpdateCheck, details) {
280
280
  }
281
281
  console.log();
282
282
  }
283
- async function selectCockpitCommandFromMenu(serviceStatus) {
284
- const selection = await selectCockpitTopLevelMenu({ serviceStatus });
283
+ async function selectCockpitCommandFromMenu(serviceStatus, moduleCount) {
284
+ const selection = await selectCockpitTopLevelMenu({ serviceStatus, moduleCount });
285
285
  if (selection.kind === 'exit') {
286
286
  return 'exit';
287
287
  }
@@ -1453,7 +1453,7 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
1453
1453
  };
1454
1454
  while (true) {
1455
1455
  try {
1456
- const command = await selectCockpitCommandFromMenu(serviceStatus);
1456
+ const command = await selectCockpitCommandFromMenu(serviceStatus, status.kind === 'environment' ? status.moduleCandidateCount : undefined);
1457
1457
  if (command === 'exit') {
1458
1458
  return;
1459
1459
  }
@@ -20,6 +20,7 @@ const topLevelCategoryOrder = [
20
20
  ];
21
21
  const topLevelCommands = topLevelCategoryOrder.flatMap((category) => cockpitCommands.filter((command) => command.category === category && command.id !== 'exit'));
22
22
  const topLevelCommandLabelWidth = Math.max(...topLevelCommands.map((command) => command.label.length));
23
+ const moduleDependentCommandIds = new Set(['list-modules', 'install', 'update', 'test', 'lint', 'pot', 'remove-module']);
23
24
  function rgb(red, green, blue, value) {
24
25
  return `\u001B[38;2;${red};${green};${blue}m${value}\u001B[39m`;
25
26
  }
@@ -32,7 +33,7 @@ function categoryHeading(category) {
32
33
  function commandName(command) {
33
34
  return `${rgb(226, 184, 96, ` ${command.label.padEnd(topLevelCommandLabelWidth)}`)}${dim(` ${command.description}`)}`;
34
35
  }
35
- function disabledReason(command, serviceStatus) {
36
+ function serviceDisabledReason(command, serviceStatus) {
36
37
  if (command.category !== 'services' || !serviceStatus)
37
38
  return undefined;
38
39
  if (serviceStatus.kind === 'docker-not-running')
@@ -44,31 +45,32 @@ function disabledReason(command, serviceStatus) {
44
45
  }
45
46
  return undefined;
46
47
  }
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;
48
+ function moduleDisabledReason(command, moduleCount) {
49
+ return moduleCount === 0 && moduleDependentCommandIds.has(command.id) ? 'No modules found.' : undefined;
55
50
  }
56
- function disabledError(serviceStatus) {
57
- const reason = disabledMenuReason(serviceStatus);
58
- return reason ? `This option is disabled and cannot be selected.\nReason: ${reason}` : undefined;
51
+ function disabledReason(command, serviceStatus, moduleCount) {
52
+ return serviceDisabledReason(command, serviceStatus) ?? moduleDisabledReason(command, moduleCount);
53
+ }
54
+ function disabledError() {
55
+ return 'This option is disabled and cannot be selected.';
56
+ }
57
+ function commandDisabledValue(reason) {
58
+ if (!reason) {
59
+ return undefined;
60
+ }
61
+ return reason;
59
62
  }
60
- function categoryChoices(category, index, serviceStatus) {
63
+ function categoryChoices(category, index, serviceStatus, moduleCount) {
61
64
  const choices = [
62
65
  promptSeparator(categoryHeading(category)),
63
66
  ...topLevelCommands
64
67
  .filter((command) => command.category === category)
65
68
  .map((command) => {
66
- const reason = disabledReason(command, serviceStatus);
67
69
  return {
68
70
  value: command,
69
71
  name: commandName(command),
70
72
  short: command.label,
71
- disabled: reason ? true : undefined,
73
+ disabled: commandDisabledValue(disabledReason(command, serviceStatus, moduleCount)),
72
74
  };
73
75
  }),
74
76
  ];
@@ -101,8 +103,8 @@ function menuDeps(deps = {}) {
101
103
  function isCockpitCommand(value) {
102
104
  return typeof value === 'object' && value !== null && 'id' in value && 'slashAlias' in value;
103
105
  }
104
- function topLevelChoices(serviceStatus) {
105
- return topLevelCategoryOrder.flatMap((category, index) => categoryChoices(category, index, serviceStatus));
106
+ function topLevelChoices(serviceStatus, moduleCount) {
107
+ return topLevelCategoryOrder.flatMap((category, index) => categoryChoices(category, index, serviceStatus, moduleCount));
106
108
  }
107
109
  function defaultCommand(serviceStatus) {
108
110
  if (serviceStatus?.kind === 'running') {
@@ -115,7 +117,7 @@ function defaultCommand(serviceStatus) {
115
117
  }
116
118
  export async function selectCockpitTopLevelMenu(options = {}) {
117
119
  const deps = menuDeps(options);
118
- const choices = topLevelChoices(options.serviceStatus);
120
+ const choices = topLevelChoices(options.serviceStatus, options.moduleCount);
119
121
  const cancelAction = 'back';
120
122
  const selected = await deps.select({
121
123
  message: '',
@@ -124,7 +126,7 @@ export async function selectCockpitTopLevelMenu(options = {}) {
124
126
  pageSize: topLevelPageSize(choices.length),
125
127
  loop: false,
126
128
  hideMessage: true,
127
- disabledError: disabledError(options.serviceStatus),
129
+ disabledError: disabledError(),
128
130
  navigationWarning: options.navigationWarning,
129
131
  escapeBehavior: 'ignore',
130
132
  });
@@ -129,13 +129,30 @@ function renderedNavigationWarning(navigationWarning) {
129
129
  const warning = typeof navigationWarning === 'function' ? navigationWarning() : navigationWarning;
130
130
  return warning ? `\u001B[2m\u001B[38;2;226;184;96m${warning}\u001B[0m` : undefined;
131
131
  }
132
- function hiddenSelectTheme(disabledError, navigationHelp = 'exit', navigationWarning, hideMessage = true) {
132
+ function hiddenSelectTheme(disabledError, navigationHelp = 'exit', navigationWarning, hideMessage = true, disabledReasonLabels = []) {
133
+ let activeDisabledReason;
133
134
  const keysHelpTip = navigationHelp === 'back'
134
135
  ? '↑↓ navigate • ⏎ select • Esc to go back'
135
136
  : '↑↓ navigate • ⏎ select • Ctrl+C exit';
137
+ const disabledLabelPattern = / \(disabled\)$/u;
138
+ const disabledReasonSuffixes = [...disabledReasonLabels]
139
+ .sort((left, right) => right.length - left.length)
140
+ .map((reason) => ` ${reason}`);
141
+ const cursor = '\u001B[38;2;226;184;96m❯\u001B[39m';
142
+ const disabledCursor = '-';
136
143
  const style = {
137
144
  highlight: (text) => text,
138
- disabled: (text) => styleText('dim', text.replace(/ \(disabled\)$/u, ''), { validateStream: false }),
145
+ disabled: (text) => {
146
+ let renderedText = text.replace(disabledLabelPattern, '');
147
+ const reasonSuffix = disabledReasonSuffixes.find((suffix) => renderedText.endsWith(suffix));
148
+ if (reasonSuffix) {
149
+ renderedText = renderedText.slice(0, -reasonSuffix.length);
150
+ }
151
+ if (text.startsWith(`${cursor} `) || text.startsWith(`${disabledCursor} ${cursor} `)) {
152
+ activeDisabledReason = reasonSuffix?.trim();
153
+ }
154
+ return styleText('dim', renderedText, { validateStream: false });
155
+ },
139
156
  keysHelpTip: () => {
140
157
  const warning = renderedNavigationWarning(navigationWarning);
141
158
  return warning ? `${warning}\n${keysHelpTip}` : keysHelpTip;
@@ -147,12 +164,34 @@ function hiddenSelectTheme(disabledError, navigationHelp = 'exit', navigationWar
147
164
  return {
148
165
  prefix: '',
149
166
  icon: {
150
- cursor: '\u001B[38;2;226;184;96m❯\u001B[39m',
167
+ cursor,
151
168
  },
152
169
  style,
153
- i18n: disabledError ? { disabledError } : undefined,
170
+ i18n: disabledError ? disabledErrorI18n(disabledError, () => activeDisabledReason) : undefined,
154
171
  };
155
172
  }
173
+ function disabledErrorI18n(disabledError, activeReason) {
174
+ const i18n = { disabledError };
175
+ Object.defineProperty(i18n, 'disabledError', {
176
+ get: () => {
177
+ const reason = activeReason();
178
+ return reason ? `${disabledError}\nReason: ${reason}` : disabledError;
179
+ },
180
+ });
181
+ return i18n;
182
+ }
183
+ function collectDisabledReasonLabels(choices) {
184
+ const reasons = new Set();
185
+ for (const choice of choices) {
186
+ if (typeof choice === 'object' &&
187
+ choice !== null &&
188
+ 'disabled' in choice &&
189
+ typeof choice.disabled === 'string') {
190
+ reasons.add(choice.disabled);
191
+ }
192
+ }
193
+ return [...reasons];
194
+ }
156
195
  function withHiddenSelectMessage(config) {
157
196
  if (!config.hideMessage &&
158
197
  !config.disabledError &&
@@ -165,7 +204,7 @@ function withHiddenSelectMessage(config) {
165
204
  return {
166
205
  ...inquirerConfig,
167
206
  message: config.hideMessage ? '' : inquirerConfig.message,
168
- theme: hiddenSelectTheme(disabledError, navigationHelp, navigationWarning, Boolean(config.hideMessage)),
207
+ theme: hiddenSelectTheme(disabledError, navigationHelp, navigationWarning, Boolean(config.hideMessage), collectDisabledReasonLabels(inquirerConfig.choices)),
169
208
  };
170
209
  }
171
210
  function asInquirerConfirmConfig(options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/toolkit",
3
- "version": "0.9.17",
3
+ "version": "0.9.19",
4
4
  "description": "WPMoo Toolkit for development, staging, and production lifecycle workflows.",
5
5
  "type": "module",
6
6
  "repository": {