@wpmoo/odoo 0.8.60 → 0.8.61
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 +13 -1
- package/dist/cli.js +24 -7
- package/dist/doctor.js +120 -14
- package/dist/help.js +5 -2
- package/docs/generated-environment-verification.md +14 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,10 +139,12 @@ npx @wpmoo/odoo --version
|
|
|
139
139
|
|
|
140
140
|
npx @wpmoo/odoo status
|
|
141
141
|
npx @wpmoo/odoo doctor
|
|
142
|
+
npx @wpmoo/odoo doctor --fix
|
|
142
143
|
npx @wpmoo/odoo add-repo --repo-url https://github.com/example-org/odoo_sample_module_reports.git
|
|
143
144
|
npx @wpmoo/odoo remove-repo --repo odoo_sample_module_reports
|
|
144
145
|
npx @wpmoo/odoo add-module --repo odoo_sample_module --module odoo_sample_module_base
|
|
145
146
|
npx @wpmoo/odoo remove-module --repo odoo_sample_module --module odoo_sample_module_base
|
|
147
|
+
npx @wpmoo/odoo reset --dry-run
|
|
146
148
|
npx @wpmoo/odoo reset
|
|
147
149
|
|
|
148
150
|
npx @wpmoo/odoo start
|
|
@@ -346,9 +348,15 @@ Docker Compose access, GitHub CLI authentication when available, and PostgreSQL
|
|
|
346
348
|
18 compatibility in compose mount targets (for mounts to
|
|
347
349
|
`/var/lib/postgresql/data` or `/var/lib/postgresql/18/docker`).
|
|
348
350
|
|
|
351
|
+
Use `doctor --fix` for safe file-level repairs. It can normalize PostgreSQL 18
|
|
352
|
+
mount targets and regenerate `odoo/custom/manifests/sources.yaml` from
|
|
353
|
+
metadata plus `.gitmodules`, then it runs doctor again and reports any remaining
|
|
354
|
+
manual issues.
|
|
355
|
+
|
|
349
356
|
Safe reset refreshes generated environment files without deleting product source code:
|
|
350
357
|
|
|
351
358
|
```bash
|
|
359
|
+
npx @wpmoo/odoo reset --dry-run
|
|
352
360
|
npx @wpmoo/odoo reset
|
|
353
361
|
```
|
|
354
362
|
|
|
@@ -357,6 +365,9 @@ Safe reset updates generated files such as `.wpmoo/odoo.json`, `moo`,
|
|
|
357
365
|
Agent Skills. Compose overlays like `compose.yaml` and `compose/dev.yaml` are
|
|
358
366
|
also refreshed from the current compose template source.
|
|
359
367
|
|
|
368
|
+
Use `reset --dry-run` first when you want a deterministic preview of refreshed
|
|
369
|
+
files and cleanup warnings without writing to the environment.
|
|
370
|
+
|
|
360
371
|
It does not touch source repo folders under
|
|
361
372
|
`odoo/custom/src/private`, module source code, Git history, remotes, or
|
|
362
373
|
branches. It also preserves local runtime artifacts and custom source layout
|
|
@@ -373,8 +384,9 @@ Recommended recovery pattern:
|
|
|
373
384
|
|
|
374
385
|
```bash
|
|
375
386
|
./moo snapshot devel before-reset
|
|
387
|
+
npx @wpmoo/odoo reset --dry-run
|
|
376
388
|
npx @wpmoo/odoo reset
|
|
377
|
-
npx @wpmoo/odoo doctor
|
|
389
|
+
npx @wpmoo/odoo doctor --fix
|
|
378
390
|
./moo restore-snapshot before-reset devel
|
|
379
391
|
```
|
|
380
392
|
|
package/dist/cli.js
CHANGED
|
@@ -464,11 +464,25 @@ function removeRepoOptionsFromArgs(argv) {
|
|
|
464
464
|
stage: booleanOption(values, 'stage', true),
|
|
465
465
|
};
|
|
466
466
|
}
|
|
467
|
-
function
|
|
467
|
+
function resetCommandOptionsFromArgs(argv) {
|
|
468
468
|
const { values } = parseArgs(argv);
|
|
469
469
|
return {
|
|
470
470
|
target: resolve(stringOption(values, 'target') ?? process.cwd()),
|
|
471
471
|
stage: booleanOption(values, 'stage', true),
|
|
472
|
+
dryRun: booleanOption(values, 'dryRun', false),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function doctorOptionsFromArgs(argv) {
|
|
476
|
+
if (argv.length === 0) {
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
const { values } = parseArgs(argv);
|
|
480
|
+
const keys = Object.keys(values);
|
|
481
|
+
if (keys.length !== 1 || !Object.hasOwn(values, 'fix')) {
|
|
482
|
+
throw new Error('Usage: wpmoo doctor');
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
fix: booleanOption(values, 'fix', false),
|
|
472
486
|
};
|
|
473
487
|
}
|
|
474
488
|
function sourceUsage() {
|
|
@@ -859,17 +873,20 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
859
873
|
}
|
|
860
874
|
if (route.command === 'reset') {
|
|
861
875
|
console.log(renderBanner());
|
|
862
|
-
const options =
|
|
863
|
-
|
|
876
|
+
const options = resetCommandOptionsFromArgs(route.argv);
|
|
877
|
+
if (options.dryRun) {
|
|
878
|
+
console.log(renderSafeResetPreview(options.target, options.stage));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const resetOptions = { target: options.target, stage: options.stage };
|
|
882
|
+
await safeResetEnvironment(resetOptions);
|
|
864
883
|
outro(`Safe reset refreshed generated environment files in ${options.target}.`);
|
|
865
884
|
return;
|
|
866
885
|
}
|
|
867
886
|
if (route.command === 'doctor') {
|
|
868
|
-
|
|
869
|
-
throw new Error('Usage: wpmoo doctor');
|
|
870
|
-
}
|
|
887
|
+
const options = doctorOptionsFromArgs(route.argv);
|
|
871
888
|
console.log(renderBanner());
|
|
872
|
-
console.log(await runDoctor(cwd));
|
|
889
|
+
console.log(options.fix === undefined ? await runDoctor(cwd) : await runDoctor(cwd, options));
|
|
873
890
|
return;
|
|
874
891
|
}
|
|
875
892
|
if (route.command === 'status') {
|
package/dist/doctor.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { access, readFile } from 'node:fs/promises';
|
|
1
|
+
import { access, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
import { detectComposeLayout, readEnvFile, selectedComposeEnvironment } from './compose-layout.js';
|
|
5
5
|
import { dailyActionScripts } from './daily-actions.js';
|
|
6
6
|
import { defaultPostgresVersion } from './external-templates.js';
|
|
7
|
-
import { defaultOdooVersion, markerPath } from './environment.js';
|
|
8
|
-
import { listGitmoduleSources, readSourceManifest, sourceManifestPath, } from './source-manifest.js';
|
|
7
|
+
import { defaultOdooVersion, markerPath, replaceSourceRepos } from './environment.js';
|
|
8
|
+
import { listGitmoduleSources, readSourceManifest, sourceReposFromManifest, sourceManifestPath, syncManifestFromMetadataAndGitmodules, writeSourceManifest, } from './source-manifest.js';
|
|
9
9
|
const realCommandRunner = async (command, args, options) => {
|
|
10
10
|
const result = await execa(command, args, { cwd: options.cwd });
|
|
11
11
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
@@ -37,6 +37,9 @@ function commandErrorText(error) {
|
|
|
37
37
|
function isRecord(value) {
|
|
38
38
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
39
39
|
}
|
|
40
|
+
function isDoctorOptions(value) {
|
|
41
|
+
return isRecord(value);
|
|
42
|
+
}
|
|
40
43
|
const incompatiblePostgres18MountTargets = ['/var/lib/postgresql/data', '/var/lib/postgresql/18/docker'];
|
|
41
44
|
function parsePostgresMajorFromValue(value) {
|
|
42
45
|
if (!value)
|
|
@@ -63,6 +66,48 @@ function hasInvalidPostgres18Mount(line, mountTarget) {
|
|
|
63
66
|
];
|
|
64
67
|
return shortPatterns.some((pattern) => pattern.test(line));
|
|
65
68
|
}
|
|
69
|
+
function isNonAmbiguousLineForMountFix(line, mountTarget) {
|
|
70
|
+
return hasInvalidPostgres18Mount(line, mountTarget);
|
|
71
|
+
}
|
|
72
|
+
function replaceMountTargetInLine(line, from, to) {
|
|
73
|
+
return line.split(from).join(to);
|
|
74
|
+
}
|
|
75
|
+
function normalizePostgres18MountTargetsInComposeContent(content) {
|
|
76
|
+
const fixedTargets = [];
|
|
77
|
+
const fixed = [];
|
|
78
|
+
const hasTrailingNewline = content.endsWith('\n');
|
|
79
|
+
const comparableContent = hasTrailingNewline ? content.slice(0, -1) : content;
|
|
80
|
+
const lines = comparableContent.split(/\r?\n/);
|
|
81
|
+
const nextLines = [];
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
const commentIndex = line.indexOf('#');
|
|
84
|
+
const comment = commentIndex === -1 ? '' : line.slice(commentIndex);
|
|
85
|
+
const body = commentIndex === -1 ? line : line.slice(0, commentIndex);
|
|
86
|
+
let nextBody = body;
|
|
87
|
+
let lineFixed = false;
|
|
88
|
+
for (const target of incompatiblePostgres18MountTargets) {
|
|
89
|
+
if (!isNonAmbiguousLineForMountFix(body, target))
|
|
90
|
+
continue;
|
|
91
|
+
nextBody = replaceMountTargetInLine(nextBody, target, '/var/lib/postgresql');
|
|
92
|
+
if (!fixedTargets.includes(target)) {
|
|
93
|
+
fixedTargets.push(target);
|
|
94
|
+
}
|
|
95
|
+
lineFixed = true;
|
|
96
|
+
}
|
|
97
|
+
if (lineFixed) {
|
|
98
|
+
fixed.push(line);
|
|
99
|
+
nextLines.push(`${nextBody}${comment}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
nextLines.push(line);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
content: `${nextLines.join('\n')}${hasTrailingNewline ? '\n' : ''}`,
|
|
107
|
+
fixed,
|
|
108
|
+
fixedTargets,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
66
111
|
function invalidPostgres18MountTargetsInCompose(content) {
|
|
67
112
|
const badTargets = new Set();
|
|
68
113
|
for (const rawLine of content.split(/\r?\n/)) {
|
|
@@ -199,7 +244,7 @@ function formatKeyForPath(key) {
|
|
|
199
244
|
const [sourceType, ...pathParts] = key.split(':');
|
|
200
245
|
return sourceRepoPath(sourceType, pathParts.join(':'));
|
|
201
246
|
}
|
|
202
|
-
function checkSourceConsistency(sourceRepos, manifestEntries, gitmoduleSources, manifestExists) {
|
|
247
|
+
function checkSourceConsistency(sourceRepos, manifestEntries, gitmoduleSources, manifestExists, gitmodulesExists) {
|
|
203
248
|
if (!manifestExists) {
|
|
204
249
|
return [];
|
|
205
250
|
}
|
|
@@ -224,13 +269,21 @@ function checkSourceConsistency(sourceRepos, manifestEntries, gitmoduleSources,
|
|
|
224
269
|
if (!metadataEntries.has(key)) {
|
|
225
270
|
errors.push(`Manifest source entry missing in metadata: ${formatKeyForPath(key)}`);
|
|
226
271
|
}
|
|
227
|
-
if (!gitmoduleSet.has(key)) {
|
|
272
|
+
if (gitmodulesExists && !gitmoduleSet.has(key)) {
|
|
228
273
|
errors.push(`Manifest source path missing in .gitmodules: ${formatKeyForPath(key)}`);
|
|
229
274
|
}
|
|
230
275
|
}
|
|
231
276
|
return errors;
|
|
232
277
|
}
|
|
233
|
-
|
|
278
|
+
async function repairSourceManifestFromDiscoveredState(target, sourceRepos, fallbackBranch, gitmoduleSources) {
|
|
279
|
+
const entries = syncManifestFromMetadataAndGitmodules(sourceRepos, fallbackBranch, gitmoduleSources);
|
|
280
|
+
await writeSourceManifest(target, entries);
|
|
281
|
+
await replaceSourceRepos(target, sourceReposFromManifest(entries));
|
|
282
|
+
}
|
|
283
|
+
export async function runDoctor(target = process.cwd(), runnerOrOptions = realCommandRunner, options = {}) {
|
|
284
|
+
const actualRunner = isDoctorOptions(runnerOrOptions) ? realCommandRunner : runnerOrOptions;
|
|
285
|
+
const actualOptions = isDoctorOptions(runnerOrOptions) ? runnerOrOptions : options;
|
|
286
|
+
const appliedFixes = [];
|
|
234
287
|
const lines = ['WPMoo doctor'];
|
|
235
288
|
const errors = [];
|
|
236
289
|
const warnings = [];
|
|
@@ -272,6 +325,16 @@ export async function runDoctor(target = process.cwd(), runner = realCommandRunn
|
|
|
272
325
|
errors.push(`Cannot read compose file for compatibility check: ${file}: ${errorMessage(error)}`);
|
|
273
326
|
continue;
|
|
274
327
|
}
|
|
328
|
+
if (actualOptions.fix) {
|
|
329
|
+
const normalization = normalizePostgres18MountTargetsInComposeContent(content);
|
|
330
|
+
if (normalization.fixed.length > 0) {
|
|
331
|
+
await writeFile(composePath, normalization.content, 'utf8');
|
|
332
|
+
for (const target of normalization.fixedTargets) {
|
|
333
|
+
appliedFixes.push(`Normalized PostgreSQL 18 mount target in '${file}': replaced '${target}' -> '/var/lib/postgresql'`);
|
|
334
|
+
}
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
275
338
|
const badMounts = invalidPostgres18MountTargetsInCompose(content);
|
|
276
339
|
for (const badMount of badMounts) {
|
|
277
340
|
errors.push(`PostgreSQL 18 compatibility issue in '${file}': mount target '${badMount}' is invalid; recommend using '/var/lib/postgresql'`);
|
|
@@ -301,17 +364,43 @@ export async function runDoctor(target = process.cwd(), runner = realCommandRunn
|
|
|
301
364
|
const manifestPath = join(target, sourceManifestPath);
|
|
302
365
|
const hasManifest = await exists(manifestPath);
|
|
303
366
|
let manifestEntries = [];
|
|
367
|
+
let manifestReadError;
|
|
304
368
|
if (hasManifest) {
|
|
305
369
|
try {
|
|
306
370
|
manifestEntries = (await readSourceManifest(target)).sources;
|
|
307
371
|
}
|
|
308
372
|
catch (error) {
|
|
309
|
-
|
|
373
|
+
manifestReadError = `Failed to read source manifest ${sourceManifestPath}: ${errorMessage(error)}`;
|
|
374
|
+
if (!actualOptions.fix) {
|
|
375
|
+
errors.push(manifestReadError);
|
|
376
|
+
}
|
|
310
377
|
}
|
|
311
378
|
}
|
|
312
379
|
const gitmoduleSources = await listGitmoduleSources(target);
|
|
313
|
-
|
|
314
|
-
|
|
380
|
+
const hasGitmodules = await exists(join(target, '.gitmodules'));
|
|
381
|
+
const sourceConsistencyIssues = !manifestReadError
|
|
382
|
+
? checkSourceConsistency(sourceRepos, manifestEntries, gitmoduleSources, hasManifest, hasGitmodules)
|
|
383
|
+
: [];
|
|
384
|
+
const shouldSyncSources = actualOptions.fix &&
|
|
385
|
+
(manifestReadError || sourceConsistencyIssues.length > 0 || (!hasManifest && (sourceRepos.length > 0 || gitmoduleSources.length > 0)));
|
|
386
|
+
if (sourceConsistencyIssues.length > 0) {
|
|
387
|
+
if (actualOptions.fix) {
|
|
388
|
+
const uniqueIssues = [...new Set(sourceConsistencyIssues)];
|
|
389
|
+
appliedFixes.push(...uniqueIssues.map((issue) => `Will regenerate source manifest and metadata to fix: ${issue}`));
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
errors.push(...sourceConsistencyIssues);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
else if (manifestReadError) {
|
|
396
|
+
appliedFixes.push('Will regenerate source manifest and metadata after repairing source manifest read failure.');
|
|
397
|
+
}
|
|
398
|
+
else if (shouldSyncSources) {
|
|
399
|
+
appliedFixes.push('Will create missing source manifest from metadata and .gitmodules state.');
|
|
400
|
+
}
|
|
401
|
+
if (shouldSyncSources && actualOptions.fix) {
|
|
402
|
+
await repairSourceManifestFromDiscoveredState(target, sourceRepos, odooVersion, gitmoduleSources);
|
|
403
|
+
appliedFixes.push('Synced source manifest and metadata with current metadata/.gitmodules state.');
|
|
315
404
|
}
|
|
316
405
|
if (env) {
|
|
317
406
|
const httpPort = validatePort('HTTP_PORT', env, errors);
|
|
@@ -324,14 +413,14 @@ export async function runDoctor(target = process.cwd(), runner = realCommandRunn
|
|
|
324
413
|
}
|
|
325
414
|
}
|
|
326
415
|
try {
|
|
327
|
-
await
|
|
416
|
+
await actualRunner('docker', ['version'], { cwd: target });
|
|
328
417
|
lines.push('OK docker CLI');
|
|
329
418
|
}
|
|
330
419
|
catch (error) {
|
|
331
420
|
errors.push(`Docker CLI check failed: ${errorMessage(error)}`);
|
|
332
421
|
}
|
|
333
422
|
try {
|
|
334
|
-
await
|
|
423
|
+
await actualRunner('docker', ['compose', 'version'], { cwd: target });
|
|
335
424
|
lines.push('OK docker compose');
|
|
336
425
|
}
|
|
337
426
|
catch (error) {
|
|
@@ -339,7 +428,7 @@ export async function runDoctor(target = process.cwd(), runner = realCommandRunn
|
|
|
339
428
|
}
|
|
340
429
|
if (sourceRepos.length > 0) {
|
|
341
430
|
try {
|
|
342
|
-
const result = await
|
|
431
|
+
const result = await actualRunner('git', ['submodule', 'status', '--recursive'], { cwd: target });
|
|
343
432
|
const submoduleErrors = sourceSubmoduleStatusErrors(result.stdout, sourceRepos);
|
|
344
433
|
errors.push(...submoduleErrors);
|
|
345
434
|
if (submoduleErrors.length === 0) {
|
|
@@ -356,16 +445,33 @@ export async function runDoctor(target = process.cwd(), runner = realCommandRunn
|
|
|
356
445
|
}
|
|
357
446
|
}
|
|
358
447
|
try {
|
|
359
|
-
await
|
|
448
|
+
await actualRunner('gh', ['auth', 'status'], { cwd: target });
|
|
360
449
|
lines.push('OK GitHub CLI auth');
|
|
361
450
|
}
|
|
362
451
|
catch (error) {
|
|
363
452
|
warnings.push(`WARN GitHub CLI auth: ${errorMessage(error)}`);
|
|
364
453
|
}
|
|
365
454
|
if (errors.length > 0) {
|
|
455
|
+
if (actualOptions.fix && appliedFixes.length > 0) {
|
|
456
|
+
return [
|
|
457
|
+
'Doctor auto-fixes were not enough to satisfy all checks.',
|
|
458
|
+
...appliedFixes.map((fix) => `- ${fix}`),
|
|
459
|
+
renderFailure(errors),
|
|
460
|
+
].join('\n');
|
|
461
|
+
}
|
|
366
462
|
throw new Error(renderFailure(errors));
|
|
367
463
|
}
|
|
368
464
|
lines.push(...warnings);
|
|
369
465
|
lines.push('Doctor checks passed.');
|
|
370
|
-
|
|
466
|
+
const report = lines.join('\n');
|
|
467
|
+
if (actualOptions.fix && appliedFixes.length > 0) {
|
|
468
|
+
const postFixReport = await runDoctor(target, actualRunner, { ...actualOptions, fix: false });
|
|
469
|
+
return [
|
|
470
|
+
'Applied safe doctor fixes:',
|
|
471
|
+
...appliedFixes.map((fix) => `- ${fix}`),
|
|
472
|
+
'',
|
|
473
|
+
postFixReport,
|
|
474
|
+
].join('\n');
|
|
475
|
+
}
|
|
476
|
+
return report;
|
|
371
477
|
}
|
package/dist/help.js
CHANGED
|
@@ -15,8 +15,8 @@ Usage:
|
|
|
15
15
|
npx @wpmoo/odoo source remove --repo <name> [--source-type private|oca|external]
|
|
16
16
|
npx @wpmoo/odoo add-module --repo <source-repo> --module <module-name>
|
|
17
17
|
npx @wpmoo/odoo remove-module --repo <source-repo> --module <module-name>
|
|
18
|
-
npx @wpmoo/odoo reset
|
|
19
|
-
npx @wpmoo/odoo doctor
|
|
18
|
+
npx @wpmoo/odoo reset [--dry-run]
|
|
19
|
+
npx @wpmoo/odoo doctor [--fix]
|
|
20
20
|
npx @wpmoo/odoo start
|
|
21
21
|
npx @wpmoo/odoo stop
|
|
22
22
|
npx @wpmoo/odoo logs [service]
|
|
@@ -87,6 +87,7 @@ Wizard local-only path:
|
|
|
87
87
|
Status and doctor:
|
|
88
88
|
status: fast and offline. Reads local environment metadata and files only.
|
|
89
89
|
doctor: deeper health check. May check Docker CLI access and GitHub workflows.
|
|
90
|
+
doctor --fix: applies safe file-level repairs. Runs doctor again after fixes.
|
|
90
91
|
|
|
91
92
|
Task recipes:
|
|
92
93
|
Create environment:
|
|
@@ -105,11 +106,13 @@ Task recipes:
|
|
|
105
106
|
npx @wpmoo/odoo test <module[,module]> [--db <db>] [--mode init|update] [--tags <tags>]
|
|
106
107
|
Safe reset and recover:
|
|
107
108
|
npx @wpmoo/odoo snapshot [db] [snapshot-name]
|
|
109
|
+
npx @wpmoo/odoo reset --dry-run
|
|
108
110
|
npx @wpmoo/odoo reset
|
|
109
111
|
npx @wpmoo/odoo restore-snapshot <snapshot-name> [db]
|
|
110
112
|
Daily command checks:
|
|
111
113
|
npx @wpmoo/odoo status
|
|
112
114
|
npx @wpmoo/odoo doctor
|
|
115
|
+
npx @wpmoo/odoo doctor --fix
|
|
113
116
|
npx @wpmoo/odoo logs [service]
|
|
114
117
|
npx @wpmoo/odoo restart
|
|
115
118
|
|
|
@@ -21,11 +21,12 @@ not validate staging or production deployments.
|
|
|
21
21
|
| Compose resource files | Compact compose layout is present (`compose.yaml` + environment overlays under `compose/`), plus config/resources/scripts. | `npx @wpmoo/odoo create ...` |
|
|
22
22
|
| `./moo` delegation | `./moo` dispatches fixed daily actions to the matching script and preserves argument pass-through. | `./moo <action> ...` |
|
|
23
23
|
| Doctor checks | Metadata, compose files, scripts, source repo paths, and local tooling checks behave as expected. | `npx @wpmoo/odoo doctor` or `./moo doctor` |
|
|
24
|
-
|
|
|
24
|
+
| Doctor safe fixes | Safe file-level fixes are applied only with `--fix`, then doctor runs again and reports any remaining manual issues. | `npx @wpmoo/odoo doctor --fix` |
|
|
25
|
+
| Generated Postgres checks | For PostgreSQL 18 environments, doctor validates db mount targets avoid old PG image-specific paths and can normalize safe targets with `--fix`. | `npx @wpmoo/odoo doctor`, `npx @wpmoo/odoo doctor --fix` |
|
|
25
26
|
| Source repo add/remove | Source repository registration and submodule lifecycle behave correctly. | `npx @wpmoo/odoo add-repo ...`, `npx @wpmoo/odoo remove-repo ...` |
|
|
26
27
|
| Source manifest sync | Source repo metadata, `.gitmodules`, and `odoo/custom/manifests/sources.yaml` stay aligned. | `npx @wpmoo/odoo source list`, `npx @wpmoo/odoo source sync` |
|
|
27
28
|
| Module add/remove | Module registration changes are applied to the selected source repo config. | `npx @wpmoo/odoo add-module ...`, `npx @wpmoo/odoo remove-module ...` |
|
|
28
|
-
| Safe reset | Generated files are refreshed (including `compose.yaml` overlays and env example) without deleting source module code. Local runtime/data directories and custom source layout content are preserved; legacy user-editable paths from older templates may remain and are reported for manual cleanup. | `npx @wpmoo/odoo reset` |
|
|
29
|
+
| Safe reset | Generated files are refreshed (including `compose.yaml` overlays and env example) without deleting source module code. Local runtime/data directories and custom source layout content are preserved; legacy user-editable paths from older templates may remain and are reported for manual cleanup. | `npx @wpmoo/odoo reset --dry-run`, `npx @wpmoo/odoo reset` |
|
|
29
30
|
| Snapshot/restore and lint/pot | These actions are delegated by `./moo` to compose scripts without extra package-side logic. | `./moo snapshot ...`, `./moo restore-snapshot ...`, `./moo lint`, `./moo pot ...` |
|
|
30
31
|
|
|
31
32
|
## Compact compose checks
|
|
@@ -56,6 +57,10 @@ volume and tmpfs mount targets use `/var/lib/postgresql` directly:
|
|
|
56
57
|
Paths such as `/var/lib/postgresql/data` and `/var/lib/postgresql/18/docker` are
|
|
57
58
|
no longer accepted by the package `doctor` check.
|
|
58
59
|
|
|
60
|
+
`doctor --fix` may rewrite these safe mount targets to `/var/lib/postgresql`.
|
|
61
|
+
It does not upgrade existing database data; if a real PostgreSQL major upgrade
|
|
62
|
+
is involved, use PostgreSQL upgrade tooling first.
|
|
63
|
+
|
|
59
64
|
## Safe reset policy
|
|
60
65
|
|
|
61
66
|
Safe reset intentionally avoids deleting user-editable legacy paths from old
|
|
@@ -80,6 +85,9 @@ odoo/custom/patches/
|
|
|
80
85
|
odoo/custom/manifests/
|
|
81
86
|
```
|
|
82
87
|
|
|
88
|
+
Run `npx @wpmoo/odoo reset --dry-run` before writing changes when you need to
|
|
89
|
+
review the generated file refresh plan.
|
|
90
|
+
|
|
83
91
|
## Source manifest checks
|
|
84
92
|
|
|
85
93
|
Generated environments include `odoo/custom/manifests/sources.yaml`. The manifest
|
|
@@ -99,8 +107,10 @@ manifest and normalize `.wpmoo/odoo.json` source entries:
|
|
|
99
107
|
npx @wpmoo/odoo source sync
|
|
100
108
|
```
|
|
101
109
|
|
|
102
|
-
`doctor` fails when manifest entries, metadata entries, and source
|
|
103
|
-
paths diverge.
|
|
110
|
+
`doctor` fails when manifest entries, metadata entries, and registered source
|
|
111
|
+
submodule paths diverge. `doctor --fix` can regenerate
|
|
112
|
+
`odoo/custom/manifests/sources.yaml` from metadata plus `.gitmodules` when the
|
|
113
|
+
manifest is missing, unreadable, or stale.
|
|
104
114
|
|
|
105
115
|
## Local verification commands
|
|
106
116
|
|