@wpmoo/toolkit 0.9.21 → 0.9.23
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/README.md +11 -0
- package/dist/cli.js +6 -17
- package/dist/cockpit/command-registry.js +23 -24
- package/dist/cockpit/menu.js +16 -4
- package/dist/prompts/index.js +4 -1
- package/dist/status.js +16 -0
- package/docs/generated-environment-verification.md +26 -0
- package/docs/handoff.md +17 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,6 +190,17 @@ The unscoped `wpmoo` short alias is optional. If npm returns `E404` or rejects
|
|
|
190
190
|
that alias during the publish workflow, the workflow reports a non-blocking
|
|
191
191
|
warning while keeping the scoped package release valid.
|
|
192
192
|
|
|
193
|
+
### Release candidate rules
|
|
194
|
+
|
|
195
|
+
- **Required release artifacts**: `@wpmoo/toolkit`, `@wpmoo/odoo`, and
|
|
196
|
+
`@wpmoo/odoo-dev` must be publishable at package version `$VERSION` for tag
|
|
197
|
+
`v$VERSION` to count as valid.
|
|
198
|
+
- **Optional artifact**: `wpmoo` at package version `$VERSION` is best-effort only. If npm
|
|
199
|
+
rejects it, the release still succeeds as long as the three required scoped
|
|
200
|
+
packages are valid.
|
|
201
|
+
- **Smoke expectation**: run `npm run smoke:published -- "$VERSION"` after the
|
|
202
|
+
release tag workflow completes.
|
|
203
|
+
|
|
193
204
|
## Documentation
|
|
194
205
|
|
|
195
206
|
- [External Resources](docs/external-resources.md)
|
package/dist/cli.js
CHANGED
|
@@ -38,7 +38,7 @@ import { confirmPrompt, introPrompt, isPromptCancel, notePrompt, outroPrompt, se
|
|
|
38
38
|
import { renderBanner } from './templates.js';
|
|
39
39
|
import { checkForUpdate, isUpdateCheckSkipped, restartCli } from './update-check.js';
|
|
40
40
|
import { packageName, packageVersion, renderVersion, renderVersionTag } from './version.js';
|
|
41
|
-
import { environmentStatusJson, getEnvironmentStatus,
|
|
41
|
+
import { environmentStatusJson, getEnvironmentStatus, environmentBannerSummaryLine, renderEnvironmentStatusForTarget, } from './status.js';
|
|
42
42
|
import { getGitHubAccounts, getGitHubRepositoryStatus, githubRepositoryUrl, realGitHub, createGitHubRepository, } from './github.js';
|
|
43
43
|
import { environmentGitHubOwner } from './environment-context.js';
|
|
44
44
|
import { handlePromptCancel, handleUnavailableMenuChoice, installPromptCancelKeyTracker, isMenuBackSignal, MenuBackSignal, menuIntroTitle, menuPromptMessage, } from './menu-navigation.js';
|
|
@@ -184,27 +184,16 @@ function validateRepoName(value) {
|
|
|
184
184
|
function startupVersionLine(latestVersion) {
|
|
185
185
|
return `v${packageVersion()}${latestVersion ? ` -> v${latestVersion} available` : ''}`;
|
|
186
186
|
}
|
|
187
|
-
function pluralize(count, singular, plural) {
|
|
188
|
-
return `${count} ${count === 1 ? singular : plural}`;
|
|
189
|
-
}
|
|
190
|
-
function renderStartupEnvironmentLine(status) {
|
|
191
|
-
if (status.kind !== 'environment') {
|
|
192
|
-
return `Environment: ${renderEnvironmentStatusSummary(status)}`;
|
|
193
|
-
}
|
|
194
|
-
const issueCount = status.composeErrors.length + status.invalidSourceRepoPaths.length + status.missingCoreFiles.length;
|
|
195
|
-
const issueSuffix = issueCount > 0 ? ` · ${pluralize(issueCount, 'issue', 'issues')}` : '';
|
|
196
|
-
return [
|
|
197
|
-
`Environment: Odoo ${status.odooVersion}`,
|
|
198
|
-
pluralize(status.sourceRepoCount, 'repo', 'repos'),
|
|
199
|
-
pluralize(status.moduleCandidateCount, 'module', 'modules'),
|
|
200
|
-
].join(' · ') + issueSuffix;
|
|
201
|
-
}
|
|
202
187
|
function renderStartupBanner(details, latestVersion) {
|
|
203
188
|
const versionLine = startupVersionLine(latestVersion);
|
|
204
189
|
return renderBanner(details?.(versionLine), details ? { version: versionLine } : undefined);
|
|
205
190
|
}
|
|
206
191
|
function renderCockpitStatusLines(status, serviceStatus, lastStatus) {
|
|
207
|
-
return [
|
|
192
|
+
return [
|
|
193
|
+
environmentBannerSummaryLine(status),
|
|
194
|
+
renderServiceRuntimeStatusLine(serviceStatus),
|
|
195
|
+
lastStatus,
|
|
196
|
+
];
|
|
208
197
|
}
|
|
209
198
|
function renderLastCommandStatus(command) {
|
|
210
199
|
return `Last: ${command.label} ✓ completed`;
|
|
@@ -29,41 +29,40 @@ function internalCommand(id, category, label, description, aliases = []) {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
export const cockpitCommands = [
|
|
32
|
-
dailyCommand('start', 'services', 'Start services', 'Start
|
|
33
|
-
dailyCommand('stop', 'services', 'Stop services', 'Stop
|
|
34
|
-
dailyCommand('restart', 'services', 'Restart services', 'Restart
|
|
35
|
-
dailyCommand('logs', 'services', 'View logs', '
|
|
36
|
-
dailyCommand('shell', 'services', 'Open shell', 'Open a
|
|
32
|
+
dailyCommand('start', 'services', 'Start services', 'Start Odoo services.', ['up', 'compose up']),
|
|
33
|
+
dailyCommand('stop', 'services', 'Stop services', 'Stop Odoo services.', ['down', 'compose down']),
|
|
34
|
+
dailyCommand('restart', 'services', 'Restart services', 'Restart Odoo services.', ['reload']),
|
|
35
|
+
dailyCommand('logs', 'services', 'View logs', 'Tail service logs.', ['log', 'tail']),
|
|
36
|
+
dailyCommand('shell', 'services', 'Open shell', 'Open a service shell.', ['bash', 'terminal']),
|
|
37
37
|
internalCommand('list-modules', 'modules', 'List modules', 'Browse detected Odoo modules by source category.', [
|
|
38
38
|
'modules list',
|
|
39
39
|
'browse modules',
|
|
40
|
+
'/module',
|
|
41
|
+
'module',
|
|
40
42
|
]),
|
|
41
|
-
dailyCommand('install', 'modules', 'Install module', 'Install
|
|
42
|
-
dailyCommand('update', 'modules', 'Update module', 'Update
|
|
43
|
-
dailyCommand('test', 'modules', 'Run tests', 'Run
|
|
44
|
-
dailyCommand('lint', 'modules', 'Run environment lint', 'Run
|
|
45
|
-
dailyCommand('pot', 'modules', 'Generate POT', 'Generate translation
|
|
46
|
-
dailyCommand('psql', 'database', 'Open psql', 'Open
|
|
47
|
-
dailyCommand('snapshot', 'database', 'Create snapshot', 'Create a database snapshot.', ['backup', 'dump']),
|
|
48
|
-
dailyCommand('restore-snapshot', 'database', 'Restore snapshot', 'Restore a
|
|
49
|
-
dailyCommand('resetdb', 'database', 'Reset database', 'Reset
|
|
43
|
+
dailyCommand('install', 'modules', 'Install module', 'Install modules in the database.', ['install module', 'module']),
|
|
44
|
+
dailyCommand('update', 'modules', 'Update module', 'Update modules in the database.', ['upgrade', 'module']),
|
|
45
|
+
dailyCommand('test', 'modules', 'Run tests', 'Run tests for selected modules.', ['tests', 'pytest', 'module']),
|
|
46
|
+
dailyCommand('lint', 'modules', 'Run environment lint', 'Run environment lint checks.', ['check', 'quality']),
|
|
47
|
+
dailyCommand('pot', 'modules', 'Generate POT', 'Generate module translation templates.', ['translation', 'i18n']),
|
|
48
|
+
dailyCommand('psql', 'database', 'Open psql', 'Open PostgreSQL prompt.', ['postgres', 'sql', '/db']),
|
|
49
|
+
dailyCommand('snapshot', 'database', 'Create snapshot', 'Create a database snapshot.', ['backup', 'dump', '/snapshot']),
|
|
50
|
+
dailyCommand('restore-snapshot', 'database', 'Restore snapshot', 'Restore a named snapshot.', ['restore', 'snapshot restore']),
|
|
51
|
+
dailyCommand('resetdb', 'database', 'Reset database', 'Reset the environment database.', ['reset db', 'database reset']),
|
|
50
52
|
internalCommand('status', 'diagnostics', 'Environment status', 'Show a summary of the current environment state.', [
|
|
51
53
|
'state',
|
|
52
54
|
'summary',
|
|
53
55
|
]),
|
|
54
|
-
internalCommand('doctor', 'diagnostics', 'Run doctor', 'Run environment diagnostics
|
|
55
|
-
|
|
56
|
-
'health',
|
|
57
|
-
]),
|
|
58
|
-
internalCommand('add-repo', 'repositories', 'Add source repo', 'Add a source repository as an environment submodule.', [
|
|
56
|
+
internalCommand('doctor', 'diagnostics', 'Run doctor', 'Run environment diagnostics.', ['diagnose', 'health']),
|
|
57
|
+
internalCommand('add-repo', 'repositories', 'Add source repo', 'Add a source repository.', [
|
|
59
58
|
'repository add',
|
|
60
59
|
'source add',
|
|
61
60
|
]),
|
|
62
|
-
internalCommand('remove-repo', 'repositories', 'Remove source repo', 'Remove a source repository
|
|
63
|
-
internalCommand('add-module', 'modules', 'Add module', 'Add a module
|
|
64
|
-
internalCommand('remove-module', 'modules', 'Remove module', 'Remove a module
|
|
65
|
-
internalCommand('safe-reset', 'maintenance', 'Safe reset environment', 'Refresh generated
|
|
66
|
-
internalCommand('exit', 'maintenance', 'Exit', '
|
|
61
|
+
internalCommand('remove-repo', 'repositories', 'Remove source repo', 'Remove a source repository.', ['repository remove', 'source remove']),
|
|
62
|
+
internalCommand('add-module', 'modules', 'Add module', 'Add a module to a source repository.', ['module add']),
|
|
63
|
+
internalCommand('remove-module', 'modules', 'Remove module', 'Remove a module from a source repository.', ['module remove']),
|
|
64
|
+
internalCommand('safe-reset', 'maintenance', 'Safe reset environment', 'Refresh generated files only.', ['reset', 'refresh', '/safe']),
|
|
65
|
+
internalCommand('exit', 'maintenance', 'Exit', 'Close the command palette.', ['quit', 'back']),
|
|
67
66
|
];
|
|
68
67
|
const defaultCommandIds = new Set(['start', 'logs', 'test', 'status', 'doctor', 'exit']);
|
|
69
68
|
export function normalizeCockpitSearchTerm(term) {
|
package/dist/cockpit/menu.js
CHANGED
|
@@ -33,6 +33,21 @@ function categoryHeading(category) {
|
|
|
33
33
|
function commandName(command) {
|
|
34
34
|
return `${rgb(226, 184, 96, ` ${command.label.padEnd(topLevelCommandLabelWidth)}`)}${dim(` ${command.description}`)}`;
|
|
35
35
|
}
|
|
36
|
+
const disabledReasonNextStep = {
|
|
37
|
+
'No modules found.': 'Next: choose "Add module" first.',
|
|
38
|
+
'Services stopped.': 'Next: choose "Start services" first.',
|
|
39
|
+
'Already running.': 'Next: choose "Stop services" or "Restart services".',
|
|
40
|
+
'Docker not running.': 'Next: start Docker, then choose "Start services".',
|
|
41
|
+
'No source repos found.': 'Next: choose "Add source repo" first.',
|
|
42
|
+
};
|
|
43
|
+
function disabledError(reason) {
|
|
44
|
+
const base = 'This option is disabled and cannot be selected.';
|
|
45
|
+
if (!reason) {
|
|
46
|
+
return base;
|
|
47
|
+
}
|
|
48
|
+
const nextStep = disabledReasonNextStep[reason];
|
|
49
|
+
return nextStep ? `${base}\nReason: ${reason}\n${nextStep}` : `${base}\nReason: ${reason}`;
|
|
50
|
+
}
|
|
36
51
|
function serviceDisabledReason(command, serviceStatus) {
|
|
37
52
|
if (command.category !== 'services' || !serviceStatus)
|
|
38
53
|
return undefined;
|
|
@@ -56,9 +71,6 @@ function disabledReason(command, serviceStatus, moduleCount, sourceRepoCount) {
|
|
|
56
71
|
moduleDisabledReason(command, moduleCount) ??
|
|
57
72
|
sourceRepoDisabledReason(command, sourceRepoCount));
|
|
58
73
|
}
|
|
59
|
-
function disabledError() {
|
|
60
|
-
return 'This option is disabled and cannot be selected.';
|
|
61
|
-
}
|
|
62
74
|
function commandDisabledValue(reason) {
|
|
63
75
|
if (!reason) {
|
|
64
76
|
return undefined;
|
|
@@ -131,7 +143,7 @@ export async function selectCockpitTopLevelMenu(options = {}) {
|
|
|
131
143
|
pageSize: topLevelPageSize(choices.length),
|
|
132
144
|
loop: false,
|
|
133
145
|
hideMessage: true,
|
|
134
|
-
disabledError
|
|
146
|
+
disabledError,
|
|
135
147
|
navigationWarning: options.navigationWarning,
|
|
136
148
|
escapeBehavior: 'ignore',
|
|
137
149
|
});
|
package/dist/prompts/index.js
CHANGED
|
@@ -171,10 +171,13 @@ function hiddenSelectTheme(disabledError, navigationHelp = 'exit', navigationWar
|
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
173
|
function disabledErrorI18n(disabledError, activeReason) {
|
|
174
|
-
const i18n = { disabledError };
|
|
174
|
+
const i18n = typeof disabledError === 'string' ? { disabledError } : { disabledError: disabledError(undefined) };
|
|
175
175
|
Object.defineProperty(i18n, 'disabledError', {
|
|
176
176
|
get: () => {
|
|
177
177
|
const reason = activeReason();
|
|
178
|
+
if (typeof disabledError === 'function') {
|
|
179
|
+
return disabledError(reason);
|
|
180
|
+
}
|
|
178
181
|
return reason ? `${disabledError}\nReason: ${reason}` : disabledError;
|
|
179
182
|
},
|
|
180
183
|
});
|
package/dist/status.js
CHANGED
|
@@ -5,6 +5,10 @@ import { defaultOdooVersion, markerPath } from './environment.js';
|
|
|
5
5
|
import { emptyModuleQualitySummary, mergeModuleQualitySummaries, scanModuleQuality, } from './module-quality.js';
|
|
6
6
|
import { isValidPathSegment, validateRepoPath } from './path-validation.js';
|
|
7
7
|
const validSourceTypes = ['private', 'oca', 'external'];
|
|
8
|
+
const summarySeparator = ' \u00B7 ';
|
|
9
|
+
function pluralize(count, singular, plural) {
|
|
10
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
11
|
+
}
|
|
8
12
|
function normalizeSourceType(sourceType) {
|
|
9
13
|
if (typeof sourceType === 'string' && validSourceTypes.includes(sourceType)) {
|
|
10
14
|
return sourceType;
|
|
@@ -85,6 +89,18 @@ async function missingCoreFiles(target, odooVersion) {
|
|
|
85
89
|
missing.push(...composeLayout.missingFiles);
|
|
86
90
|
return { missing, composeFiles: composeLayout.files, composeErrors: composeLayout.errors };
|
|
87
91
|
}
|
|
92
|
+
export function environmentBannerSummaryLine(status) {
|
|
93
|
+
if (status.kind !== 'environment') {
|
|
94
|
+
return `Environment: ${summaryText(status)}`;
|
|
95
|
+
}
|
|
96
|
+
const issueCount = status.composeErrors.length + status.invalidSourceRepoPaths.length + status.missingCoreFiles.length;
|
|
97
|
+
const issueSuffix = issueCount > 0 ? `${summarySeparator}${pluralize(issueCount, 'issue', 'issues')}` : '';
|
|
98
|
+
return [
|
|
99
|
+
`Environment: Odoo ${status.odooVersion}`,
|
|
100
|
+
pluralize(status.sourceRepoCount, 'repo', 'repos'),
|
|
101
|
+
pluralize(status.moduleCandidateCount, 'module', 'modules'),
|
|
102
|
+
].join(summarySeparator) + issueSuffix;
|
|
103
|
+
}
|
|
88
104
|
function summaryText(status) {
|
|
89
105
|
if (status.kind === 'no_environment')
|
|
90
106
|
return 'No WPMoo environment detected.';
|
|
@@ -160,3 +160,29 @@ npm test
|
|
|
160
160
|
npm run test:coverage
|
|
161
161
|
npm run build
|
|
162
162
|
```
|
|
163
|
+
|
|
164
|
+
## Coverage watchlist (risk monitoring)
|
|
165
|
+
|
|
166
|
+
The following list is a risk watchlist for Train 2 verification, not a hard gate.
|
|
167
|
+
It uses the full `npm run test:coverage` suite to highlight where changes in
|
|
168
|
+
high-impact runtime files should be reviewed with extra care:
|
|
169
|
+
|
|
170
|
+
- `src/cli.ts`: **watch**: 83.74% line coverage (1458/1741), function coverage
|
|
171
|
+
92.47% (86/93), branch coverage 80.61% (420/521). This file remains the
|
|
172
|
+
highest-risk surface because it owns direct commands, cockpit dispatch, JSON
|
|
173
|
+
routes, and release-facing error behavior.
|
|
174
|
+
- `src/doctor.ts`: **observe**: 94.48% line coverage (702/743), function
|
|
175
|
+
coverage 95.56% (43/45), branch coverage 86.42% (229/265).
|
|
176
|
+
- `src/module-actions.ts`: **observe**: 96.83% line coverage (519/536),
|
|
177
|
+
function coverage 97.22% (35/36), branch coverage 88.97% (129/145).
|
|
178
|
+
- `src/templates.ts`: **observe**: 99.24% line coverage (262/264), function
|
|
179
|
+
coverage 100.00% (38/38), branch coverage 90.08% (109/121).
|
|
180
|
+
- `src/prompts/index.ts`: **observe**: 95.15% line coverage (294/309),
|
|
181
|
+
function coverage 100.00% (36/36), branch coverage 92.31% (96/104).
|
|
182
|
+
|
|
183
|
+
Train 2 full-suite coverage baseline:
|
|
184
|
+
|
|
185
|
+
- Statements: 92.65% (7304/7883)
|
|
186
|
+
- Branches: 88.24% (2432/2756)
|
|
187
|
+
- Functions: 96.27% (595/618)
|
|
188
|
+
- Lines: 92.65% (7304/7883)
|
package/docs/handoff.md
CHANGED
|
@@ -42,7 +42,23 @@ npm view "@wpmoo/odoo-dev@$VERSION" version
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
`npm view "wpmoo@$VERSION" version` is optional and may report that the short
|
|
45
|
-
alias is absent.
|
|
45
|
+
alias is absent. A release is valid when all required scoped packages verify:
|
|
46
|
+
|
|
47
|
+
- `npm view "@wpmoo/toolkit@$VERSION" version`
|
|
48
|
+
- `npm view "@wpmoo/odoo@$VERSION" version`
|
|
49
|
+
- `npm view "@wpmoo/odoo-dev@$VERSION" version`
|
|
50
|
+
|
|
51
|
+
Optional short alias rule:
|
|
52
|
+
|
|
53
|
+
- `wpmoo` may be reported as missing or fail publish without invalidating the
|
|
54
|
+
release candidate. Scoped packages are the supported release artifacts and
|
|
55
|
+
are sufficient to mark the release valid.
|
|
56
|
+
|
|
57
|
+
Suggested smoke check:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run smoke:published -- "$VERSION"
|
|
61
|
+
```
|
|
46
62
|
|
|
47
63
|
Current command standard:
|
|
48
64
|
|