@wpmoo/odoo 0.8.64 → 0.8.65

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 CHANGED
@@ -138,8 +138,11 @@ npx @wpmoo/odoo --help
138
138
  npx @wpmoo/odoo --version
139
139
 
140
140
  npx @wpmoo/odoo status
141
+ npx @wpmoo/odoo status --json
141
142
  npx @wpmoo/odoo doctor
143
+ npx @wpmoo/odoo doctor --json
142
144
  npx @wpmoo/odoo doctor --fix
145
+ npx @wpmoo/odoo source list --json
143
146
  npx @wpmoo/odoo add-repo --repo-url https://github.com/example-org/odoo_sample_module_reports.git
144
147
  npx @wpmoo/odoo remove-repo --repo odoo_sample_module_reports
145
148
  npx @wpmoo/odoo add-module --repo odoo_sample_module --module odoo_sample_module_base --source-type private
@@ -319,12 +322,14 @@ Inspect configured sources:
319
322
 
320
323
  ```bash
321
324
  npx @wpmoo/odoo source list
325
+ npx @wpmoo/odoo source list --json
322
326
  ```
323
327
 
324
328
  Regenerate the manifest and metadata from the current metadata/gitmodule state:
325
329
 
326
330
  ```bash
327
331
  npx @wpmoo/odoo source sync
332
+ npx @wpmoo/odoo source sync --json
328
333
  ```
329
334
 
330
335
  `source add` and `source remove` are direct aliases for the same repository
@@ -344,10 +349,23 @@ npx @wpmoo/odoo source remove --repo server-tools --source-type oca
344
349
 
345
350
  ```bash
346
351
  npx @wpmoo/odoo status
352
+ npx @wpmoo/odoo status --json
347
353
  ```
348
354
 
349
355
  It reports whether the environment is detected, which Odoo version is selected, how many source repos are configured, how many module candidates are present, which core files are missing, and the recommended next action.
350
356
 
357
+ For automation and VS Code cockpit integration, all of these commands also support
358
+ `--json`:
359
+
360
+ ```bash
361
+ npx @wpmoo/odoo status --json
362
+ npx @wpmoo/odoo source list --json
363
+ npx @wpmoo/odoo source sync --json
364
+ npx @wpmoo/odoo doctor --json
365
+ ```
366
+
367
+ JSON output is optional; human-readable output remains the default.
368
+
351
369
  `doctor` performs deeper checks:
352
370
 
353
371
  ```bash
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import { detectDevelopmentEnvironment } from './environment.js';
12
12
  import { commandOdooVersion } from './environment-version.js';
13
13
  import { defaultAgentSkillsTemplateUrl } from './external-templates.js';
14
14
  import { isDailyActionCommand, runDailyAction } from './daily-actions.js';
15
- import { runDoctor } from './doctor.js';
15
+ import { getDoctorReport, runDoctor } from './doctor.js';
16
16
  import { getOriginUrl, realGit } from './git.js';
17
17
  import { renderHelp } from './help.js';
18
18
  import { addModuleToSourceRepo, listModulesInSourceRepo, removeModuleFromSourceRepo, } from './module-actions.js';
@@ -22,13 +22,13 @@ import { promptRepositoryUrl } from './prompt-repositories.js';
22
22
  import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-url.js';
23
23
  import { addModuleRepo, listModuleRepos, removeModuleRepo } from './repo-actions.js';
24
24
  import { renderSafeResetPreview, safeResetEnvironment } from './safe-reset.js';
25
- import { listSources, renderSourceList, syncSources } from './source-actions.js';
25
+ import { listSources, renderSourceList, sourceListJson, sourceSyncJson, syncSources, } from './source-actions.js';
26
26
  import { checkGitHubRepositories, createGitHubRepositories, manualCreateCommands, repositoryPreflightAvailable, } from './repository-preflight.js';
27
27
  import { scaffold } from './scaffold.js';
28
28
  import { renderBanner } from './templates.js';
29
29
  import { checkForUpdate, installLatestPackage, isUpdateCheckSkipped, restartCli } from './update-check.js';
30
30
  import { packageName, packageVersion, renderVersion, renderVersionTag } from './version.js';
31
- import { getEnvironmentStatus, renderEnvironmentStatusForTarget, renderEnvironmentStatusSummary, } from './status.js';
31
+ import { environmentStatusJson, getEnvironmentStatus, renderEnvironmentStatusForTarget, renderEnvironmentStatusSummary, } from './status.js';
32
32
  import { getGitHubAccounts, getGitHubRepositoryStatus, githubRepositoryUrl, realGitHub, createGitHubRepository, } from './github.js';
33
33
  import { environmentGitHubOwner } from './environment-context.js';
34
34
  import { handlePromptCancel, handleUnavailableMenuChoice, installPromptCancelKeyTracker, isMenuBackSignal, MenuBackSignal, menuIntroTitle, menuPromptMessage, } from './menu-navigation.js';
@@ -106,6 +106,12 @@ function booleanOption(values, key, fallback) {
106
106
  return false;
107
107
  throw new Error(`Invalid boolean value for --${key}: ${value}`);
108
108
  }
109
+ function jsonOption(values) {
110
+ return booleanOption(values, 'json', false);
111
+ }
112
+ function printJson(value) {
113
+ console.log(JSON.stringify(value));
114
+ }
109
115
  function yellow(value) {
110
116
  if (!process.stdout.isTTY || process.env.NO_COLOR !== undefined)
111
117
  return value;
@@ -493,17 +499,19 @@ function resetCommandOptionsFromArgs(argv) {
493
499
  };
494
500
  }
495
501
  function doctorOptionsFromArgs(argv) {
496
- if (argv.length === 0) {
497
- return {};
498
- }
499
502
  const { values } = parseArgs(argv);
500
503
  const keys = Object.keys(values);
501
- if (keys.length !== 1 || !Object.hasOwn(values, 'fix')) {
504
+ const allowedKeys = new Set(['fix', 'json']);
505
+ if (!keys.every((key) => allowedKeys.has(key))) {
502
506
  throw new Error('Usage: wpmoo doctor');
503
507
  }
504
- return {
505
- fix: booleanOption(values, 'fix', false),
508
+ const options = {
509
+ json: jsonOption(values),
506
510
  };
511
+ if (Object.hasOwn(values, 'fix')) {
512
+ options.fix = booleanOption(values, 'fix', false);
513
+ }
514
+ return options;
507
515
  }
508
516
  function sourceUsage() {
509
517
  return 'Usage: wpmoo source <list|sync|add|remove> [options]';
@@ -513,11 +521,15 @@ function sourceSyncOptionsFromArgs(argv) {
513
521
  return {
514
522
  target: resolve(stringOption(values, 'target') ?? process.cwd()),
515
523
  stage: booleanOption(values, 'stage', true),
524
+ json: jsonOption(values),
516
525
  };
517
526
  }
518
- function sourceListTargetFromArgs(argv) {
527
+ function sourceListOptionsFromArgs(argv) {
519
528
  const { values } = parseArgs(argv);
520
- return resolve(stringOption(values, 'target') ?? process.cwd());
529
+ return {
530
+ target: resolve(stringOption(values, 'target') ?? process.cwd()),
531
+ json: jsonOption(values),
532
+ };
521
533
  }
522
534
  async function runSourceCommand(argv) {
523
535
  const [subcommand, ...subcommandArgv] = argv;
@@ -525,15 +537,24 @@ async function runSourceCommand(argv) {
525
537
  throw new Error(sourceUsage());
526
538
  }
527
539
  if (subcommand === 'list') {
540
+ const options = sourceListOptionsFromArgs(subcommandArgv);
541
+ const sources = await listSources(options.target);
542
+ if (options.json) {
543
+ printJson(sourceListJson(sources));
544
+ return;
545
+ }
528
546
  console.log(renderBanner());
529
- const target = sourceListTargetFromArgs(subcommandArgv);
530
- console.log(renderSourceList(await listSources(target)));
547
+ console.log(renderSourceList(sources));
531
548
  return;
532
549
  }
533
550
  if (subcommand === 'sync') {
534
- console.log(renderBanner());
535
551
  const options = sourceSyncOptionsFromArgs(subcommandArgv);
536
- await syncSources(options);
552
+ const sources = await syncSources({ target: options.target, stage: options.stage });
553
+ if (options.json) {
554
+ printJson(sourceSyncJson(sources, options.target));
555
+ return;
556
+ }
557
+ console.log(renderBanner());
537
558
  outro(`Synced source manifest in ${options.target}.`);
538
559
  return;
539
560
  }
@@ -907,14 +928,28 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
907
928
  }
908
929
  if (route.command === 'doctor') {
909
930
  const options = doctorOptionsFromArgs(route.argv);
931
+ const doctorOptions = {};
932
+ if (options.fix !== undefined) {
933
+ doctorOptions.fix = options.fix;
934
+ }
935
+ if (options.json) {
936
+ printJson(await getDoctorReport(cwd, doctorOptions));
937
+ return;
938
+ }
910
939
  console.log(renderBanner());
911
- console.log(options.fix === undefined ? await runDoctor(cwd) : await runDoctor(cwd, options));
940
+ console.log(options.fix === undefined ? await runDoctor(cwd) : await runDoctor(cwd, doctorOptions));
912
941
  return;
913
942
  }
914
943
  if (route.command === 'status') {
915
- if (route.argv.length > 0) {
944
+ const { values } = parseArgs(route.argv);
945
+ const keys = Object.keys(values);
946
+ if (!keys.every((key) => key === 'json')) {
916
947
  throw new Error('Usage: wpmoo status');
917
948
  }
949
+ if (jsonOption(values)) {
950
+ printJson(environmentStatusJson(await getEnvironmentStatus(cwd)));
951
+ return;
952
+ }
918
953
  console.log(renderBanner());
919
954
  console.log(await renderEnvironmentStatusForTarget(cwd));
920
955
  return;
package/dist/doctor.js CHANGED
@@ -40,6 +40,11 @@ function isRecord(value) {
40
40
  function isDoctorOptions(value) {
41
41
  return isRecord(value);
42
42
  }
43
+ function isMetadataError(message) {
44
+ return (message.startsWith('Missing metadata file:') ||
45
+ message.startsWith('Invalid metadata JSON in .wpmoo/odoo.json') ||
46
+ message.startsWith('Invalid sourceRepos entry in .wpmoo/odoo.json'));
47
+ }
43
48
  const incompatiblePostgres18MountTargets = ['/var/lib/postgresql/data', '/var/lib/postgresql/18/docker'];
44
49
  function parsePostgresMajorFromValue(value) {
45
50
  if (!value)
@@ -280,24 +285,41 @@ async function repairSourceManifestFromDiscoveredState(target, sourceRepos, fall
280
285
  await writeSourceManifest(target, entries);
281
286
  await replaceSourceRepos(target, sourceReposFromManifest(entries));
282
287
  }
283
- export async function runDoctor(target = process.cwd(), runnerOrOptions = realCommandRunner, options = {}) {
288
+ export async function getDoctorReport(target = process.cwd(), runnerOrOptions = realCommandRunner, options = {}) {
284
289
  const actualRunner = isDoctorOptions(runnerOrOptions) ? realCommandRunner : runnerOrOptions;
285
290
  const actualOptions = isDoctorOptions(runnerOrOptions) ? runnerOrOptions : options;
286
- const appliedFixes = [];
287
- const lines = ['WPMoo doctor'];
288
291
  const errors = [];
289
292
  const warnings = [];
290
- const metadata = await readMetadata(target);
291
- lines.push(`OK metadata ${markerPath}`);
293
+ const checks = [];
294
+ const appliedFixes = [];
295
+ const report = {
296
+ schemaVersion: 1,
297
+ command: 'doctor',
298
+ ok: false,
299
+ target,
300
+ checks,
301
+ warnings,
302
+ errors,
303
+ appliedFixes,
304
+ };
305
+ let metadata;
306
+ try {
307
+ metadata = await readMetadata(target);
308
+ }
309
+ catch (error) {
310
+ errors.push(errorMessage(error));
311
+ return report;
312
+ }
313
+ checks.push(`OK metadata ${markerPath}`);
292
314
  const engine = metadataString(metadata, 'engine') ?? 'compose';
293
315
  if (engine !== 'compose') {
294
316
  errors.push(`Unsupported environment engine: ${engine}`);
295
317
  }
296
318
  else {
297
- lines.push('OK engine compose');
319
+ checks.push('OK engine compose');
298
320
  }
299
321
  const odooVersion = metadataString(metadata, 'odooVersion') ?? defaultOdooVersion;
300
- lines.push(`OK Odoo version ${odooVersion}`);
322
+ checks.push(`OK Odoo version ${odooVersion}`);
301
323
  const env = await readEnvFile(target);
302
324
  const composeVersions = new Set([odooVersion]);
303
325
  const envOdooVersion = env?.get('ODOO_VERSION')?.trim();
@@ -312,7 +334,7 @@ export async function runDoctor(target = process.cwd(), runnerOrOptions = realCo
312
334
  errors.push(...composeLayout.errors);
313
335
  }
314
336
  else {
315
- lines.push(`OK compose files ${composeLayout.files.join(', ')}`);
337
+ checks.push(`OK compose files ${composeLayout.files.join(', ')}`);
316
338
  const postgresVersion = inferPostgresVersion(metadata, odooVersion, env);
317
339
  if (postgresVersion === '18') {
318
340
  for (const file of composeLayout.files) {
@@ -351,16 +373,23 @@ export async function runDoctor(target = process.cwd(), runnerOrOptions = realCo
351
373
  }
352
374
  }
353
375
  if (errors.length === scriptErrorCount) {
354
- lines.push(`OK scripts ${scriptNames.length} checked`);
376
+ checks.push(`OK scripts ${scriptNames.length} checked`);
377
+ }
378
+ let sourceRepos;
379
+ try {
380
+ sourceRepos = sourceReposFromMetadata(metadata);
381
+ }
382
+ catch (error) {
383
+ errors.push(errorMessage(error));
384
+ return report;
355
385
  }
356
- const sourceRepos = sourceReposFromMetadata(metadata);
357
386
  for (const repo of sourceRepos) {
358
387
  const relativePath = sourceRepoPath(normalizeSourceType(repo.sourceType), repo.path);
359
388
  if (!(await exists(join(target, relativePath))) && repo.path) {
360
389
  errors.push(`Missing source repo path: ${relativePath}`);
361
390
  }
362
391
  }
363
- lines.push(`OK source repos ${sourceRepos.length} checked`);
392
+ checks.push(`OK source repos ${sourceRepos.length} checked`);
364
393
  const manifestPath = join(target, sourceManifestPath);
365
394
  const hasManifest = await exists(manifestPath);
366
395
  let manifestEntries = [];
@@ -409,19 +438,19 @@ export async function runDoctor(target = process.cwd(), runnerOrOptions = realCo
409
438
  errors.push('HTTP_PORT and GEVENT_PORT in .env must not be equal');
410
439
  }
411
440
  if (/^\d+$/.test(httpPort) && /^\d+$/.test(geventPort) && httpPort !== geventPort) {
412
- lines.push(`OK .env ports HTTP_PORT=${httpPort} GEVENT_PORT=${geventPort}`);
441
+ checks.push(`OK .env ports HTTP_PORT=${httpPort} GEVENT_PORT=${geventPort}`);
413
442
  }
414
443
  }
415
444
  try {
416
445
  await actualRunner('docker', ['version'], { cwd: target });
417
- lines.push('OK docker CLI');
446
+ checks.push('OK docker CLI');
418
447
  }
419
448
  catch (error) {
420
449
  errors.push(`Docker CLI check failed: ${errorMessage(error)}`);
421
450
  }
422
451
  try {
423
452
  await actualRunner('docker', ['compose', 'version'], { cwd: target });
424
- lines.push('OK docker compose');
453
+ checks.push('OK docker compose');
425
454
  }
426
455
  catch (error) {
427
456
  errors.push(`Docker Compose check failed: ${errorMessage(error)}`);
@@ -432,12 +461,12 @@ export async function runDoctor(target = process.cwd(), runnerOrOptions = realCo
432
461
  const submoduleErrors = sourceSubmoduleStatusErrors(result.stdout, sourceRepos);
433
462
  errors.push(...submoduleErrors);
434
463
  if (submoduleErrors.length === 0) {
435
- lines.push(`OK git submodules ${sourceRepos.length} checked`);
464
+ checks.push(`OK git submodules ${sourceRepos.length} checked`);
436
465
  }
437
466
  }
438
467
  catch (error) {
439
468
  if (isNotGitCheckoutError(error)) {
440
- lines.push('OK git submodules skipped (not a git checkout)');
469
+ checks.push('OK git submodules skipped (not a git checkout)');
441
470
  }
442
471
  else {
443
472
  errors.push(`Git submodule status check failed: ${errorMessage(error)}`);
@@ -446,32 +475,45 @@ export async function runDoctor(target = process.cwd(), runnerOrOptions = realCo
446
475
  }
447
476
  try {
448
477
  await actualRunner('gh', ['auth', 'status'], { cwd: target });
449
- lines.push('OK GitHub CLI auth');
478
+ checks.push('OK GitHub CLI auth');
450
479
  }
451
480
  catch (error) {
452
- warnings.push(`WARN GitHub CLI auth: ${errorMessage(error)}`);
481
+ warnings.push(`GitHub CLI auth: ${errorMessage(error)}`);
453
482
  }
454
- if (errors.length > 0) {
455
- if (actualOptions.fix && appliedFixes.length > 0) {
483
+ report.ok = errors.length === 0;
484
+ return report;
485
+ }
486
+ export async function runDoctor(target = process.cwd(), runnerOrOptions = realCommandRunner, options = {}) {
487
+ const report = await getDoctorReport(target, runnerOrOptions, options);
488
+ const actualRunner = isDoctorOptions(runnerOrOptions) ? realCommandRunner : runnerOrOptions;
489
+ const actualOptions = isDoctorOptions(runnerOrOptions) ? runnerOrOptions : options;
490
+ if (!report.ok) {
491
+ if (report.errors.some(isMetadataError)) {
492
+ throw new Error(report.errors[0]);
493
+ }
494
+ if (actualOptions.fix && report.appliedFixes.length > 0) {
456
495
  return [
457
496
  'Doctor auto-fixes were not enough to satisfy all checks.',
458
- ...appliedFixes.map((fix) => `- ${fix}`),
459
- renderFailure(errors),
497
+ ...report.appliedFixes.map((fix) => `- ${fix}`),
498
+ renderFailure(report.errors),
460
499
  ].join('\n');
461
500
  }
462
- throw new Error(renderFailure(errors));
501
+ throw new Error(renderFailure(report.errors));
463
502
  }
464
- lines.push(...warnings);
465
- lines.push('Doctor checks passed.');
466
- const report = lines.join('\n');
467
- if (actualOptions.fix && appliedFixes.length > 0) {
503
+ const renderedReport = [
504
+ 'WPMoo doctor',
505
+ ...report.checks,
506
+ ...report.warnings.map((warning) => `WARN ${warning}`),
507
+ 'Doctor checks passed.',
508
+ ];
509
+ if (report.appliedFixes.length > 0) {
468
510
  const postFixReport = await runDoctor(target, actualRunner, { ...actualOptions, fix: false });
469
511
  return [
470
512
  'Applied safe doctor fixes:',
471
- ...appliedFixes.map((fix) => `- ${fix}`),
513
+ ...report.appliedFixes.map((fix) => `- ${fix}`),
472
514
  '',
473
515
  postFixReport,
474
516
  ].join('\n');
475
517
  }
476
- return report;
518
+ return renderedReport.join('\n');
477
519
  }
package/dist/help.js CHANGED
@@ -7,16 +7,20 @@ Usage:
7
7
  npx @wpmoo/odoo
8
8
  npx @wpmoo/odoo create --product <slug> [--target <path>] --dev-repo-url <url> --source-repo-url <url>
9
9
  npx @wpmoo/odoo status
10
+ npx @wpmoo/odoo status --json
10
11
  npx @wpmoo/odoo add-repo --repo-url <url> [--source-type private|oca|external]
11
12
  npx @wpmoo/odoo remove-repo --repo <name>
12
13
  npx @wpmoo/odoo source list
14
+ npx @wpmoo/odoo source list --json
13
15
  npx @wpmoo/odoo source sync
16
+ npx @wpmoo/odoo source sync --json
14
17
  npx @wpmoo/odoo source add --repo-url <url> [--source-type private|oca|external]
15
18
  npx @wpmoo/odoo source remove --repo <name> [--source-type private|oca|external]
16
19
  npx @wpmoo/odoo add-module --repo <source-repo> --module <module-name> [--source-type <category>]
17
20
  npx @wpmoo/odoo remove-module --repo <source-repo> --module <module-name> [--source-type <category>]
18
21
  npx @wpmoo/odoo reset [--dry-run]
19
22
  npx @wpmoo/odoo doctor [--fix]
23
+ npx @wpmoo/odoo doctor --json
20
24
  npx @wpmoo/odoo start
21
25
  npx @wpmoo/odoo stop
22
26
  npx @wpmoo/odoo logs [service]
@@ -48,6 +52,7 @@ Options:
48
52
  --postgres-version <value> PostgreSQL image version written to compose .env.example.
49
53
  --http-port <port> Host HTTP port written to .env.example.
50
54
  --gevent-port <port> Host gevent/live chat port written to .env.example.
55
+ --json Emit machine-readable JSON. Human-readable output remains the default.
51
56
  --repo-url <url> Source repo URL for add-repo.
52
57
  --source-type <category> Source repo category for add-repo/remove-repo/add-module/remove-module. One of private, oca, external. Default: private.
53
58
  --repo <name> Source repo folder name for repo/module actions.
@@ -121,6 +126,13 @@ Task recipes:
121
126
  npx @wpmoo/odoo logs [service]
122
127
  npx @wpmoo/odoo restart
123
128
 
129
+ Machine-readable JSON output:
130
+ for automation and VS Code cockpit integration while keeping default human-readable output.
131
+ npx @wpmoo/odoo status --json
132
+ npx @wpmoo/odoo source list --json
133
+ npx @wpmoo/odoo source sync --json
134
+ npx @wpmoo/odoo doctor --json
135
+
124
136
  Example:
125
137
  npx @wpmoo/odoo create \\
126
138
  --product odoo_sample_module \\
@@ -1,6 +1,12 @@
1
1
  import { defaultOdooVersion, readEnvironmentMetadata, replaceSourceRepos } from './environment.js';
2
2
  import { realGit, stageAll } from './git.js';
3
3
  import { listGitmoduleSources, readSourceManifest, sourceManifestEntriesFromMetadata, sourceReposFromManifest, syncManifestFromMetadataAndGitmodules, writeSourceManifest, } from './source-manifest.js';
4
+ function cloneSourceEntries(entries) {
5
+ return entries.map((entry) => ({
6
+ ...entry,
7
+ addons: [...entry.addons],
8
+ }));
9
+ }
4
10
  export async function listSources(target) {
5
11
  const metadata = await readEnvironmentMetadata(target);
6
12
  const manifest = await readSourceManifest(target);
@@ -24,6 +30,23 @@ export function renderSourceList(entries) {
24
30
  })
25
31
  .join('\n');
26
32
  }
33
+ export function sourceListJson(entries) {
34
+ return {
35
+ schemaVersion: 1,
36
+ command: 'source list',
37
+ ok: true,
38
+ sources: cloneSourceEntries(entries),
39
+ };
40
+ }
41
+ export function sourceSyncJson(entries, target) {
42
+ return {
43
+ schemaVersion: 1,
44
+ command: 'source sync',
45
+ ok: true,
46
+ target,
47
+ sources: cloneSourceEntries(entries),
48
+ };
49
+ }
27
50
  export async function syncSources(options, git = realGit) {
28
51
  const metadata = await readEnvironmentMetadata(options.target);
29
52
  const manifest = await readSourceManifest(options.target);
package/dist/status.js CHANGED
@@ -123,6 +123,21 @@ function summaryText(status) {
123
123
  : 'Environment ready';
124
124
  return `${prefix}: Odoo ${status.odooVersion}, source repos ${status.sourceRepoCount}, module candidates ${status.moduleCandidateCount}.`;
125
125
  }
126
+ function isStatusHealthy(status) {
127
+ if (status.kind !== 'environment')
128
+ return false;
129
+ return (status.missingCoreFiles.length === 0 &&
130
+ status.invalidSourceRepoPaths.length === 0 &&
131
+ status.composeErrors.length === 0);
132
+ }
133
+ export function environmentStatusJson(status) {
134
+ return {
135
+ schemaVersion: 1,
136
+ command: 'status',
137
+ ok: isStatusHealthy(status),
138
+ status,
139
+ };
140
+ }
126
141
  export async function getEnvironmentStatus(target) {
127
142
  const metadataFullPath = join(target, markerPath);
128
143
  if (!(await pathExists(metadataFullPath))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/odoo",
3
- "version": "0.8.64",
3
+ "version": "0.8.65",
4
4
  "description": "WPMoo Odoo lifecycle tooling for development, staging, and production workflows.",
5
5
  "type": "module",
6
6
  "repository": {