@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/cli.js +54 -3
- package/dist/cockpit/daily-prompts.js +15 -1
- package/dist/daily-actions.js +51 -10
- package/dist/databases.js +24 -0
- package/dist/help.js +2 -2
- package/dist/module-manifest.js +298 -0
- package/dist/module-quality.js +213 -19
- package/dist/module-target-resolver.js +91 -0
- package/dist/safe-reset.js +244 -17
- package/dist/scaffold.js +2 -1
- package/dist/service-runtime-status.js +65 -3
- package/dist/templates.js +110 -8
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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\`
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
|
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: '
|
|
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\`
|
|
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
|
`;
|