@wpmoo/odoo 0.8.45 → 0.8.47
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 +37 -0
- package/dist/args.js +1 -0
- package/dist/cli.js +29 -5
- package/dist/help.js +23 -3
- package/dist/status.js +193 -0
- package/dist/templates.js +42 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,10 +178,19 @@ npx @wpmoo/odoo remove-module \
|
|
|
178
178
|
|
|
179
179
|
Check that a generated environment is structurally ready:
|
|
180
180
|
|
|
181
|
+
```bash
|
|
182
|
+
npx @wpmoo/odoo status
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Run the deeper environment health check:
|
|
186
|
+
|
|
181
187
|
```bash
|
|
182
188
|
npx @wpmoo/odoo doctor
|
|
183
189
|
```
|
|
184
190
|
|
|
191
|
+
`status` is fast and offline, and reads local metadata/files only.
|
|
192
|
+
`doctor` is a deeper health check and may check Docker CLI access and GitHub workflows.
|
|
193
|
+
|
|
185
194
|
Refresh generated environment files without deleting module source code:
|
|
186
195
|
|
|
187
196
|
```bash
|
|
@@ -216,10 +225,38 @@ Daily actions require `.wpmoo/odoo.json` in the current directory and delegate t
|
|
|
216
225
|
fixed scripts under `./scripts`; they do not search parent directories or accept
|
|
217
226
|
arbitrary script names.
|
|
218
227
|
|
|
228
|
+
Task-oriented quick recipes:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# create environment
|
|
232
|
+
npx @wpmoo/odoo create --product odoo_sample_module --dev-repo-url <dev-repo-url> --source-repo-url <source-repo-url>
|
|
233
|
+
|
|
234
|
+
# add source repo
|
|
235
|
+
npx @wpmoo/odoo add-repo --repo-url https://github.com/example-org/odoo_sample_module_reports.git
|
|
236
|
+
|
|
237
|
+
# add module
|
|
238
|
+
npx @wpmoo/odoo add-module --repo odoo_sample_module --module odoo_sample_module_base
|
|
239
|
+
|
|
240
|
+
# run tests
|
|
241
|
+
npx @wpmoo/odoo test sale --db devel --mode update --tags /sale
|
|
242
|
+
|
|
243
|
+
# safe reset / recover
|
|
244
|
+
npx @wpmoo/odoo snapshot devel before-reset
|
|
245
|
+
npx @wpmoo/odoo reset
|
|
246
|
+
npx @wpmoo/odoo restore-snapshot before-reset devel
|
|
247
|
+
|
|
248
|
+
# daily checks
|
|
249
|
+
npx @wpmoo/odoo status
|
|
250
|
+
npx @wpmoo/odoo doctor
|
|
251
|
+
./moo logs odoo
|
|
252
|
+
```
|
|
253
|
+
|
|
219
254
|
Use `npx @wpmoo/odoo ...` for package/operator commands such as create,
|
|
220
255
|
add/remove repo, add/remove module, `doctor`, and `reset`. Generated
|
|
221
256
|
environments include `./moo` for local daily commands; it also falls back to
|
|
222
257
|
`npx @wpmoo/odoo@latest` for package commands such as `./moo doctor`.
|
|
258
|
+
For the operator-facing verification matrix, see
|
|
259
|
+
[`docs/generated-environment-verification.md`](docs/generated-environment-verification.md).
|
|
223
260
|
|
|
224
261
|
## Defaults
|
|
225
262
|
|
package/dist/args.js
CHANGED
|
@@ -6,6 +6,7 @@ import { dailyActionCommands } from './daily-actions.js';
|
|
|
6
6
|
import { validateAddonName, validateRepoPath } from './path-validation.js';
|
|
7
7
|
import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-url.js';
|
|
8
8
|
const commandNames = new Set([
|
|
9
|
+
'status',
|
|
9
10
|
'create',
|
|
10
11
|
'add-repo',
|
|
11
12
|
'remove-repo',
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { confirm, intro, isCancel, note, outro, select, text } from '@clack/prompts';
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
3
4
|
import { resolve } from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
6
|
import { commandFromArgs, defaultTargetForProduct, isHelpRequested, isVersionRequested, optionsFromArgs, parseArgs, stripInternalFlags, } from './args.js';
|
|
6
7
|
import { detectDevelopmentEnvironment } from './environment.js';
|
|
7
8
|
import { commandOdooVersion } from './environment-version.js';
|
|
@@ -22,6 +23,7 @@ import { scaffold } from './scaffold.js';
|
|
|
22
23
|
import { renderBanner } from './templates.js';
|
|
23
24
|
import { checkForUpdate, installLatestPackage, isUpdateCheckSkipped, restartCli } from './update-check.js';
|
|
24
25
|
import { packageName, packageVersion, renderVersion, renderVersionTag } from './version.js';
|
|
26
|
+
import { getEnvironmentStatus, renderEnvironmentStatusForTarget, renderEnvironmentStatusSummary, } from './status.js';
|
|
25
27
|
import { getGitHubAccounts, getGitHubRepositoryStatus, githubRepositoryUrl, realGitHub, createGitHubRepository, } from './github.js';
|
|
26
28
|
import { environmentGitHubOwner } from './environment-context.js';
|
|
27
29
|
import { handlePromptCancel, handleUnavailableMenuChoice, installPromptCancelKeyTracker, isMenuBackSignal, MenuBackSignal, menuIntroTitle, menuPromptMessage, } from './menu-navigation.js';
|
|
@@ -321,7 +323,7 @@ async function selectSourceRepo(target, cancelAction = 'exit') {
|
|
|
321
323
|
const repos = await listModuleRepos(target);
|
|
322
324
|
if (repos.length === 0) {
|
|
323
325
|
if (cancelAction === 'back') {
|
|
324
|
-
note(`No source repos found under ${target}/odoo/custom/src/private.`, 'Nothing to select');
|
|
326
|
+
note(`No source repos found under ${target}/odoo/custom/src/private.\nNext: choose "Add source repo" first.`, 'Nothing to select');
|
|
325
327
|
handleUnavailableMenuChoice(cancelAction);
|
|
326
328
|
}
|
|
327
329
|
throw new Error(`No source repos found under ${target}/odoo/custom/src/private`);
|
|
@@ -409,7 +411,7 @@ async function removeRepoOptionsFromPrompts(argv, showIntro = true, cancelAction
|
|
|
409
411
|
const repos = await listModuleRepos(target);
|
|
410
412
|
if (repos.length === 0) {
|
|
411
413
|
if (cancelAction === 'back') {
|
|
412
|
-
note(`No module submodules found under ${target}/odoo/custom/src/private.`, 'Nothing to remove');
|
|
414
|
+
note(`No module submodules found under ${target}/odoo/custom/src/private.\nNext: choose "Add source repo" first.`, 'Nothing to remove');
|
|
413
415
|
handleUnavailableMenuChoice(cancelAction);
|
|
414
416
|
}
|
|
415
417
|
throw new Error(`No module submodules found under ${target}/odoo/custom/src/private`);
|
|
@@ -448,7 +450,7 @@ async function removeModuleOptionsFromPrompts(showIntro = true, cancelAction = '
|
|
|
448
450
|
const modules = await listModulesInSourceRepo(target, repoPath);
|
|
449
451
|
if (modules.length === 0) {
|
|
450
452
|
if (cancelAction === 'back') {
|
|
451
|
-
note(`No Odoo modules found under ${target}/odoo/custom/src/private/${repoPath}.`, 'Nothing to remove');
|
|
453
|
+
note(`No Odoo modules found under ${target}/odoo/custom/src/private/${repoPath}.\nNext: choose "Add module to source repo" first.`, 'Nothing to remove');
|
|
452
454
|
handleUnavailableMenuChoice(cancelAction);
|
|
453
455
|
}
|
|
454
456
|
throw new Error(`No Odoo modules found under ${target}/odoo/custom/src/private/${repoPath}`);
|
|
@@ -571,6 +573,8 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
571
573
|
}
|
|
572
574
|
while (true) {
|
|
573
575
|
try {
|
|
576
|
+
const status = await getEnvironmentStatus(cwd);
|
|
577
|
+
note(renderEnvironmentStatusSummary(status), 'Environment status');
|
|
574
578
|
const action = await selectEnvironmentActionFromMenu();
|
|
575
579
|
if (action === 'exit') {
|
|
576
580
|
return;
|
|
@@ -686,6 +690,14 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
686
690
|
console.log(await runDoctor(cwd));
|
|
687
691
|
return;
|
|
688
692
|
}
|
|
693
|
+
if (route.command === 'status') {
|
|
694
|
+
if (route.argv.length > 0) {
|
|
695
|
+
throw new Error('Usage: wpmoo status');
|
|
696
|
+
}
|
|
697
|
+
console.log(renderBanner());
|
|
698
|
+
console.log(await renderEnvironmentStatusForTarget(cwd));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
689
701
|
if (isDailyActionCommand(route.command)) {
|
|
690
702
|
console.log(renderBanner());
|
|
691
703
|
await runDailyAction(route.command, route.argv, cwd);
|
|
@@ -712,7 +724,19 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
712
724
|
}
|
|
713
725
|
outro(`Created Odoo dev overlay in ${resolvedOptions.target}. Review staged changes, then commit.`);
|
|
714
726
|
}
|
|
715
|
-
|
|
727
|
+
export function isCliEntrypoint(metaUrl, argvPath = process.argv[1]) {
|
|
728
|
+
if (!argvPath)
|
|
729
|
+
return false;
|
|
730
|
+
try {
|
|
731
|
+
const entrypointUrl = pathToFileURL(realpathSync(fileURLToPath(metaUrl))).href;
|
|
732
|
+
const argvUrl = pathToFileURL(realpathSync(argvPath)).href;
|
|
733
|
+
return entrypointUrl === argvUrl;
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
return metaUrl === pathToFileURL(argvPath).href;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (isCliEntrypoint(import.meta.url)) {
|
|
716
740
|
runCli().catch((error) => {
|
|
717
741
|
const message = error instanceof Error ? error.message : String(error);
|
|
718
742
|
console.error(message);
|
package/dist/help.js
CHANGED
|
@@ -6,6 +6,7 @@ WPMoo Odoo lifecycle tooling.
|
|
|
6
6
|
Usage:
|
|
7
7
|
npx @wpmoo/odoo
|
|
8
8
|
npx @wpmoo/odoo create --product <slug> --dev-repo-url <url> --source-repo-url <url>
|
|
9
|
+
npx @wpmoo/odoo status
|
|
9
10
|
npx @wpmoo/odoo add-repo --repo-url <url>
|
|
10
11
|
npx @wpmoo/odoo remove-repo --repo <name>
|
|
11
12
|
npx @wpmoo/odoo add-module --repo <source-repo> --module <module-name>
|
|
@@ -66,9 +67,28 @@ Daily actions:
|
|
|
66
67
|
Generated environments also include ./moo for local compose commands such as ./moo start.
|
|
67
68
|
Use ./moo or npx @wpmoo/odoo with the same daily action arguments.
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
Status and doctor:
|
|
71
|
+
status: fast and offline. Reads local environment metadata and files only.
|
|
72
|
+
doctor: deeper health check. May check Docker CLI access and GitHub workflows.
|
|
73
|
+
|
|
74
|
+
Task recipes:
|
|
75
|
+
Create environment:
|
|
76
|
+
npx @wpmoo/odoo create --product <slug> --dev-repo-url <url> --source-repo-url <url>
|
|
77
|
+
Add source repo:
|
|
78
|
+
npx @wpmoo/odoo add-repo --repo-url <url>
|
|
79
|
+
Add module:
|
|
80
|
+
npx @wpmoo/odoo add-module --repo <source-repo> --module <module-name>
|
|
81
|
+
Run tests:
|
|
82
|
+
npx @wpmoo/odoo test <module[,module]> [--db <db>] [--mode init|update] [--tags <tags>]
|
|
83
|
+
Safe reset and recover:
|
|
84
|
+
npx @wpmoo/odoo snapshot [db] [snapshot-name]
|
|
85
|
+
npx @wpmoo/odoo reset
|
|
86
|
+
npx @wpmoo/odoo restore-snapshot <snapshot-name> [db]
|
|
87
|
+
Daily command checks:
|
|
88
|
+
npx @wpmoo/odoo status
|
|
89
|
+
npx @wpmoo/odoo doctor
|
|
90
|
+
npx @wpmoo/odoo logs [service]
|
|
91
|
+
npx @wpmoo/odoo restart
|
|
72
92
|
|
|
73
93
|
Example:
|
|
74
94
|
npx @wpmoo/odoo create \\
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { access, readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { defaultOdooVersion, markerPath } from './environment.js';
|
|
4
|
+
import { isValidPathSegment, validateRepoPath } from './path-validation.js';
|
|
5
|
+
async function pathExists(path) {
|
|
6
|
+
try {
|
|
7
|
+
await access(path);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function errorMessage(error) {
|
|
15
|
+
return error instanceof Error ? error.message : String(error);
|
|
16
|
+
}
|
|
17
|
+
function isRecord(value) {
|
|
18
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function parseMetadata(content) {
|
|
21
|
+
const parsed = JSON.parse(content);
|
|
22
|
+
if (!isRecord(parsed)) {
|
|
23
|
+
throw new Error('metadata is not an object');
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
function sourceRepoPathsFromMetadata(metadata) {
|
|
28
|
+
const sourceRepoPaths = [];
|
|
29
|
+
const invalidSourceRepoPaths = [];
|
|
30
|
+
if (!Array.isArray(metadata.sourceRepos))
|
|
31
|
+
return { sourceRepoPaths, invalidSourceRepoPaths };
|
|
32
|
+
for (const repo of metadata.sourceRepos) {
|
|
33
|
+
const path = repo && typeof repo.path === 'string' ? repo.path.trim() : '';
|
|
34
|
+
if (!path)
|
|
35
|
+
continue;
|
|
36
|
+
if (!isValidPathSegment(path)) {
|
|
37
|
+
invalidSourceRepoPaths.push(path);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
sourceRepoPaths.push(validateRepoPath(path));
|
|
41
|
+
}
|
|
42
|
+
return { sourceRepoPaths, invalidSourceRepoPaths };
|
|
43
|
+
}
|
|
44
|
+
async function missingCoreFiles(target, odooVersion) {
|
|
45
|
+
const missing = [];
|
|
46
|
+
const composeFile = `docker-compose_${odooVersion}.yml`;
|
|
47
|
+
const checks = [
|
|
48
|
+
{ label: 'moo', path: join(target, 'moo') },
|
|
49
|
+
{ label: 'README.md', path: join(target, 'README.md') },
|
|
50
|
+
{ label: 'AGENTS.md', path: join(target, 'AGENTS.md') },
|
|
51
|
+
{ label: composeFile, path: join(target, composeFile) },
|
|
52
|
+
{ label: 'scripts/', path: join(target, 'scripts'), mustBeDirectory: true },
|
|
53
|
+
];
|
|
54
|
+
for (const check of checks) {
|
|
55
|
+
if (!(await pathExists(check.path))) {
|
|
56
|
+
missing.push(check.label);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (check.mustBeDirectory) {
|
|
60
|
+
const fileStat = await stat(check.path);
|
|
61
|
+
if (!fileStat.isDirectory())
|
|
62
|
+
missing.push(check.label);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return missing;
|
|
66
|
+
}
|
|
67
|
+
async function countModuleCandidatesInRepoPath(path) {
|
|
68
|
+
if (!(await pathExists(path)))
|
|
69
|
+
return 0;
|
|
70
|
+
const rootStat = await stat(path);
|
|
71
|
+
if (!rootStat.isDirectory())
|
|
72
|
+
return 0;
|
|
73
|
+
let count = 0;
|
|
74
|
+
const stack = [path];
|
|
75
|
+
while (stack.length > 0) {
|
|
76
|
+
const current = stack.pop();
|
|
77
|
+
if (!current)
|
|
78
|
+
continue;
|
|
79
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
80
|
+
let hasManifest = false;
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (entry.isFile() && entry.name === '__manifest__.py') {
|
|
83
|
+
hasManifest = true;
|
|
84
|
+
}
|
|
85
|
+
else if (entry.isDirectory()) {
|
|
86
|
+
stack.push(join(current, entry.name));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (hasManifest)
|
|
90
|
+
count += 1;
|
|
91
|
+
}
|
|
92
|
+
return count;
|
|
93
|
+
}
|
|
94
|
+
function summaryText(status) {
|
|
95
|
+
if (status.kind === 'no_environment')
|
|
96
|
+
return 'No WPMoo environment detected.';
|
|
97
|
+
if (status.kind === 'invalid_metadata')
|
|
98
|
+
return 'Environment metadata is invalid.';
|
|
99
|
+
const prefix = status.missingCoreFiles.length > 0 || status.invalidSourceRepoPaths.length > 0
|
|
100
|
+
? 'Environment needs attention'
|
|
101
|
+
: 'Environment ready';
|
|
102
|
+
return `${prefix}: Odoo ${status.odooVersion}, source repos ${status.sourceRepoCount}, module candidates ${status.moduleCandidateCount}.`;
|
|
103
|
+
}
|
|
104
|
+
export async function getEnvironmentStatus(target) {
|
|
105
|
+
const metadataFullPath = join(target, markerPath);
|
|
106
|
+
if (!(await pathExists(metadataFullPath))) {
|
|
107
|
+
return {
|
|
108
|
+
kind: 'no_environment',
|
|
109
|
+
target,
|
|
110
|
+
metadataPath: markerPath,
|
|
111
|
+
recommendedNextAction: 'Run npx @wpmoo/odoo create ...',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
let metadata;
|
|
115
|
+
try {
|
|
116
|
+
const content = await readFile(metadataFullPath, 'utf8');
|
|
117
|
+
metadata = parseMetadata(content);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
return {
|
|
121
|
+
kind: 'invalid_metadata',
|
|
122
|
+
target,
|
|
123
|
+
metadataPath: markerPath,
|
|
124
|
+
metadataError: errorMessage(error),
|
|
125
|
+
recommendedNextAction: 'Fix .wpmoo/odoo.json or run npx @wpmoo/odoo reset from a valid environment.',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const odooVersion = typeof metadata.odooVersion === 'string' && metadata.odooVersion.trim()
|
|
129
|
+
? metadata.odooVersion.trim()
|
|
130
|
+
: defaultOdooVersion;
|
|
131
|
+
const { sourceRepoPaths, invalidSourceRepoPaths } = sourceRepoPathsFromMetadata(metadata);
|
|
132
|
+
const repoRoots = sourceRepoPaths.map((path) => join(target, 'odoo/custom/src/private', path));
|
|
133
|
+
let moduleCandidateCount = 0;
|
|
134
|
+
for (const repoRoot of repoRoots) {
|
|
135
|
+
moduleCandidateCount += await countModuleCandidatesInRepoPath(repoRoot);
|
|
136
|
+
}
|
|
137
|
+
const missingFiles = await missingCoreFiles(target, odooVersion);
|
|
138
|
+
let recommendedNextAction = 'Run npx @wpmoo/odoo doctor for deep checks or ./moo start.';
|
|
139
|
+
if (invalidSourceRepoPaths.length > 0) {
|
|
140
|
+
recommendedNextAction =
|
|
141
|
+
'Fix invalid source repo paths in .wpmoo/odoo.json, then run npx @wpmoo/odoo doctor.';
|
|
142
|
+
}
|
|
143
|
+
else if (missingFiles.length > 0) {
|
|
144
|
+
recommendedNextAction = 'Run npx @wpmoo/odoo reset, then npx @wpmoo/odoo doctor.';
|
|
145
|
+
}
|
|
146
|
+
else if (sourceRepoPaths.length === 0) {
|
|
147
|
+
recommendedNextAction = 'Run npx @wpmoo/odoo add-repo ...';
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
kind: 'environment',
|
|
151
|
+
target,
|
|
152
|
+
metadataPath: markerPath,
|
|
153
|
+
odooVersion,
|
|
154
|
+
sourceRepoCount: sourceRepoPaths.length,
|
|
155
|
+
sourceRepoPaths,
|
|
156
|
+
invalidSourceRepoPaths,
|
|
157
|
+
moduleCandidateCount,
|
|
158
|
+
missingCoreFiles: missingFiles,
|
|
159
|
+
recommendedNextAction,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export function renderEnvironmentStatusSummary(status) {
|
|
163
|
+
return summaryText(status);
|
|
164
|
+
}
|
|
165
|
+
export function renderEnvironmentStatus(status) {
|
|
166
|
+
const lines = [`Status: ${summaryText(status)}`];
|
|
167
|
+
if (status.kind === 'no_environment') {
|
|
168
|
+
lines.push(`Metadata: missing ${status.metadataPath}`);
|
|
169
|
+
lines.push(`Next: ${status.recommendedNextAction}`);
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
if (status.kind === 'invalid_metadata') {
|
|
173
|
+
lines.push(`Metadata: invalid ${status.metadataPath}`);
|
|
174
|
+
lines.push(`Error: ${status.metadataError}`);
|
|
175
|
+
lines.push(`Next: ${status.recommendedNextAction}`);
|
|
176
|
+
return lines.join('\n');
|
|
177
|
+
}
|
|
178
|
+
lines.push(`Metadata: ${status.metadataPath}`);
|
|
179
|
+
lines.push(`Odoo: ${status.odooVersion}`);
|
|
180
|
+
lines.push(`Source repos: ${status.sourceRepoCount}`);
|
|
181
|
+
lines.push(`Source repo paths: ${status.sourceRepoPaths.length > 0 ? status.sourceRepoPaths.join(', ') : '(none configured)'}`);
|
|
182
|
+
if (status.invalidSourceRepoPaths.length > 0) {
|
|
183
|
+
lines.push(`Invalid source repo paths: ${status.invalidSourceRepoPaths.join(', ')}`);
|
|
184
|
+
}
|
|
185
|
+
lines.push(`Module candidates: ${status.moduleCandidateCount}`);
|
|
186
|
+
lines.push(`Missing core files: ${status.missingCoreFiles.length > 0 ? status.missingCoreFiles.join(', ') : '(none)'}`);
|
|
187
|
+
lines.push(`Next: ${status.recommendedNextAction}`);
|
|
188
|
+
return lines.join('\n');
|
|
189
|
+
}
|
|
190
|
+
export async function renderEnvironmentStatusForTarget(target) {
|
|
191
|
+
const status = await getEnvironmentStatus(target);
|
|
192
|
+
return renderEnvironmentStatus(status);
|
|
193
|
+
}
|
package/dist/templates.js
CHANGED
|
@@ -122,26 +122,57 @@ Source repositories stay under \`odoo/custom/src/private\`. At container startup
|
|
|
122
122
|
\`entrypoint.sh\` scans those repositories for addons and exposes them through
|
|
123
123
|
\`/mnt/wpmoo-addons\`.
|
|
124
124
|
|
|
125
|
-
##
|
|
125
|
+
## Daily Command Hub (\`./moo\`)
|
|
126
|
+
|
|
127
|
+
\`./moo\` routes day-to-day service and module workflows to local scripts in
|
|
128
|
+
\`./scripts/\` (for example \`start\`, \`logs\`, \`update\`, \`test\`, \`snapshot\`).
|
|
129
|
+
\`./moo status\` and \`./moo doctor\` are package fallback commands that run via
|
|
130
|
+
\`npx --yes @wpmoo/odoo@latest ...\`.
|
|
131
|
+
|
|
132
|
+
### Start And Inspect Services
|
|
126
133
|
|
|
127
134
|
\`\`\`bash
|
|
128
135
|
cp .env.example .env
|
|
129
136
|
./moo start
|
|
130
|
-
./moo logs
|
|
137
|
+
./moo logs odoo
|
|
131
138
|
./moo shell
|
|
139
|
+
./moo psql postgres
|
|
132
140
|
./moo stop
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
### Run, Update, And Test Modules
|
|
144
|
+
|
|
145
|
+
\`\`\`bash
|
|
146
|
+
./moo install ${allAddons(options)[0] ?? options.product}
|
|
147
|
+
./moo update ${allAddons(options)[0] ?? options.product}
|
|
148
|
+
./moo test ${allAddons(options)[0] ?? options.product}
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
### Snapshot And Restore
|
|
152
|
+
|
|
153
|
+
\`\`\`bash
|
|
135
154
|
./moo snapshot devel before-update
|
|
136
155
|
./moo restore-snapshot before-update devel
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
### Lint
|
|
159
|
+
|
|
160
|
+
\`\`\`bash
|
|
137
161
|
./moo lint
|
|
138
|
-
./moo pot sale devel i18n/sale.pot
|
|
139
162
|
\`\`\`
|
|
140
163
|
|
|
141
|
-
|
|
164
|
+
### Export Translations
|
|
142
165
|
|
|
143
166
|
\`\`\`bash
|
|
144
|
-
./moo
|
|
167
|
+
./moo pot ${allAddons(options)[0] ?? options.product} devel i18n/${allAddons(options)[0] ?? options.product}.pot
|
|
168
|
+
\`\`\`
|
|
169
|
+
|
|
170
|
+
### Recover / Reset
|
|
171
|
+
|
|
172
|
+
\`\`\`bash
|
|
173
|
+
./moo doctor
|
|
174
|
+
./moo status
|
|
175
|
+
./moo resetdb devel ${allAddons(options)[0] ?? options.product}
|
|
145
176
|
\`\`\`
|
|
146
177
|
`;
|
|
147
178
|
}
|
|
@@ -576,6 +607,10 @@ Useful maintenance commands:
|
|
|
576
607
|
./moo pot <module[,module]> [db] [output]
|
|
577
608
|
\`\`\`
|
|
578
609
|
|
|
610
|
+
Daily script delegation vs package fallback:
|
|
611
|
+
- \`./moo start\`, \`logs\`, \`install\`, \`update\`, \`test\`, \`snapshot\`, and related runtime tasks delegate to local \`./scripts/*.sh\`.
|
|
612
|
+
- \`./moo status\` and \`./moo doctor\` are package fallback commands routed to \`npx --yes @wpmoo/odoo@latest ...\`.
|
|
613
|
+
|
|
579
614
|
Only report completion after the relevant update/test/lint command exits cleanly.
|
|
580
615
|
`;
|
|
581
616
|
}
|