@wpmoo/toolkit 0.9.23 → 0.9.25

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/scaffold.js CHANGED
@@ -4,7 +4,7 @@ import { applyExternalAsset, renderExternalAssetCommand, writeTextFile } from '.
4
4
  import { markerPath, renderEnvironmentMetadata } from './environment.js';
5
5
  import { plannedExternalAssetOptions, renderComposeEnvExample } from './external-templates.js';
6
6
  import { cloneRepository, ensureSubmodule, ensureRemoteHasBranch, realGit, stageAll, syncSubmodules, } from './git.js';
7
- import { renderAgents, renderAppstoreRelease, renderGitignore, renderMooDelegationScript, renderPlaceholder, renderReadme, renderStatusScript, } from './templates.js';
7
+ import { renderAgents, renderAppstoreRelease, renderDoctorScript, renderGitignore, renderMooDelegationScript, renderPlaceholder, renderReadme, renderStatusScript, } from './templates.js';
8
8
  import { validateAddonName, validateRepoPath } from './path-validation.js';
9
9
  import { renderSourceManifest, sourceManifestEntriesFromMetadata } from './source-manifest.js';
10
10
  function validateSourceRepo(repo) {
@@ -58,6 +58,7 @@ export function generatedFiles(options) {
58
58
  { path: markerPath, content: renderEnvironmentMetadata(safeOptions) },
59
59
  { path: 'moo', content: renderMooDelegationScript(), mode: 0o755 },
60
60
  { path: 'scripts/status.sh', content: renderStatusScript(), mode: 0o755 },
61
+ { path: 'scripts/doctor.sh', content: renderDoctorScript(), mode: 0o755 },
61
62
  { path: '.gitignore', content: renderGitignore() },
62
63
  { path: 'README.md', content: renderReadme(safeOptions) },
63
64
  { path: 'AGENTS.md', content: renderAgents(safeOptions) },
@@ -1,4 +1,6 @@
1
1
  import { execFile } from 'node:child_process';
2
+ const odooHttpReadyUrl = 'http://127.0.0.1:8069';
3
+ const odooHttpProbeTimeoutMs = 1_000;
2
4
  function run(command, args, options) {
3
5
  return new Promise((resolve, reject) => {
4
6
  execFile(command, args, { cwd: options.cwd }, (error, stdout) => {
@@ -10,11 +12,36 @@ function run(command, args, options) {
10
12
  });
11
13
  });
12
14
  }
15
+ async function fetchOdooHttpReady() {
16
+ const controller = new AbortController();
17
+ const timeout = setTimeout(() => controller.abort(), odooHttpProbeTimeoutMs);
18
+ try {
19
+ return await fetch(odooHttpReadyUrl, { signal: controller.signal });
20
+ }
21
+ finally {
22
+ clearTimeout(timeout);
23
+ }
24
+ }
13
25
  export function renderServiceRuntimeStatusLine(status) {
14
- if (status.kind === 'running')
26
+ if (status.kind === 'running' ||
27
+ status.kind === 'services-running' ||
28
+ status.kind === 'odoo-not-ready' ||
29
+ status.kind === 'fully-ready' ||
30
+ status.kind === 'db-ready') {
31
+ if (status.kind === 'db-ready') {
32
+ return 'Status: ● DB ready';
33
+ }
34
+ if (status.kind === 'odoo-not-ready') {
35
+ return 'Status: ● Odoo not ready';
36
+ }
37
+ if (status.kind === 'fully-ready') {
38
+ return 'Status: ● Fully ready';
39
+ }
15
40
  return 'Status: ● Services running';
16
- if (status.kind === 'docker-not-running')
41
+ }
42
+ if (status.kind === 'docker-not-running') {
17
43
  return 'Status: ● Docker not running';
44
+ }
18
45
  return 'Status: ● Services stopped';
19
46
  }
20
47
  export async function getServiceRuntimeStatus(target, environmentStatus, runner = run) {
@@ -44,5 +71,40 @@ export async function getServiceRuntimeStatus(target, environmentStatus, runner
44
71
  catch {
45
72
  return { kind: 'stopped' };
46
73
  }
47
- return result.stdout.trim() ? { kind: 'running' } : { kind: 'stopped' };
74
+ if (!result.stdout.trim()) {
75
+ return { kind: 'stopped' };
76
+ }
77
+ const runningServices = result.stdout
78
+ .trim()
79
+ .split('\n')
80
+ .map((service) => service.trim())
81
+ .filter(Boolean);
82
+ if (!runningServices.includes('db')) {
83
+ return { kind: 'services-running' };
84
+ }
85
+ const dbProbeArgs = [
86
+ 'compose',
87
+ ...environmentStatus.composeFiles.flatMap((file) => ['-f', file]),
88
+ 'exec',
89
+ '-T',
90
+ 'db',
91
+ 'pg_isready',
92
+ '-U',
93
+ 'odoo',
94
+ '-d',
95
+ 'postgres',
96
+ ];
97
+ try {
98
+ await runner('docker', dbProbeArgs, { cwd: target });
99
+ }
100
+ catch {
101
+ return { kind: 'services-running' };
102
+ }
103
+ try {
104
+ const response = await fetchOdooHttpReady();
105
+ return response.ok ? { kind: 'fully-ready' } : { kind: 'odoo-not-ready' };
106
+ }
107
+ catch {
108
+ return { kind: 'db-ready' };
109
+ }
48
110
  }
package/dist/templates.js CHANGED
@@ -226,8 +226,8 @@ exposes them through \`/mnt/wpmoo-addons\`.
226
226
  \`./moo\` routes day-to-day service and module workflows to local scripts in
227
227
  \`./scripts/\` (for example \`start\`, \`logs\`, \`update\`, \`test\`, \`snapshot\`).
228
228
  \`./moo status\` runs local offline metadata checks without needing network access.
229
- \`./moo doctor\` remains the package fallback command and runs via
230
- \`npx --yes ${fallbackPackageSpec()} doctor\`.
229
+ \`./moo doctor\` runs local checks first and uses the package fallback only for
230
+ advanced usage (for example \`--help\`) via \`npx --yes ${fallbackPackageSpec()} doctor\`.
231
231
 
232
232
  ### Start And Inspect Services
233
233
 
@@ -449,7 +449,7 @@ usage() {
449
449
  case "$1" in
450
450
  "start") echo "Usage: ./moo start" ;;
451
451
  "stop") echo "Usage: ./moo stop" ;;
452
- "logs") echo "Usage: ./moo logs [service]" ;;
452
+ "logs") echo "Usage: ./moo logs [service] [tail-lines]" ;;
453
453
  "restart") echo "Usage: ./moo restart" ;;
454
454
  "shell") echo "Usage: ./moo shell" ;;
455
455
  "psql") echo "Usage: ./moo psql [db]" ;;
@@ -657,7 +657,14 @@ case "$command" in
657
657
  fi
658
658
  run_package_command "$command" "$@"
659
659
  ;;
660
- "create"|"add-repo"|"remove-repo"|"add-module"|"remove-module"|"source"|"reset"|"doctor")
660
+ "doctor")
661
+ shift
662
+ if [[ "$#" -eq 0 && -x ./scripts/doctor.sh ]]; then
663
+ run_script ./scripts/doctor.sh
664
+ fi
665
+ run_package_command "$command" "$@"
666
+ ;;
667
+ "create"|"add-repo"|"remove-repo"|"add-module"|"remove-module"|"source"|"reset")
661
668
  run_package_command "$@"
662
669
  ;;
663
670
  "start")
@@ -672,7 +679,17 @@ case "$command" in
672
679
  ;;
673
680
  "logs")
674
681
  shift
675
- service="$(optional_single_arg "$command" "odoo" "$@")"
682
+ if [[ "$#" -gt 2 || "\${1:-}" == -* || "\${2:-}" == -* ]]; then
683
+ fail_usage "$command"
684
+ fi
685
+ service="\${1:-odoo}"
686
+ if [[ "$#" -eq 2 ]]; then
687
+ if [[ ! "$2" =~ ^[1-9][0-9]*$ ]]; then
688
+ echo "Invalid logs tail count: expected a positive integer." >&2
689
+ exit 2
690
+ fi
691
+ run_script ./scripts/logs.sh "$service" "$2"
692
+ fi
676
693
  run_script ./scripts/logs.sh "$service"
677
694
  ;;
678
695
  "restart")
@@ -753,6 +770,91 @@ case "$command" in
753
770
  esac
754
771
  `;
755
772
  }
773
+ export function renderDoctorScript() {
774
+ return `#!/usr/bin/env bash
775
+ set -euo pipefail
776
+
777
+ script_dir="$(cd -- "$(dirname -- "\${BASH_SOURCE[0]}")" && pwd)"
778
+ root_dir="$(cd -- "$script_dir/.." && pwd)"
779
+ cd "$root_dir"
780
+
781
+ echo "WPMoo doctor"
782
+
783
+ issues=()
784
+ warnings=()
785
+
786
+ required_files=(
787
+ "moo"
788
+ )
789
+
790
+ required_scripts=(
791
+ "up.sh"
792
+ "down.sh"
793
+ "logs.sh"
794
+ "restart.sh"
795
+ "shell.sh"
796
+ "psql.sh"
797
+ "install.sh"
798
+ "update.sh"
799
+ "test.sh"
800
+ "resetdb.sh"
801
+ "snapshot.sh"
802
+ "restore-snapshot.sh"
803
+ "lint.sh"
804
+ "pot.sh"
805
+ "status.sh"
806
+ )
807
+
808
+ for file in "\${required_files[@]}"; do
809
+ if [[ ! -f "$file" ]]; then
810
+ issues+=("missing required file: $file")
811
+ fi
812
+ done
813
+
814
+ for script in "\${required_scripts[@]}"; do
815
+ script_path="scripts/$script"
816
+ if [[ ! -f "$script_path" ]]; then
817
+ issues+=("missing required script: $script_path")
818
+ continue
819
+ fi
820
+ if [[ ! -x "$script_path" ]]; then
821
+ issues+=("not executable: $script_path")
822
+ fi
823
+ done
824
+
825
+ if [[ ! -d scripts ]]; then
826
+ issues+=("missing scripts directory")
827
+ fi
828
+
829
+ if [[ ! -d odoo/custom/src ]]; then
830
+ warnings+=("odoo/custom/src is missing; add source repositories before running module workflows.")
831
+ fi
832
+
833
+ if [[ ! -f .wpmoo/odoo.json ]]; then
834
+ warnings+=("missing .wpmoo/odoo.json; run ./moo reset to initialize environment metadata.")
835
+ fi
836
+
837
+ if (( \${#issues[@]} > 0 )); then
838
+ echo "Doctor checks found issues."
839
+ for issue in "\${issues[@]}"; do
840
+ echo " - $issue"
841
+ done
842
+ if (( \${#warnings[@]} > 0 )); then
843
+ for warning in "\${warnings[@]}"; do
844
+ echo " - warning: $warning"
845
+ done
846
+ fi
847
+ exit 1
848
+ fi
849
+
850
+ echo "Doctor checks passed."
851
+ if (( \${#warnings[@]} > 0 )); then
852
+ for warning in "\${warnings[@]}"; do
853
+ echo " - warning: $warning"
854
+ done
855
+ fi
856
+ `;
857
+ }
756
858
  export function renderStatusScript() {
757
859
  return `#!/usr/bin/env bash
758
860
  set -euo pipefail
@@ -828,7 +930,7 @@ function emptyModuleQuality() {
828
930
  }
829
931
 
830
932
  function manifestIsInstallable(content) {
831
- return /["']installable["']\\s*:\\s*(?:True|true)\\b/.test(content);
933
+ return !/["']installable["']\\s*:\\s*(?:False|false)\\b/.test(content);
832
934
  }
833
935
 
834
936
  function menuXmlHasAction(content, moduleName) {
@@ -867,7 +969,7 @@ async function analyzeModule(modulePath) {
867
969
  issues.push({
868
970
  moduleName,
869
971
  path: moduleRelativePath,
870
- issue: 'missing installable=True in __manifest__.py',
972
+ issue: 'installable is false in __manifest__.py',
871
973
  });
872
974
  }
873
975
 
@@ -1360,7 +1462,7 @@ Useful maintenance commands:
1360
1462
  Daily script delegation vs package fallback:
1361
1463
  - \`./moo start\`, \`logs\`, \`install\`, \`update\`, \`test\`, \`snapshot\`, and related runtime tasks delegate to local \`./scripts/*.sh\`.
1362
1464
  - \`./moo status\` runs local offline metadata checks through \`./scripts/status.sh\`.
1363
- - \`./moo doctor\` remains a package fallback command routed to \`npx --yes ${fallbackPackageSpec()} doctor\`.
1465
+ - \`./moo doctor\` runs local checks first and uses package fallback for advanced usage, routed via \`npx --yes ${fallbackPackageSpec()} doctor\`.
1364
1466
 
1365
1467
  Only report completion after the relevant update/test/lint command exits cleanly.
1366
1468
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/toolkit",
3
- "version": "0.9.23",
3
+ "version": "0.9.25",
4
4
  "description": "WPMoo Toolkit for development, staging, and production lifecycle workflows.",
5
5
  "type": "module",
6
6
  "repository": {