@wpmoo/toolkit 0.9.0
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/LICENSE +22 -0
- package/README.md +519 -0
- package/dist/addons-yaml.js +59 -0
- package/dist/args.js +259 -0
- package/dist/cli.js +1039 -0
- package/dist/cockpit/command-palette.js +23 -0
- package/dist/cockpit/command-registry.js +91 -0
- package/dist/cockpit/daily-prompts.js +177 -0
- package/dist/cockpit/menu.js +99 -0
- package/dist/cockpit/safety.js +22 -0
- package/dist/compose-layout.js +118 -0
- package/dist/daily-actions.js +190 -0
- package/dist/doctor.js +519 -0
- package/dist/environment-context.js +10 -0
- package/dist/environment-version.js +5 -0
- package/dist/environment.js +136 -0
- package/dist/external-assets.js +153 -0
- package/dist/external-templates.js +86 -0
- package/dist/git.js +98 -0
- package/dist/github.js +87 -0
- package/dist/help.js +157 -0
- package/dist/menu-navigation.js +67 -0
- package/dist/module-actions.js +114 -0
- package/dist/odoo-versions.js +1 -0
- package/dist/path-validation.js +50 -0
- package/dist/prompt-copy.js +8 -0
- package/dist/prompt-repositories.js +34 -0
- package/dist/prompts/index.js +174 -0
- package/dist/repo-actions.js +158 -0
- package/dist/repo-url.js +27 -0
- package/dist/repository-preflight.js +46 -0
- package/dist/safe-reset.js +217 -0
- package/dist/scaffold.js +161 -0
- package/dist/source-actions.js +65 -0
- package/dist/source-manifest.js +338 -0
- package/dist/status.js +239 -0
- package/dist/templates.js +758 -0
- package/dist/types.js +1 -0
- package/dist/update-check.js +106 -0
- package/dist/version.js +19 -0
- package/docs/assets/patreon-donate.png +0 -0
- package/docs/assets/wpmoo-banner.png +0 -0
- package/docs/external-resources.md +136 -0
- package/docs/generated-environment-verification.md +140 -0
- package/docs/handoff.md +29 -0
- package/package.json +65 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import { packageName, packageVersion } from './version.js';
|
|
2
|
+
function fallbackPackageSpec() {
|
|
3
|
+
return `${packageName()}@${packageVersion()}`;
|
|
4
|
+
}
|
|
5
|
+
export function defaultCommunityAddons(product) {
|
|
6
|
+
return [product];
|
|
7
|
+
}
|
|
8
|
+
export function defaultProAddons(product) {
|
|
9
|
+
return [`${product}_pro`];
|
|
10
|
+
}
|
|
11
|
+
function titleizeProduct(product) {
|
|
12
|
+
return product
|
|
13
|
+
.split(/[_-]+/)
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
16
|
+
.join(' ');
|
|
17
|
+
}
|
|
18
|
+
function yamlList(items) {
|
|
19
|
+
return items.map((item) => ` - ${item}`).join('\n');
|
|
20
|
+
}
|
|
21
|
+
function allAddons(options) {
|
|
22
|
+
return options.sourceRepos.flatMap((repo) => repo.addons);
|
|
23
|
+
}
|
|
24
|
+
function hasSourceRepos(options) {
|
|
25
|
+
return options.sourceRepos.length > 0;
|
|
26
|
+
}
|
|
27
|
+
function sourceTypeOf(repo) {
|
|
28
|
+
return repo.sourceType ?? 'private';
|
|
29
|
+
}
|
|
30
|
+
function sourceRepoRelativePath(repo) {
|
|
31
|
+
return `odoo/custom/src/${sourceTypeOf(repo)}/${repo.path}`;
|
|
32
|
+
}
|
|
33
|
+
function repositoryLayout(options) {
|
|
34
|
+
const sourceRepoRows = hasSourceRepos(options)
|
|
35
|
+
? options.sourceRepos
|
|
36
|
+
.map((repo, index) => {
|
|
37
|
+
const connector = index === options.sourceRepos.length - 1 ? '└──' : '├──';
|
|
38
|
+
return `│ │ │ ${connector} ${repo.path}/ # Project-owned addon source repository`;
|
|
39
|
+
})
|
|
40
|
+
.join('\n')
|
|
41
|
+
: '│ │ │ └── (add project-owned repos with ./moo add-repo)';
|
|
42
|
+
return `${options.devRepo}/ # Development environment root
|
|
43
|
+
├── compose.yaml # Base Docker Compose file
|
|
44
|
+
├── compose/ # Compose overlays for each workflow
|
|
45
|
+
│ ├── dev.yaml # Local development services
|
|
46
|
+
│ ├── debug.yaml # Debug tooling and debug-friendly settings
|
|
47
|
+
│ ├── test.yaml # Test runner services and test database setup
|
|
48
|
+
│ ├── stage.yaml # Staging-like deployment overlay
|
|
49
|
+
│ ├── prod.yaml # Production deployment overlay
|
|
50
|
+
│ ├── proxy.yaml # Reverse proxy / edge routing overlay
|
|
51
|
+
│ └── tools.yaml # Optional maintenance and helper tools
|
|
52
|
+
├── config/ # Runtime configuration mounted into containers
|
|
53
|
+
│ ├── odoo/ # Odoo server configuration
|
|
54
|
+
│ │ ├── odoo.conf # Main Odoo configuration file
|
|
55
|
+
│ │ └── requirements.txt # Extra Python dependencies for the Odoo container
|
|
56
|
+
│ └── logrotate/ # Log rotation configuration
|
|
57
|
+
│ └── odoo # Logrotate rules for Odoo logs
|
|
58
|
+
├── resources/ # Container-side helper resources
|
|
59
|
+
│ └── odoo/ # Resources specific to the Odoo service
|
|
60
|
+
│ └── entrypoint.sh # Container startup script that discovers addons
|
|
61
|
+
├── moo # Local command hub shortcut
|
|
62
|
+
├── scripts/ # Shell scripts used by the local command hub
|
|
63
|
+
├── odoo/ # Odoo workspace data and custom source tree
|
|
64
|
+
│ └── custom/ # Custom addon layer for this environment
|
|
65
|
+
│ ├── src/ # Source repository checkout root
|
|
66
|
+
│ │ ├── private/ # Project-owned/private addon repositories
|
|
67
|
+
${sourceRepoRows}
|
|
68
|
+
│ │ ├── oca/ # OCA addon repositories
|
|
69
|
+
│ │ └── external/ # Non-OCA third-party addon repositories
|
|
70
|
+
│ ├── patches/ # Local patches for upstream repositories
|
|
71
|
+
│ └── manifests/ # Source manifests, locks, and pinned revisions
|
|
72
|
+
├── docs/ # Project-specific documentation
|
|
73
|
+
│ ├── appstore-release.md # Odoo App Store release checklist and notes
|
|
74
|
+
│ └── compose.md # Compose layout and operations reference
|
|
75
|
+
├── .env.example # Template for local environment variables
|
|
76
|
+
├── README.md # This environment overview
|
|
77
|
+
└── AGENTS.md # Agent instructions for this environment`;
|
|
78
|
+
}
|
|
79
|
+
function sourceRepoDocs(options) {
|
|
80
|
+
if (!hasSourceRepos(options)) {
|
|
81
|
+
return `This environment was scaffolded without source repository submodules.
|
|
82
|
+
Add source repositories later from the cockpit or with \`npx @wpmoo/toolkit add-repo\`.
|
|
83
|
+
They can be organized under:
|
|
84
|
+
|
|
85
|
+
\`odoo/custom/src/private\` for project-owned/private addon repositories,
|
|
86
|
+
\`odoo/custom/src/oca\` for OCA repositories, and
|
|
87
|
+
\`odoo/custom/src/external\` for non-OCA third-party repositories.
|
|
88
|
+
|
|
89
|
+
Pinned external manifests and local patches should live under
|
|
90
|
+
\`odoo/custom/manifests\` and \`odoo/custom/patches\` respectively.`;
|
|
91
|
+
}
|
|
92
|
+
return options.sourceRepos
|
|
93
|
+
.map((repo) => `### ${repo.path}
|
|
94
|
+
|
|
95
|
+
URL:
|
|
96
|
+
|
|
97
|
+
\`\`\`text
|
|
98
|
+
${repo.url}
|
|
99
|
+
\`\`\`
|
|
100
|
+
|
|
101
|
+
Submodule path:
|
|
102
|
+
|
|
103
|
+
\`\`\`text
|
|
104
|
+
${sourceRepoRelativePath(repo)}
|
|
105
|
+
\`\`\`
|
|
106
|
+
|
|
107
|
+
Source manifest entry:
|
|
108
|
+
|
|
109
|
+
\`\`\`text
|
|
110
|
+
odoo/custom/manifests/sources.yaml
|
|
111
|
+
\`\`\`
|
|
112
|
+
|
|
113
|
+
Expected addon layout:
|
|
114
|
+
|
|
115
|
+
\`\`\`text
|
|
116
|
+
${repo.path}/
|
|
117
|
+
${repo.addons.map((addon) => `├── ${addon}/`).join('\n')}
|
|
118
|
+
\`\`\``)
|
|
119
|
+
.join('\n\n');
|
|
120
|
+
}
|
|
121
|
+
function cloneDocs(options) {
|
|
122
|
+
if (!hasSourceRepos(options)) {
|
|
123
|
+
return `## Local Folder
|
|
124
|
+
|
|
125
|
+
This environment is ready in this folder:
|
|
126
|
+
|
|
127
|
+
\`\`\`bash
|
|
128
|
+
cd ${options.devRepo}
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
If you later connect it to Git, commit the generated files after reviewing them.
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
return `## Clone
|
|
135
|
+
|
|
136
|
+
Clone with submodules:
|
|
137
|
+
|
|
138
|
+
\`\`\`bash
|
|
139
|
+
git clone --recurse-submodules ${options.devRepoUrl}
|
|
140
|
+
cd ${options.devRepo}
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
If already cloned:
|
|
144
|
+
|
|
145
|
+
\`\`\`bash
|
|
146
|
+
git submodule update --init --recursive
|
|
147
|
+
\`\`\`
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
function optionalAgentSkillsReadme(options) {
|
|
151
|
+
if (!options.agentSkillsTemplateUrl)
|
|
152
|
+
return '';
|
|
153
|
+
return `
|
|
154
|
+
## Agent Skills
|
|
155
|
+
|
|
156
|
+
This environment is configured to install project-local Agent Skills from:
|
|
157
|
+
|
|
158
|
+
\`\`\`text
|
|
159
|
+
${options.agentSkillsTemplateUrl}${options.agentSkillsTemplateRef ? `#${options.agentSkillsTemplateRef}` : ''}
|
|
160
|
+
\`\`\`
|
|
161
|
+
|
|
162
|
+
After external resource installation, skills normally live under:
|
|
163
|
+
|
|
164
|
+
\`\`\`text
|
|
165
|
+
.agents/skills/
|
|
166
|
+
\`\`\`
|
|
167
|
+
|
|
168
|
+
Agents that support the Agent Skills standard can load them on demand.
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
function optionalAgentSkillsAgentsSection(options) {
|
|
172
|
+
if (!options.agentSkillsTemplateUrl)
|
|
173
|
+
return '';
|
|
174
|
+
return `
|
|
175
|
+
## Active Agent Skills
|
|
176
|
+
|
|
177
|
+
When using an agent that supports Agent Skills, prefer the project-local skills
|
|
178
|
+
installed under \`.agents/skills/\`. They are sourced from:
|
|
179
|
+
|
|
180
|
+
\`\`\`text
|
|
181
|
+
${options.agentSkillsTemplateUrl}${options.agentSkillsTemplateRef ? `#${options.agentSkillsTemplateRef}` : ''}
|
|
182
|
+
\`\`\`
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
function environmentKind() {
|
|
186
|
+
return 'Docker Compose';
|
|
187
|
+
}
|
|
188
|
+
function repoDuplicationNote() {
|
|
189
|
+
return 'Keep source repositories under the relevant source directory (`private`, `oca`, or `external`); the Compose entrypoint exposes discovered addons through `/mnt/wpmoo-addons`.';
|
|
190
|
+
}
|
|
191
|
+
function verificationCommand(options) {
|
|
192
|
+
const firstAddon = allAddons(options)[0] ?? options.product;
|
|
193
|
+
return `./moo test ${firstAddon}`;
|
|
194
|
+
}
|
|
195
|
+
function environmentUsageDocs(options) {
|
|
196
|
+
return `## Docker Compose Notes
|
|
197
|
+
|
|
198
|
+
This environment uses the compact WPMoo Compose layout:
|
|
199
|
+
|
|
200
|
+
\`\`\`text
|
|
201
|
+
compose.yaml
|
|
202
|
+
compose/dev.yaml
|
|
203
|
+
compose/stage.yaml
|
|
204
|
+
compose/prod.yaml
|
|
205
|
+
config/odoo/odoo.conf
|
|
206
|
+
resources/odoo/entrypoint.sh
|
|
207
|
+
\`\`\`
|
|
208
|
+
|
|
209
|
+
Development uses compose.yaml plus compose/dev.yaml by default.
|
|
210
|
+
Set WPMOO_ENV=stage or WPMOO_ENV=prod only after providing production-grade secrets and volumes.
|
|
211
|
+
|
|
212
|
+
If copied from the standalone resource, additional compose notes are in
|
|
213
|
+
\`docs/compose.md\`.
|
|
214
|
+
|
|
215
|
+
Source repositories stay under \`odoo/custom/src/{private,oca,external}\` when
|
|
216
|
+
configured. At
|
|
217
|
+
container startup, \`entrypoint.sh\` scans those repositories for addons and
|
|
218
|
+
exposes them through \`/mnt/wpmoo-addons\`.
|
|
219
|
+
|
|
220
|
+
## Daily Command Hub (\`./moo\`)
|
|
221
|
+
|
|
222
|
+
\`./moo\` routes day-to-day service and module workflows to local scripts in
|
|
223
|
+
\`./scripts/\` (for example \`start\`, \`logs\`, \`update\`, \`test\`, \`snapshot\`).
|
|
224
|
+
\`./moo status\` and \`./moo doctor\` are package fallback commands that run via
|
|
225
|
+
\`npx --yes ${fallbackPackageSpec()} ...\`.
|
|
226
|
+
|
|
227
|
+
### Start And Inspect Services
|
|
228
|
+
|
|
229
|
+
\`\`\`bash
|
|
230
|
+
cp .env.example .env
|
|
231
|
+
./moo start
|
|
232
|
+
./moo logs odoo
|
|
233
|
+
./moo shell
|
|
234
|
+
./moo psql postgres
|
|
235
|
+
./moo stop
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
### Run, Update, And Test Modules
|
|
239
|
+
|
|
240
|
+
\`\`\`bash
|
|
241
|
+
./moo install ${allAddons(options)[0] ?? options.product}
|
|
242
|
+
./moo update ${allAddons(options)[0] ?? options.product}
|
|
243
|
+
./moo test ${allAddons(options)[0] ?? options.product}
|
|
244
|
+
\`\`\`
|
|
245
|
+
|
|
246
|
+
### Snapshot And Restore
|
|
247
|
+
|
|
248
|
+
\`\`\`bash
|
|
249
|
+
./moo snapshot devel before-update
|
|
250
|
+
./moo restore-snapshot --dry-run before-update devel
|
|
251
|
+
./moo restore-snapshot before-update devel
|
|
252
|
+
\`\`\`
|
|
253
|
+
|
|
254
|
+
### Lint
|
|
255
|
+
|
|
256
|
+
\`\`\`bash
|
|
257
|
+
./moo lint
|
|
258
|
+
\`\`\`
|
|
259
|
+
|
|
260
|
+
### Export Translations
|
|
261
|
+
|
|
262
|
+
\`\`\`bash
|
|
263
|
+
./moo pot ${allAddons(options)[0] ?? options.product} devel i18n/${allAddons(options)[0] ?? options.product}.pot
|
|
264
|
+
\`\`\`
|
|
265
|
+
|
|
266
|
+
### Recover / Reset
|
|
267
|
+
|
|
268
|
+
\`\`\`bash
|
|
269
|
+
./moo doctor
|
|
270
|
+
./moo status
|
|
271
|
+
./moo resetdb devel ${allAddons(options)[0] ?? options.product}
|
|
272
|
+
\`\`\`
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
const BANNER_GRADIENT_START = [31, 151, 231];
|
|
276
|
+
const BANNER_GRADIENT_END = [209, 95, 127];
|
|
277
|
+
const ANSI_BOLD = '\u001B[1m';
|
|
278
|
+
const ANSI_DIM = '\u001B[2m';
|
|
279
|
+
const ANSI_INFO = '\u001B[38;2;139;166;190m';
|
|
280
|
+
const ANSI_RESET = '\u001B[0m';
|
|
281
|
+
const BANNER_DIVIDER_WIDTH = 40;
|
|
282
|
+
function gradientColor(column, width) {
|
|
283
|
+
const ratio = width <= 1 ? 0 : column / (width - 1);
|
|
284
|
+
const [startR, startG, startB] = BANNER_GRADIENT_START;
|
|
285
|
+
const [endR, endG, endB] = BANNER_GRADIENT_END;
|
|
286
|
+
const r = Math.round(startR + (endR - startR) * ratio);
|
|
287
|
+
const g = Math.round(startG + (endG - startG) * ratio);
|
|
288
|
+
const b = Math.round(startB + (endB - startB) * ratio);
|
|
289
|
+
return `\u001B[38;2;${r};${g};${b}m`;
|
|
290
|
+
}
|
|
291
|
+
function applyBannerGradient(banner) {
|
|
292
|
+
const lines = banner.split('\n');
|
|
293
|
+
return lines
|
|
294
|
+
.map((line) => {
|
|
295
|
+
const width = line.length;
|
|
296
|
+
return Array.from(line)
|
|
297
|
+
.map((character, column) => `${gradientColor(column, width)}${character}`)
|
|
298
|
+
.join('');
|
|
299
|
+
})
|
|
300
|
+
.join('\n');
|
|
301
|
+
}
|
|
302
|
+
function renderDimInfo(value) {
|
|
303
|
+
return `${ANSI_DIM}${ANSI_INFO}${value}${ANSI_RESET}`;
|
|
304
|
+
}
|
|
305
|
+
export function renderBanner(details = [], options = {}) {
|
|
306
|
+
const title = `${applyBannerGradient('WPMoo Toolkit')}${options.version ? ` ${renderDimInfo(options.version)}` : ''}`;
|
|
307
|
+
const header = [
|
|
308
|
+
title,
|
|
309
|
+
applyBannerGradient('Workflow Platform · Micro Object Oriented'),
|
|
310
|
+
renderDimInfo('Development, staging, and production workflows for Odoo projects.'),
|
|
311
|
+
applyBannerGradient('─'.repeat(BANNER_DIVIDER_WIDTH)),
|
|
312
|
+
].join('\n');
|
|
313
|
+
const detailsBlock = details.length > 0 ? `\n${details.map((line) => renderDimInfo(line)).join('\n')}` : '';
|
|
314
|
+
return `\n${ANSI_BOLD}${header}${ANSI_RESET}${detailsBlock}`;
|
|
315
|
+
}
|
|
316
|
+
export function renderGitignore() {
|
|
317
|
+
return `# macOS/editor noise
|
|
318
|
+
.DS_Store
|
|
319
|
+
.idea/
|
|
320
|
+
.vscode/*.log
|
|
321
|
+
|
|
322
|
+
# Node/local package files
|
|
323
|
+
node_modules/
|
|
324
|
+
dist/
|
|
325
|
+
coverage/
|
|
326
|
+
*.log
|
|
327
|
+
|
|
328
|
+
# Python/cache files
|
|
329
|
+
__pycache__/
|
|
330
|
+
*.py[cod]
|
|
331
|
+
.pytest_cache/
|
|
332
|
+
.mypy_cache/
|
|
333
|
+
.ruff_cache/
|
|
334
|
+
|
|
335
|
+
# Local environment files
|
|
336
|
+
.env
|
|
337
|
+
.env.*
|
|
338
|
+
!.env.example
|
|
339
|
+
*.local
|
|
340
|
+
|
|
341
|
+
# Local generated files
|
|
342
|
+
*.code-workspace
|
|
343
|
+
addons/
|
|
344
|
+
auto/
|
|
345
|
+
backups/
|
|
346
|
+
data/
|
|
347
|
+
filestore/
|
|
348
|
+
postgresql/
|
|
349
|
+
sessions/
|
|
350
|
+
odoo/custom/auto/
|
|
351
|
+
odoo/custom/src/*/.git-aggregate-cache/
|
|
352
|
+
|
|
353
|
+
# Backups and archives
|
|
354
|
+
*.bak
|
|
355
|
+
*.backup
|
|
356
|
+
*.dump
|
|
357
|
+
*.sql
|
|
358
|
+
*.zip
|
|
359
|
+
*.tar
|
|
360
|
+
*.tar.gz
|
|
361
|
+
`;
|
|
362
|
+
}
|
|
363
|
+
export function renderMooDelegationScript() {
|
|
364
|
+
return `#!/usr/bin/env bash
|
|
365
|
+
set -euo pipefail
|
|
366
|
+
|
|
367
|
+
script_dir="$(cd -- "$(dirname -- "\${BASH_SOURCE[0]}")" && pwd)"
|
|
368
|
+
cd "$script_dir"
|
|
369
|
+
|
|
370
|
+
usage() {
|
|
371
|
+
case "$1" in
|
|
372
|
+
"start") echo "Usage: ./moo start" ;;
|
|
373
|
+
"stop") echo "Usage: ./moo stop" ;;
|
|
374
|
+
"logs") echo "Usage: ./moo logs [service]" ;;
|
|
375
|
+
"restart") echo "Usage: ./moo restart" ;;
|
|
376
|
+
"shell") echo "Usage: ./moo shell" ;;
|
|
377
|
+
"psql") echo "Usage: ./moo psql [db]" ;;
|
|
378
|
+
"install") echo "Usage: ./moo install <module[,module]> [db]" ;;
|
|
379
|
+
"update") echo "Usage: ./moo update <module[,module]> [db]" ;;
|
|
380
|
+
"test") echo "Usage: ./moo test <module[,module]> [--db <db>] [--mode init|update] [--tags <tags>]" ;;
|
|
381
|
+
"resetdb") echo "Usage: ./moo resetdb [db] [module[,module]]" ;;
|
|
382
|
+
"snapshot") echo "Usage: ./moo snapshot [db] [snapshot-name]" ;;
|
|
383
|
+
"restore-snapshot") echo "Usage: ./moo restore-snapshot [--dry-run] <snapshot-name> [db]" ;;
|
|
384
|
+
"lint") echo "Usage: ./moo lint" ;;
|
|
385
|
+
"pot") echo "Usage: ./moo pot <module[,module]> [db] [output]" ;;
|
|
386
|
+
esac
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fail_usage() {
|
|
390
|
+
usage "$1" >&2
|
|
391
|
+
exit 2
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
require_no_args() {
|
|
395
|
+
local command="$1"
|
|
396
|
+
shift
|
|
397
|
+
if [[ "$#" -ne 0 ]]; then
|
|
398
|
+
fail_usage "$command"
|
|
399
|
+
fi
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
optional_single_arg() {
|
|
403
|
+
local command="$1"
|
|
404
|
+
local fallback="$2"
|
|
405
|
+
shift 2
|
|
406
|
+
if [[ "$#" -gt 1 ]]; then
|
|
407
|
+
fail_usage "$command"
|
|
408
|
+
fi
|
|
409
|
+
printf '%s\\n' "\${1:-$fallback}"
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
require_module_args() {
|
|
413
|
+
local command="$1"
|
|
414
|
+
shift
|
|
415
|
+
if [[ "$#" -lt 1 || "\${1:-}" == -* || "$#" -gt 2 ]]; then
|
|
416
|
+
fail_usage "$command"
|
|
417
|
+
fi
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
positional_args() {
|
|
421
|
+
local command="$1"
|
|
422
|
+
local min="$2"
|
|
423
|
+
local max="$3"
|
|
424
|
+
shift 3
|
|
425
|
+
if [[ "$#" -lt "$min" || "$#" -gt "$max" ]]; then
|
|
426
|
+
fail_usage "$command"
|
|
427
|
+
fi
|
|
428
|
+
for arg in "$@"; do
|
|
429
|
+
if [[ "$arg" == -* ]]; then
|
|
430
|
+
fail_usage "$command"
|
|
431
|
+
fi
|
|
432
|
+
done
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
validate_test_args() {
|
|
436
|
+
if [[ "$#" -lt 1 || "\${1:-}" == -* ]]; then
|
|
437
|
+
fail_usage "test"
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
shift
|
|
441
|
+
while [[ "$#" -gt 0 ]]; do
|
|
442
|
+
case "$1" in
|
|
443
|
+
"--db"|"--tags")
|
|
444
|
+
if [[ "$#" -lt 2 || "\${2:-}" == --* ]]; then
|
|
445
|
+
echo "Missing value for $1" >&2
|
|
446
|
+
exit 2
|
|
447
|
+
fi
|
|
448
|
+
shift 2
|
|
449
|
+
;;
|
|
450
|
+
"--mode")
|
|
451
|
+
if [[ "$#" -lt 2 || "\${2:-}" == --* ]]; then
|
|
452
|
+
echo "Missing value for --mode" >&2
|
|
453
|
+
exit 2
|
|
454
|
+
fi
|
|
455
|
+
if [[ "$2" != "init" && "$2" != "update" ]]; then
|
|
456
|
+
echo "Invalid value for --mode: expected init or update" >&2
|
|
457
|
+
exit 2
|
|
458
|
+
fi
|
|
459
|
+
shift 2
|
|
460
|
+
;;
|
|
461
|
+
*)
|
|
462
|
+
echo "Unknown option for ./moo test: $1" >&2
|
|
463
|
+
exit 2
|
|
464
|
+
;;
|
|
465
|
+
esac
|
|
466
|
+
done
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
run_script() {
|
|
470
|
+
local script="$1"
|
|
471
|
+
shift
|
|
472
|
+
if [[ ! -x "$script" ]]; then
|
|
473
|
+
echo "Missing daily action script: \${script#./}" >&2
|
|
474
|
+
exit 1
|
|
475
|
+
fi
|
|
476
|
+
exec "$script" "$@"
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
command="\${1:-}"
|
|
480
|
+
case "$command" in
|
|
481
|
+
"start")
|
|
482
|
+
shift
|
|
483
|
+
require_no_args "$command" "$@"
|
|
484
|
+
run_script ./scripts/up.sh
|
|
485
|
+
;;
|
|
486
|
+
"stop")
|
|
487
|
+
shift
|
|
488
|
+
require_no_args "$command" "$@"
|
|
489
|
+
run_script ./scripts/down.sh
|
|
490
|
+
;;
|
|
491
|
+
"logs")
|
|
492
|
+
shift
|
|
493
|
+
service="$(optional_single_arg "$command" "odoo" "$@")"
|
|
494
|
+
run_script ./scripts/logs.sh "$service"
|
|
495
|
+
;;
|
|
496
|
+
"restart")
|
|
497
|
+
shift
|
|
498
|
+
require_no_args "$command" "$@"
|
|
499
|
+
run_script ./scripts/restart.sh
|
|
500
|
+
;;
|
|
501
|
+
"shell")
|
|
502
|
+
shift
|
|
503
|
+
require_no_args "$command" "$@"
|
|
504
|
+
run_script ./scripts/shell.sh
|
|
505
|
+
;;
|
|
506
|
+
"psql")
|
|
507
|
+
shift
|
|
508
|
+
db="$(optional_single_arg "$command" "postgres" "$@")"
|
|
509
|
+
run_script ./scripts/psql.sh "$db"
|
|
510
|
+
;;
|
|
511
|
+
"install")
|
|
512
|
+
shift
|
|
513
|
+
require_module_args "$command" "$@"
|
|
514
|
+
run_script ./scripts/install.sh "$@"
|
|
515
|
+
;;
|
|
516
|
+
"update")
|
|
517
|
+
shift
|
|
518
|
+
require_module_args "$command" "$@"
|
|
519
|
+
run_script ./scripts/update.sh "$@"
|
|
520
|
+
;;
|
|
521
|
+
"test")
|
|
522
|
+
shift
|
|
523
|
+
validate_test_args "$@"
|
|
524
|
+
run_script ./scripts/test.sh "$@"
|
|
525
|
+
;;
|
|
526
|
+
"resetdb")
|
|
527
|
+
shift
|
|
528
|
+
positional_args "$command" 0 2 "$@"
|
|
529
|
+
run_script ./scripts/resetdb.sh "$@"
|
|
530
|
+
;;
|
|
531
|
+
"snapshot")
|
|
532
|
+
shift
|
|
533
|
+
positional_args "$command" 0 2 "$@"
|
|
534
|
+
run_script ./scripts/snapshot.sh "$@"
|
|
535
|
+
;;
|
|
536
|
+
"restore-snapshot")
|
|
537
|
+
shift
|
|
538
|
+
restore_args=()
|
|
539
|
+
if [[ "\${1:-}" == "--dry-run" ]]; then
|
|
540
|
+
restore_args+=("--dry-run")
|
|
541
|
+
shift
|
|
542
|
+
fi
|
|
543
|
+
positional_args "$command" 1 2 "$@"
|
|
544
|
+
restore_args+=("$@")
|
|
545
|
+
run_script ./scripts/restore-snapshot.sh "\${restore_args[@]}"
|
|
546
|
+
;;
|
|
547
|
+
"lint")
|
|
548
|
+
shift
|
|
549
|
+
require_no_args "$command" "$@"
|
|
550
|
+
run_script ./scripts/lint.sh
|
|
551
|
+
;;
|
|
552
|
+
"pot")
|
|
553
|
+
shift
|
|
554
|
+
positional_args "$command" 1 3 "$@"
|
|
555
|
+
run_script ./scripts/pot.sh "$@"
|
|
556
|
+
;;
|
|
557
|
+
*)
|
|
558
|
+
exec npx --yes ${fallbackPackageSpec()} "$@"
|
|
559
|
+
;;
|
|
560
|
+
esac
|
|
561
|
+
`;
|
|
562
|
+
}
|
|
563
|
+
export function renderAddonsYaml(options) {
|
|
564
|
+
return `# Addons activated from source submodules.
|
|
565
|
+
#
|
|
566
|
+
# Source repos are managed as Git submodules under odoo/custom/src/private.
|
|
567
|
+
# Do not duplicate these same repos in repos.yaml.
|
|
568
|
+
|
|
569
|
+
${options.sourceRepos.map((repo) => `${sourceTypeOf(repo)}/${repo.path}:\n${yamlList(repo.addons)}`).join('\n\n')}
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
export function renderReposYaml(options) {
|
|
573
|
+
return `# git-aggregator repositories.
|
|
574
|
+
#
|
|
575
|
+
# Project source repositories are intentionally not listed here because
|
|
576
|
+
# they are pinned as Git submodules:
|
|
577
|
+
#
|
|
578
|
+
${options.sourceRepos.map((repo) => `# - ${sourceTypeOf(repo)}/${repo.path}`).join('\n')}
|
|
579
|
+
#
|
|
580
|
+
# Keep this file for upstream/OCA repositories that should be aggregated.
|
|
581
|
+
|
|
582
|
+
odoo:
|
|
583
|
+
defaults:
|
|
584
|
+
depth: $DEPTH_MERGE
|
|
585
|
+
remotes:
|
|
586
|
+
origin: https://github.com/OCA/OCB.git
|
|
587
|
+
odoo: https://github.com/odoo/odoo.git
|
|
588
|
+
target: origin $ODOO_VERSION
|
|
589
|
+
merges:
|
|
590
|
+
- origin $ODOO_VERSION
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
export function renderReadme(options) {
|
|
594
|
+
const title = titleizeProduct(options.product);
|
|
595
|
+
return `# ${title} Development Environment
|
|
596
|
+
|
|
597
|
+
Private ${environmentKind()} development environment for the ${title} product.
|
|
598
|
+
|
|
599
|
+
This folder owns the development environment only. Product source code lives
|
|
600
|
+
in source repository submodules under \`odoo/custom/src/private\`,
|
|
601
|
+
\`odoo/custom/src/oca\`, or \`odoo/custom/src/external\` when source
|
|
602
|
+
repositories are connected.
|
|
603
|
+
|
|
604
|
+
## Repository Layout
|
|
605
|
+
|
|
606
|
+
\`\`\`text
|
|
607
|
+
${repositoryLayout(options)}
|
|
608
|
+
\`\`\`
|
|
609
|
+
|
|
610
|
+
${cloneDocs(options)}
|
|
611
|
+
|
|
612
|
+
## WPMoo CLI Shortcut
|
|
613
|
+
|
|
614
|
+
This environment includes a local \`moo\` shortcut script. From the repository
|
|
615
|
+
root:
|
|
616
|
+
|
|
617
|
+
\`\`\`bash
|
|
618
|
+
./moo
|
|
619
|
+
./moo start
|
|
620
|
+
./moo stop
|
|
621
|
+
./moo restart
|
|
622
|
+
./moo doctor
|
|
623
|
+
./moo add-module
|
|
624
|
+
\`\`\`
|
|
625
|
+
|
|
626
|
+
Optionally, if this repository root is on your \`PATH\`, you can run \`moo ...\`
|
|
627
|
+
from anywhere and the script will return to this environment root first.
|
|
628
|
+
${optionalAgentSkillsReadme(options)}
|
|
629
|
+
## Source Repositories
|
|
630
|
+
|
|
631
|
+
${sourceRepoDocs(options)}
|
|
632
|
+
|
|
633
|
+
${environmentUsageDocs(options)}
|
|
634
|
+
## Branching
|
|
635
|
+
|
|
636
|
+
Use Odoo major-version branches in source repositories when you add them:
|
|
637
|
+
|
|
638
|
+
\`\`\`text
|
|
639
|
+
${options.odooVersion}
|
|
640
|
+
\`\`\`
|
|
641
|
+
|
|
642
|
+
If this environment is connected to Git, the dev repository can stay on \`main\`
|
|
643
|
+
and pin exact source commits through submodule references.
|
|
644
|
+
`;
|
|
645
|
+
}
|
|
646
|
+
export function renderAgents(options) {
|
|
647
|
+
const repoList = hasSourceRepos(options)
|
|
648
|
+
? options.sourceRepos.map((repo) => `- \`${repo.path}\`: \`${repo.url}\``).join('\n')
|
|
649
|
+
: '- No source repositories are configured yet.';
|
|
650
|
+
const sourceLayout = hasSourceRepos(options)
|
|
651
|
+
? `Product repositories are Git submodules. They are listed under the private
|
|
652
|
+
source directory below for this environment:
|
|
653
|
+
|
|
654
|
+
\`\`\`text
|
|
655
|
+
${options.sourceRepos.map(sourceRepoRelativePath).join('\n')}
|
|
656
|
+
\`\`\`
|
|
657
|
+
|
|
658
|
+
${repoDuplicationNote()}`
|
|
659
|
+
: 'No source repositories are configured yet. Use `./moo add-repo` or the cockpit Repositories menu before module-specific work.';
|
|
660
|
+
const addonList = hasSourceRepos(options)
|
|
661
|
+
? options.sourceRepos
|
|
662
|
+
.map((repo) => `\`${repo.path}\` addons:\n${repo.addons.map((addon) => `- \`${addon}\``).join('\n')}`)
|
|
663
|
+
.join('\n\n')
|
|
664
|
+
: 'No addon boundaries are known yet. Add source repositories before module-specific implementation.';
|
|
665
|
+
return `# AGENTS.md
|
|
666
|
+
|
|
667
|
+
## Project
|
|
668
|
+
|
|
669
|
+
Private ${environmentKind()} development environment for the ${titleizeProduct(options.product)} product.
|
|
670
|
+
|
|
671
|
+
## Repository Roles
|
|
672
|
+
|
|
673
|
+
- \`${options.devRepo}\`: environment/config only, private.
|
|
674
|
+
${repoList}
|
|
675
|
+
|
|
676
|
+
## Source Layout
|
|
677
|
+
|
|
678
|
+
${sourceLayout}
|
|
679
|
+
${optionalAgentSkillsAgentsSection(options)}
|
|
680
|
+
## Addon Boundaries
|
|
681
|
+
|
|
682
|
+
${addonList}
|
|
683
|
+
|
|
684
|
+
Public/community addons must not depend on private paid addons. Private paid
|
|
685
|
+
addons may depend on public/community addons.
|
|
686
|
+
|
|
687
|
+
## Odoo 19 Rules
|
|
688
|
+
|
|
689
|
+
- Use \`<list>\` instead of \`<tree>\`.
|
|
690
|
+
- Use direct view expressions such as \`invisible="..."\` instead of \`attrs\`.
|
|
691
|
+
- Use \`models.Constraint\` instead of \`_sql_constraints\`.
|
|
692
|
+
- Use \`@api.ondelete(at_uninstall=False)\` for delete validation.
|
|
693
|
+
- Avoid \`default_*\` field names in \`res.config.settings\`.
|
|
694
|
+
- Keep core/community installable without any pro modules.
|
|
695
|
+
|
|
696
|
+
## Verification
|
|
697
|
+
|
|
698
|
+
Use the environment's addon test/update command:
|
|
699
|
+
|
|
700
|
+
\`\`\`bash
|
|
701
|
+
${verificationCommand(options)}
|
|
702
|
+
\`\`\`
|
|
703
|
+
|
|
704
|
+
Useful maintenance commands:
|
|
705
|
+
|
|
706
|
+
\`\`\`bash
|
|
707
|
+
./moo lint
|
|
708
|
+
./moo resetdb [db] [module[,module]]
|
|
709
|
+
./moo snapshot [db] [snapshot-name]
|
|
710
|
+
./moo restore-snapshot [--dry-run] <snapshot-name> [db]
|
|
711
|
+
./moo pot <module[,module]> [db] [output]
|
|
712
|
+
\`\`\`
|
|
713
|
+
|
|
714
|
+
Daily script delegation vs package fallback:
|
|
715
|
+
- \`./moo start\`, \`logs\`, \`install\`, \`update\`, \`test\`, \`snapshot\`, and related runtime tasks delegate to local \`./scripts/*.sh\`.
|
|
716
|
+
- \`./moo status\` and \`./moo doctor\` are package fallback commands routed to \`npx --yes ${fallbackPackageSpec()} ...\`.
|
|
717
|
+
|
|
718
|
+
Only report completion after the relevant update/test/lint command exits cleanly.
|
|
719
|
+
`;
|
|
720
|
+
}
|
|
721
|
+
export function renderAppstoreRelease(options) {
|
|
722
|
+
return `# Odoo Apps Release Notes
|
|
723
|
+
|
|
724
|
+
Paid addons can live together in a private source repository during development.
|
|
725
|
+
Each paid addon still needs its own App Store metadata.
|
|
726
|
+
|
|
727
|
+
Per addon checklist:
|
|
728
|
+
|
|
729
|
+
- \`__manifest__.py\` has correct \`name\`, \`summary\`, \`version\`, \`depends\`,
|
|
730
|
+
\`license\`, \`price\`, \`currency\`, and \`support\`.
|
|
731
|
+
- \`license\` is appropriate for paid distribution, typically \`OPL-1\`.
|
|
732
|
+
- \`static/description/icon.png\` exists.
|
|
733
|
+
- \`static/description/index.html\` exists.
|
|
734
|
+
- Screenshots are stored under \`static/description/\`.
|
|
735
|
+
- Community dependency versions are compatible with the target Odoo major
|
|
736
|
+
version.
|
|
737
|
+
|
|
738
|
+
Recommended release flow:
|
|
739
|
+
|
|
740
|
+
1. Develop in the relevant private source repository.
|
|
741
|
+
2. Update the addon manifest version.
|
|
742
|
+
3. Run update/test commands in this dev environment.
|
|
743
|
+
4. Tag the source commit.
|
|
744
|
+
5. Prepare an App Store publish package or publish mirror per paid addon.
|
|
745
|
+
6. Trigger Odoo Apps repository scan/update.
|
|
746
|
+
|
|
747
|
+
If App Store scan coupling becomes a problem, create separate private publish
|
|
748
|
+
mirror repositories for each paid addon. Keep development in
|
|
749
|
+
\`odoo/custom/src/private\`; mirrors should be generated artifacts, not
|
|
750
|
+
hand-edited source repositories.
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
export function renderPlaceholder(title, body) {
|
|
754
|
+
return `# ${title}
|
|
755
|
+
|
|
756
|
+
${body}
|
|
757
|
+
`;
|
|
758
|
+
}
|