@wpmoo/toolkit 0.9.24 → 0.9.26

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.
@@ -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]" ;;
@@ -601,6 +601,62 @@ allow_stage_lifecycle() {
601
601
  [[ "$value" == "1" ]]
602
602
  }
603
603
 
604
+ allow_no_recent_snapshot() {
605
+ local value="\${WPMOO_ALLOW_NO_RECENT_SNAPSHOT:-$(env_file_value WPMOO_ALLOW_NO_RECENT_SNAPSHOT)}"
606
+ [[ "$value" == "1" ]]
607
+ }
608
+
609
+ allow_migrations() {
610
+ local value="\${WPMOO_ALLOW_MIGRATIONS:-$(env_file_value WPMOO_ALLOW_MIGRATIONS)}"
611
+ [[ "$value" == "1" ]]
612
+ }
613
+
614
+ has_recent_snapshot() {
615
+ local dir
616
+ for dir in backups backup snapshots; do
617
+ [[ -d "$dir" ]] || continue
618
+ if find "$dir" -type f \\( -name "*.dump" -o -name "*.sql" -o -name "*.sql.gz" -o -name "*.zip" -o -name "*.tar" -o -name "*.tar.gz" \\) -mtime -1 -print -quit 2>/dev/null | grep -q .; then
619
+ return 0
620
+ fi
621
+ done
622
+ return 1
623
+ }
624
+
625
+ require_recent_snapshot_or_override() {
626
+ local command="$1"
627
+ local env_name
628
+ env_name="$(selected_env)"
629
+ if [[ "$env_name" == "stage" || "$env_name" == "prod" ]]; then
630
+ if ! allow_no_recent_snapshot && ! has_recent_snapshot; then
631
+ echo "Refusing destructive command '$command' in WPMOO_ENV=$env_name without a recent database snapshot. Create a snapshot first or set WPMOO_ALLOW_NO_RECENT_SNAPSHOT=1 to run it intentionally." >&2
632
+ exit 1
633
+ fi
634
+ fi
635
+ }
636
+
637
+ has_migration_risk() {
638
+ local base
639
+ for base in odoo/custom/src/private odoo/custom/src/oca odoo/custom/src/external; do
640
+ [[ -d "$base" ]] || continue
641
+ if find "$base" -type f \\( -path "*/migrations/*/pre-migration.py" -o -path "*/migrations/*/post-migration.py" -o -path "*/migrations/*/end-migration.py" -o -path "*/migration/*/pre-migration.py" -o -path "*/migration/*/post-migration.py" -o -path "*/migration/*/end-migration.py" -o -path "*/scripts/migrate.py" -o -path "*/scripts/migration.py" \\) -print -quit 2>/dev/null | grep -q .; then
642
+ return 0
643
+ fi
644
+ done
645
+ return 1
646
+ }
647
+
648
+ require_migrations_allowed() {
649
+ local command="$1"
650
+ local env_name
651
+ env_name="$(selected_env)"
652
+ if [[ "$env_name" == "stage" || "$env_name" == "prod" ]]; then
653
+ if ! allow_migrations && has_migration_risk; then
654
+ echo "Refusing migration-risk command '$command' in WPMOO_ENV=$env_name. Review detected migration scripts or set WPMOO_ALLOW_MIGRATIONS=1 to run it intentionally." >&2
655
+ exit 1
656
+ fi
657
+ fi
658
+ }
659
+
604
660
  require_stage_lifecycle_allowed() {
605
661
  local command="$1"
606
662
  local env_name
@@ -657,7 +713,14 @@ case "$command" in
657
713
  fi
658
714
  run_package_command "$command" "$@"
659
715
  ;;
660
- "create"|"add-repo"|"remove-repo"|"add-module"|"remove-module"|"source"|"reset"|"doctor")
716
+ "doctor")
717
+ shift
718
+ if [[ "$#" -eq 0 && -x ./scripts/doctor.sh ]]; then
719
+ run_script ./scripts/doctor.sh
720
+ fi
721
+ run_package_command "$command" "$@"
722
+ ;;
723
+ "create"|"add-repo"|"remove-repo"|"add-module"|"remove-module"|"source"|"reset")
661
724
  run_package_command "$@"
662
725
  ;;
663
726
  "start")
@@ -672,7 +735,17 @@ case "$command" in
672
735
  ;;
673
736
  "logs")
674
737
  shift
675
- service="$(optional_single_arg "$command" "odoo" "$@")"
738
+ if [[ "$#" -gt 2 || "\${1:-}" == -* || "\${2:-}" == -* ]]; then
739
+ fail_usage "$command"
740
+ fi
741
+ service="\${1:-odoo}"
742
+ if [[ "$#" -eq 2 ]]; then
743
+ if [[ ! "$2" =~ ^[1-9][0-9]*$ ]]; then
744
+ echo "Invalid logs tail count: expected a positive integer." >&2
745
+ exit 2
746
+ fi
747
+ run_script ./scripts/logs.sh "$service" "$2"
748
+ fi
676
749
  run_script ./scripts/logs.sh "$service"
677
750
  ;;
678
751
  "restart")
@@ -695,6 +768,7 @@ case "$command" in
695
768
  require_module_args "$command" "$@"
696
769
  require_stage_lifecycle_allowed "$command"
697
770
  require_prod_lifecycle_allowed "$command"
771
+ require_migrations_allowed "$command"
698
772
  run_script ./scripts/install.sh "$@"
699
773
  ;;
700
774
  "update")
@@ -702,18 +776,21 @@ case "$command" in
702
776
  require_module_args "$command" "$@"
703
777
  require_stage_lifecycle_allowed "$command"
704
778
  require_prod_lifecycle_allowed "$command"
779
+ require_migrations_allowed "$command"
705
780
  run_script ./scripts/update.sh "$@"
706
781
  ;;
707
782
  "test")
708
783
  shift
709
784
  validate_test_args "$@"
710
785
  require_prod_lifecycle_allowed "$command"
786
+ require_migrations_allowed "$command"
711
787
  run_script ./scripts/test.sh "$@"
712
788
  ;;
713
789
  "resetdb")
714
790
  shift
715
791
  positional_args "$command" 0 2 "$@"
716
792
  require_destructive_allowed "$command"
793
+ require_recent_snapshot_or_override "$command"
717
794
  run_script ./scripts/resetdb.sh "$@"
718
795
  ;;
719
796
  "snapshot")
@@ -732,6 +809,7 @@ case "$command" in
732
809
  restore_args+=("$@")
733
810
  if [[ "\${restore_args[0]:-}" != "--dry-run" ]]; then
734
811
  require_destructive_allowed "$command"
812
+ require_recent_snapshot_or_override "$command"
735
813
  fi
736
814
  run_script ./scripts/restore-snapshot.sh "\${restore_args[@]}"
737
815
  ;;
@@ -753,6 +831,91 @@ case "$command" in
753
831
  esac
754
832
  `;
755
833
  }
834
+ export function renderDoctorScript() {
835
+ return `#!/usr/bin/env bash
836
+ set -euo pipefail
837
+
838
+ script_dir="$(cd -- "$(dirname -- "\${BASH_SOURCE[0]}")" && pwd)"
839
+ root_dir="$(cd -- "$script_dir/.." && pwd)"
840
+ cd "$root_dir"
841
+
842
+ echo "WPMoo doctor"
843
+
844
+ issues=()
845
+ warnings=()
846
+
847
+ required_files=(
848
+ "moo"
849
+ )
850
+
851
+ required_scripts=(
852
+ "up.sh"
853
+ "down.sh"
854
+ "logs.sh"
855
+ "restart.sh"
856
+ "shell.sh"
857
+ "psql.sh"
858
+ "install.sh"
859
+ "update.sh"
860
+ "test.sh"
861
+ "resetdb.sh"
862
+ "snapshot.sh"
863
+ "restore-snapshot.sh"
864
+ "lint.sh"
865
+ "pot.sh"
866
+ "status.sh"
867
+ )
868
+
869
+ for file in "\${required_files[@]}"; do
870
+ if [[ ! -f "$file" ]]; then
871
+ issues+=("missing required file: $file")
872
+ fi
873
+ done
874
+
875
+ for script in "\${required_scripts[@]}"; do
876
+ script_path="scripts/$script"
877
+ if [[ ! -f "$script_path" ]]; then
878
+ issues+=("missing required script: $script_path")
879
+ continue
880
+ fi
881
+ if [[ ! -x "$script_path" ]]; then
882
+ issues+=("not executable: $script_path")
883
+ fi
884
+ done
885
+
886
+ if [[ ! -d scripts ]]; then
887
+ issues+=("missing scripts directory")
888
+ fi
889
+
890
+ if [[ ! -d odoo/custom/src ]]; then
891
+ warnings+=("odoo/custom/src is missing; add source repositories before running module workflows.")
892
+ fi
893
+
894
+ if [[ ! -f .wpmoo/odoo.json ]]; then
895
+ warnings+=("missing .wpmoo/odoo.json; run ./moo reset to initialize environment metadata.")
896
+ fi
897
+
898
+ if (( \${#issues[@]} > 0 )); then
899
+ echo "Doctor checks found issues."
900
+ for issue in "\${issues[@]}"; do
901
+ echo " - $issue"
902
+ done
903
+ if (( \${#warnings[@]} > 0 )); then
904
+ for warning in "\${warnings[@]}"; do
905
+ echo " - warning: $warning"
906
+ done
907
+ fi
908
+ exit 1
909
+ fi
910
+
911
+ echo "Doctor checks passed."
912
+ if (( \${#warnings[@]} > 0 )); then
913
+ for warning in "\${warnings[@]}"; do
914
+ echo " - warning: $warning"
915
+ done
916
+ fi
917
+ `;
918
+ }
756
919
  export function renderStatusScript() {
757
920
  return `#!/usr/bin/env bash
758
921
  set -euo pipefail
@@ -1360,7 +1523,7 @@ Useful maintenance commands:
1360
1523
  Daily script delegation vs package fallback:
1361
1524
  - \`./moo start\`, \`logs\`, \`install\`, \`update\`, \`test\`, \`snapshot\`, and related runtime tasks delegate to local \`./scripts/*.sh\`.
1362
1525
  - \`./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\`.
1526
+ - \`./moo doctor\` runs local checks first and uses package fallback for advanced usage, routed via \`npx --yes ${fallbackPackageSpec()} doctor\`.
1364
1527
 
1365
1528
  Only report completion after the relevant update/test/lint command exits cleanly.
1366
1529
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/toolkit",
3
- "version": "0.9.24",
3
+ "version": "0.9.26",
4
4
  "description": "WPMoo Toolkit for development, staging, and production lifecycle workflows.",
5
5
  "type": "module",
6
6
  "repository": {