@wpmoo/odoo 0.8.30
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 +231 -0
- package/dist/addons-yaml.js +59 -0
- package/dist/args.js +247 -0
- package/dist/cli.js +703 -0
- package/dist/environment-context.js +10 -0
- package/dist/environment-version.js +5 -0
- package/dist/environment.js +61 -0
- package/dist/external-assets.js +113 -0
- package/dist/external-templates.js +69 -0
- package/dist/git.js +98 -0
- package/dist/github.js +87 -0
- package/dist/help.js +62 -0
- package/dist/menu-navigation.js +67 -0
- package/dist/module-actions.js +107 -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/repo-actions.js +106 -0
- package/dist/repo-url.js +27 -0
- package/dist/repository-preflight.js +46 -0
- package/dist/safe-reset.js +129 -0
- package/dist/scaffold.js +125 -0
- package/dist/templates.js +414 -0
- package/dist/types.js +1 -0
- package/dist/update-check.js +106 -0
- package/dist/version.js +19 -0
- package/docs/assets/wpmoo-banner.png +0 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 WPMoo.org
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# @wpmoo/odoo
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://github.com/wpmoo-org/wpmoo-odoo/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
WPMoo Odoo lifecycle tooling for development, staging, and production workflows.
|
|
10
|
+
|
|
11
|
+
The CLI currently creates Docker Compose based Odoo environments, adds source
|
|
12
|
+
repositories as Git submodules, and stages the result with `git add .`. It does
|
|
13
|
+
not commit. Staging and production workflows will build on the same package and
|
|
14
|
+
command surface.
|
|
15
|
+
|
|
16
|
+
Compose resources and project-local Agent Skills are copied from standalone
|
|
17
|
+
repositories, so large Docker/skill assets do not need to be embedded in the
|
|
18
|
+
TypeScript CLI. The compose resource uses static version-specific files such as
|
|
19
|
+
`docker-compose_19.0.yml` so it can also be used standalone.
|
|
20
|
+
|
|
21
|
+
For a product named `odoo_sample_module`, create these repositories first:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
odoo_sample_module_dev # private development environment repo
|
|
25
|
+
odoo_sample_module # source repo
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The CLI writes into `./odoo_sample_module_dev`. If that directory does not exist
|
|
29
|
+
locally, it clones the dev repo URL you provide.
|
|
30
|
+
|
|
31
|
+
When GitHub CLI is installed and authenticated, the interactive wizard detects
|
|
32
|
+
your GitHub username and organizations. If multiple accounts are available, it
|
|
33
|
+
asks where the repos should live and uses that owner for the default repo URLs.
|
|
34
|
+
The wizard also checks whether the dev and source repositories are accessible.
|
|
35
|
+
If they are not accessible, it can create them for you after confirmation.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
brew install gh
|
|
39
|
+
gh auth login
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use cloneable repository URLs such as
|
|
43
|
+
`https://github.com/example-org/odoo_sample_module.git`. If a GitHub
|
|
44
|
+
organization page URL like `https://github.com/orgs/example-org/odoo_sample_module`
|
|
45
|
+
is entered, the CLI normalizes it to the cloneable form.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Interactive wizard:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @wpmoo/odoo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The wizard is context-aware. If the current directory is not already a WPMoo
|
|
56
|
+
Odoo development environment, it starts the create flow directly.
|
|
57
|
+
|
|
58
|
+
Inside an existing environment, it shows maintenance actions:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
Add source repo
|
|
62
|
+
Remove source repo
|
|
63
|
+
Add module to source repo
|
|
64
|
+
Remove module from source repo
|
|
65
|
+
Safe reset environment
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Non-interactive:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx @wpmoo/odoo create \
|
|
72
|
+
--product odoo_sample_module \
|
|
73
|
+
--odoo-version 19.0 \
|
|
74
|
+
--dev-repo-url https://github.com/example-org/odoo_sample_module_dev.git \
|
|
75
|
+
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
76
|
+
--create-missing-repos \
|
|
77
|
+
--init-empty-repos
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Multiple source repositories:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @wpmoo/odoo create \
|
|
84
|
+
--product odoo_sample_module \
|
|
85
|
+
--dev-repo-url https://github.com/example-org/odoo_sample_module_dev.git \
|
|
86
|
+
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
87
|
+
--source-addons odoo_sample_module,odoo_sample_module_portal \
|
|
88
|
+
--source-repo-url git@github.com:example-org/odoo_sample_module_reports.git \
|
|
89
|
+
--source-path odoo_sample_module_reports \
|
|
90
|
+
--source-addons odoo_sample_module_reports
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Dry run:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx @wpmoo/odoo create \
|
|
97
|
+
--product odoo_sample_module \
|
|
98
|
+
--dev-repo-url https://github.com/example-org/odoo_sample_module_dev.git \
|
|
99
|
+
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
100
|
+
--dry-run
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Default Docker Compose engine through an external standalone compose resource:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx @wpmoo/odoo create \
|
|
107
|
+
--product odoo_sample_module \
|
|
108
|
+
--dev-repo-url https://github.com/example-org/odoo_sample_module_dev.git \
|
|
109
|
+
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
110
|
+
--agent-skills-template
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
During local resource development, point to local clones of the standalone repos:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git clone https://github.com/wpmoo-org/odoo-docker-compose ../odoo-docker-compose
|
|
117
|
+
git clone https://github.com/wpmoo-org/odoo-skills ../odoo-skills
|
|
118
|
+
|
|
119
|
+
npx @wpmoo/odoo create \
|
|
120
|
+
--engine compose \
|
|
121
|
+
--compose-template-url ../odoo-docker-compose \
|
|
122
|
+
--agent-skills-template \
|
|
123
|
+
--agent-skills-template-url ../odoo-skills \
|
|
124
|
+
--product odoo_sample_module \
|
|
125
|
+
--source-repo-url https://github.com/example-org/odoo_sample_module.git
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Add a source repository later from inside the dev environment:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx @wpmoo/odoo
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Choose `Add source repo`, then enter only the repository name, such as
|
|
135
|
+
`odoo_sample_module_reports`. The CLI uses the environment's GitHub owner and
|
|
136
|
+
Odoo version, checks whether the repository exists, can create it with GitHub
|
|
137
|
+
CLI when needed, and initializes empty repositories with the environment Odoo
|
|
138
|
+
branch automatically.
|
|
139
|
+
|
|
140
|
+
Non-interactive URL form:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npx @wpmoo/odoo add-repo \
|
|
144
|
+
--repo-url https://github.com/example-org/odoo_sample_module_reports.git \
|
|
145
|
+
--odoo-version 19.0 \
|
|
146
|
+
--init-empty-repos
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
When run inside a generated environment, maintenance actions use the environment
|
|
150
|
+
Odoo version from `.wpmoo/odoo.json`. Pass `--odoo-version` only when you
|
|
151
|
+
need an explicit override.
|
|
152
|
+
|
|
153
|
+
Remove a source repository from the dev environment:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx @wpmoo/odoo remove-repo --repo odoo_sample_module_reports
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Add a minimal Odoo module skeleton to a selected source repository:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npx @wpmoo/odoo add-module \
|
|
163
|
+
--repo odoo_sample_module \
|
|
164
|
+
--module odoo_sample_module_base \
|
|
165
|
+
--odoo-version 19.0
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Remove a module registration without deleting source files:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npx @wpmoo/odoo remove-module \
|
|
172
|
+
--repo odoo_sample_module \
|
|
173
|
+
--module odoo_sample_module_base
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Refresh generated environment files without deleting module source code:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npx @wpmoo/odoo reset
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Defaults
|
|
183
|
+
|
|
184
|
+
Each source repo can contain one or many Odoo modules. For example:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
odoo/custom/src/private/odoo_sample_module/
|
|
188
|
+
├── odoo_sample_module_base/
|
|
189
|
+
└── odoo_sample_module_another_module/
|
|
190
|
+
|
|
191
|
+
odoo/custom/src/private/odoo_sample_module_pro/
|
|
192
|
+
├── odoo_sample_module_payment/
|
|
193
|
+
└── odoo_sample_module_analytics/
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
If the project has portal, demo, payment, reports, or other addons, pass
|
|
197
|
+
`--source-addons` in non-interactive advanced usage or add modules later with the
|
|
198
|
+
CLI.
|
|
199
|
+
|
|
200
|
+
## WPMoo Development Guidelines
|
|
201
|
+
|
|
202
|
+
The CLI keeps environment creation focused on Docker Compose resources, source
|
|
203
|
+
submodules, and WPMoo metadata. It does not install agent tools, editor setup,
|
|
204
|
+
doctor scripts, or other optional development packs.
|
|
205
|
+
|
|
206
|
+
If you want agent-assisted workflows inside a generated environment, install
|
|
207
|
+
and manage them manually in that environment. For example, Agentic Stack can be
|
|
208
|
+
installed separately:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
brew tap codejunkie99/agentic-stack https://github.com/codejunkie99/agentic-stack
|
|
212
|
+
brew install agentic-stack
|
|
213
|
+
agentic-stack codex --yes
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Keep these files under normal project review, just like any other generated or
|
|
217
|
+
tool-owned development guideline files.
|
|
218
|
+
|
|
219
|
+
## Notes
|
|
220
|
+
|
|
221
|
+
- V1 is overlay-first and uses WPMoo's Docker Compose resources by default.
|
|
222
|
+
- Product source repositories are managed as Git submodules under
|
|
223
|
+
`odoo/custom/src/private`.
|
|
224
|
+
- Product source repositories are discovered from `odoo/custom/src/private` by
|
|
225
|
+
the compose entrypoint and exposed through `/mnt/wpmoo-addons`.
|
|
226
|
+
- Empty source repos can be initialized with an empty commit and the selected
|
|
227
|
+
Odoo branch when `--init-empty-repos` is provided.
|
|
228
|
+
- Missing GitHub repositories can be created with GitHub CLI when
|
|
229
|
+
`--create-missing-repos` is provided, or through the interactive wizard.
|
|
230
|
+
- Legacy `--org`, `--community-repo`, and `--pro-repo` flags are still accepted
|
|
231
|
+
when no `--source-repo-url` flags are provided.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function escapeRegExp(value) {
|
|
2
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
function ensureFinalNewline(value) {
|
|
5
|
+
return value.endsWith('\n') ? value : `${value}\n`;
|
|
6
|
+
}
|
|
7
|
+
function yamlList(items) {
|
|
8
|
+
return items.map((item) => ` - ${item}`).join('\n');
|
|
9
|
+
}
|
|
10
|
+
function renderSourceRepoBlock(repo) {
|
|
11
|
+
return `private/${repo.path}:\n${yamlList(repo.addons)}\n`;
|
|
12
|
+
}
|
|
13
|
+
export function addSourceRepoToAddonsYaml(content, repo) {
|
|
14
|
+
const blockPattern = new RegExp(`^private/${escapeRegExp(repo.path)}:\\s*$`, 'm');
|
|
15
|
+
if (blockPattern.test(content)) {
|
|
16
|
+
return content;
|
|
17
|
+
}
|
|
18
|
+
const base = ensureFinalNewline(content.trimEnd());
|
|
19
|
+
return `${base}\n${renderSourceRepoBlock(repo)}`;
|
|
20
|
+
}
|
|
21
|
+
export function removeSourceRepoFromAddonsYaml(content, repoPath) {
|
|
22
|
+
const blockPattern = new RegExp(`(^|\\n)private/${escapeRegExp(repoPath)}:\\n(?:[ \\t].*(?:\\n|$))*`, 'g');
|
|
23
|
+
const updated = content
|
|
24
|
+
.replace(blockPattern, (match, prefix) => (prefix === '\n' && match.endsWith('\n') ? '\n' : ''))
|
|
25
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
26
|
+
return ensureFinalNewline(updated.trimEnd());
|
|
27
|
+
}
|
|
28
|
+
function sourceRepoBlockPattern(repoPath) {
|
|
29
|
+
return new RegExp(`(^private/${escapeRegExp(repoPath)}:\\n)((?:[ \\t].*(?:\\n|$))*)`, 'm');
|
|
30
|
+
}
|
|
31
|
+
function parseYamlListItems(blockBody) {
|
|
32
|
+
return blockBody
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map((line) => line.trim().match(/^-\s+(.+)$/)?.[1]?.trim())
|
|
35
|
+
.filter((item) => Boolean(item));
|
|
36
|
+
}
|
|
37
|
+
export function addModuleToSourceRepoInAddonsYaml(content, repoPath, moduleName) {
|
|
38
|
+
const blockPattern = sourceRepoBlockPattern(repoPath);
|
|
39
|
+
const match = content.match(blockPattern);
|
|
40
|
+
if (!match) {
|
|
41
|
+
return addSourceRepoToAddonsYaml(content, { path: repoPath, addons: [moduleName] });
|
|
42
|
+
}
|
|
43
|
+
const addons = parseYamlListItems(match[2]);
|
|
44
|
+
if (addons.includes(moduleName)) {
|
|
45
|
+
return content;
|
|
46
|
+
}
|
|
47
|
+
const replacement = `${match[1]}${yamlList([...addons, moduleName])}\n`;
|
|
48
|
+
return content.replace(blockPattern, replacement);
|
|
49
|
+
}
|
|
50
|
+
export function removeModuleFromSourceRepoInAddonsYaml(content, repoPath, moduleName) {
|
|
51
|
+
const blockPattern = sourceRepoBlockPattern(repoPath);
|
|
52
|
+
const match = content.match(blockPattern);
|
|
53
|
+
if (!match) {
|
|
54
|
+
return content;
|
|
55
|
+
}
|
|
56
|
+
const addons = parseYamlListItems(match[2]).filter((addon) => addon !== moduleName);
|
|
57
|
+
const replacement = `${match[1]}${addons.length ? `${yamlList(addons)}\n` : ''}`;
|
|
58
|
+
return ensureFinalNewline(content.replace(blockPattern, replacement).trimEnd());
|
|
59
|
+
}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { basename, resolve } from 'node:path';
|
|
2
|
+
import { supportedOdooVersions } from './odoo-versions.js';
|
|
3
|
+
import { defaultAgentSkillsTemplateUrl, defaultComposeTemplateUrl } from './external-templates.js';
|
|
4
|
+
import { defaultCommunityAddons, defaultProAddons } from './templates.js';
|
|
5
|
+
import { validateAddonName, validateRepoPath } from './path-validation.js';
|
|
6
|
+
import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-url.js';
|
|
7
|
+
const commandNames = new Set(['create', 'add-repo', 'remove-repo', 'add-module', 'remove-module', 'reset']);
|
|
8
|
+
const internalFlags = new Set(['--no-update-check']);
|
|
9
|
+
export function isUpdateCheckFlag(arg) {
|
|
10
|
+
return internalFlags.has(arg);
|
|
11
|
+
}
|
|
12
|
+
export function stripInternalFlags(argv) {
|
|
13
|
+
return argv.filter((arg) => !isUpdateCheckFlag(arg));
|
|
14
|
+
}
|
|
15
|
+
export function commandFromArgs(argv) {
|
|
16
|
+
if (argv.length === 0) {
|
|
17
|
+
return { command: 'menu', argv: [] };
|
|
18
|
+
}
|
|
19
|
+
const [first, ...rest] = argv;
|
|
20
|
+
if (commandNames.has(first)) {
|
|
21
|
+
return { command: first, argv: rest };
|
|
22
|
+
}
|
|
23
|
+
if (first.startsWith('--') || first === '-h' || first === '-v') {
|
|
24
|
+
return { command: 'create', argv };
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`Unknown command: ${first}`);
|
|
27
|
+
}
|
|
28
|
+
export function parseArgs(argv) {
|
|
29
|
+
const values = {};
|
|
30
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
31
|
+
const arg = argv[index];
|
|
32
|
+
if (!arg.startsWith('--')) {
|
|
33
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
34
|
+
}
|
|
35
|
+
const [rawKey, inlineValue] = arg.slice(2).split('=', 2);
|
|
36
|
+
const key = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
37
|
+
if (inlineValue !== undefined) {
|
|
38
|
+
values[key] = inlineValue;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const next = argv[index + 1];
|
|
42
|
+
if (!next || next.startsWith('--')) {
|
|
43
|
+
values[key] = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
values[key] = next;
|
|
47
|
+
index += 1;
|
|
48
|
+
}
|
|
49
|
+
return { values };
|
|
50
|
+
}
|
|
51
|
+
function stringValue(values, key) {
|
|
52
|
+
const value = values[key];
|
|
53
|
+
return typeof value === 'string' ? value : undefined;
|
|
54
|
+
}
|
|
55
|
+
function listValue(value, fallback) {
|
|
56
|
+
if (!value)
|
|
57
|
+
return fallback;
|
|
58
|
+
return value
|
|
59
|
+
.split(',')
|
|
60
|
+
.map((item) => item.trim())
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
function validateAddons(addons) {
|
|
64
|
+
return addons.map(validateAddonName);
|
|
65
|
+
}
|
|
66
|
+
function valueAfter(argv, index, key) {
|
|
67
|
+
const arg = argv[index];
|
|
68
|
+
const inlineValue = arg.includes('=') ? arg.slice(arg.indexOf('=') + 1) : undefined;
|
|
69
|
+
if (inlineValue !== undefined) {
|
|
70
|
+
return { value: inlineValue, nextIndex: index };
|
|
71
|
+
}
|
|
72
|
+
const next = argv[index + 1];
|
|
73
|
+
if (!next || next.startsWith('--')) {
|
|
74
|
+
throw new Error(`Missing value for --${key}`);
|
|
75
|
+
}
|
|
76
|
+
return { value: next, nextIndex: index + 1 };
|
|
77
|
+
}
|
|
78
|
+
function parseSourceRepos(argv) {
|
|
79
|
+
const repos = [];
|
|
80
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
81
|
+
const arg = argv[index];
|
|
82
|
+
if (!arg.startsWith('--'))
|
|
83
|
+
continue;
|
|
84
|
+
const rawKey = arg.slice(2).split('=', 1)[0];
|
|
85
|
+
const key = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
86
|
+
if (key === 'sourceRepoUrl') {
|
|
87
|
+
const parsed = valueAfter(argv, index, rawKey);
|
|
88
|
+
repos.push({ url: normalizeRepositoryUrl(parsed.value) });
|
|
89
|
+
index = parsed.nextIndex;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (key === 'sourcePath') {
|
|
93
|
+
const current = repos.at(-1);
|
|
94
|
+
if (!current)
|
|
95
|
+
throw new Error('--source-path must follow --source-repo-url');
|
|
96
|
+
const parsed = valueAfter(argv, index, rawKey);
|
|
97
|
+
current.path = parsed.value;
|
|
98
|
+
index = parsed.nextIndex;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (key === 'sourceAddons') {
|
|
102
|
+
const current = repos.at(-1);
|
|
103
|
+
if (!current)
|
|
104
|
+
throw new Error('--source-addons must follow --source-repo-url');
|
|
105
|
+
const parsed = valueAfter(argv, index, rawKey);
|
|
106
|
+
current.addons = listValue(parsed.value, []);
|
|
107
|
+
index = parsed.nextIndex;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return repos.map((repo) => {
|
|
111
|
+
const path = validateRepoPath(repo.path?.trim() || inferRepoPath(repo.url));
|
|
112
|
+
return {
|
|
113
|
+
url: repo.url,
|
|
114
|
+
path,
|
|
115
|
+
addons: validateAddons(repo.addons?.length ? repo.addons : [path]),
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function booleanValue(values, key, fallback) {
|
|
120
|
+
const value = values[key];
|
|
121
|
+
if (value === undefined)
|
|
122
|
+
return fallback;
|
|
123
|
+
if (typeof value === 'boolean')
|
|
124
|
+
return value;
|
|
125
|
+
const normalized = value.toLowerCase().trim();
|
|
126
|
+
if (['true', '1', 'yes', 'y'].includes(normalized))
|
|
127
|
+
return true;
|
|
128
|
+
if (['false', '0', 'no', 'n'].includes(normalized))
|
|
129
|
+
return false;
|
|
130
|
+
throw new Error(`Invalid boolean value for --${key}: ${value}`);
|
|
131
|
+
}
|
|
132
|
+
function visibilityValue(values) {
|
|
133
|
+
const value = stringValue(values, 'repoVisibility') ?? 'private';
|
|
134
|
+
if (value === 'private' || value === 'public') {
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`Invalid value for --repo-visibility: ${value}`);
|
|
138
|
+
}
|
|
139
|
+
function engineValue(values) {
|
|
140
|
+
const value = stringValue(values, 'engine') ?? 'compose';
|
|
141
|
+
if (value === 'compose') {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`Invalid value for --engine: ${value}`);
|
|
145
|
+
}
|
|
146
|
+
function assertNoRemovedDevelopmentPackFlags(argv) {
|
|
147
|
+
for (const arg of argv) {
|
|
148
|
+
const rawKey = arg.startsWith('--') ? arg.slice(2).split('=', 1)[0] : '';
|
|
149
|
+
if (rawKey === 'pack' || rawKey === 'no-packs') {
|
|
150
|
+
throw new Error('Optional development packs were removed. See the WPMoo Development Guidelines in README.md.');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function defaultTargetForProduct(product, cwd = process.cwd()) {
|
|
155
|
+
const devRepo = `${product}_dev`;
|
|
156
|
+
return basename(cwd) === devRepo ? cwd : resolve(cwd, devRepo);
|
|
157
|
+
}
|
|
158
|
+
export function optionsFromArgs(argv) {
|
|
159
|
+
assertNoRemovedDevelopmentPackFlags(argv);
|
|
160
|
+
const { values } = parseArgs(argv);
|
|
161
|
+
const product = stringValue(values, 'product');
|
|
162
|
+
if (!product) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
const parsedSourceRepos = parseSourceRepos(argv);
|
|
166
|
+
const hasLegacySourceConfig = [
|
|
167
|
+
'org',
|
|
168
|
+
'communityRepo',
|
|
169
|
+
'communityRepoUrl',
|
|
170
|
+
'communityAddons',
|
|
171
|
+
'proRepo',
|
|
172
|
+
'proRepoUrl',
|
|
173
|
+
'proAddons',
|
|
174
|
+
].some((key) => values[key] !== undefined);
|
|
175
|
+
if (parsedSourceRepos.length === 0 && !hasLegacySourceConfig) {
|
|
176
|
+
throw new Error('Missing --source-repo-url. Provide at least one source repository URL.');
|
|
177
|
+
}
|
|
178
|
+
const org = stringValue(values, 'org') ?? inferGitHubOwner(parsedSourceRepos[0]?.url ?? '') ?? 'example-org';
|
|
179
|
+
const odooVersion = stringValue(values, 'odooVersion') ?? supportedOdooVersions[0];
|
|
180
|
+
const engine = engineValue(values);
|
|
181
|
+
const installAgentSkillsTemplate = booleanValue(values, 'agentSkillsTemplate', values.agentSkillsTemplateUrl !== undefined);
|
|
182
|
+
const devRepoUrl = normalizeRepositoryUrl(stringValue(values, 'devRepoUrl') ?? `https://github.com/${org}/${product}_dev.git`);
|
|
183
|
+
const devRepo = stringValue(values, 'devRepo') ?? inferRepoPath(devRepoUrl);
|
|
184
|
+
const communityRepo = validateRepoPath(stringValue(values, 'communityRepo') ?? product);
|
|
185
|
+
const proRepo = validateRepoPath(stringValue(values, 'proRepo') ?? `${product}_pro`);
|
|
186
|
+
const targetValue = stringValue(values, 'target');
|
|
187
|
+
const target = targetValue ? resolve(targetValue) : defaultTargetForProduct(product);
|
|
188
|
+
const communityRepoUrl = normalizeRepositoryUrl(stringValue(values, 'communityRepoUrl') ?? `https://github.com/${org}/${communityRepo}.git`);
|
|
189
|
+
const proRepoUrl = normalizeRepositoryUrl(stringValue(values, 'proRepoUrl') ?? `https://github.com/${org}/${proRepo}.git`);
|
|
190
|
+
const communityAddons = validateAddons(listValue(stringValue(values, 'communityAddons'), defaultCommunityAddons(product)));
|
|
191
|
+
const proAddons = validateAddons(listValue(stringValue(values, 'proAddons'), defaultProAddons(product)));
|
|
192
|
+
const hasExplicitProRepo = values.proRepo !== undefined || values.proRepoUrl !== undefined || values.proAddons !== undefined;
|
|
193
|
+
const sourceRepos = parsedSourceRepos.length > 0
|
|
194
|
+
? parsedSourceRepos
|
|
195
|
+
: [
|
|
196
|
+
{
|
|
197
|
+
url: communityRepoUrl,
|
|
198
|
+
path: communityRepo,
|
|
199
|
+
addons: communityAddons,
|
|
200
|
+
},
|
|
201
|
+
...(hasExplicitProRepo
|
|
202
|
+
? [
|
|
203
|
+
{
|
|
204
|
+
url: proRepoUrl,
|
|
205
|
+
path: proRepo,
|
|
206
|
+
addons: proAddons,
|
|
207
|
+
},
|
|
208
|
+
]
|
|
209
|
+
: []),
|
|
210
|
+
].filter((repo) => repo.url && repo.path && repo.addons.length);
|
|
211
|
+
return {
|
|
212
|
+
product,
|
|
213
|
+
org,
|
|
214
|
+
odooVersion,
|
|
215
|
+
engine,
|
|
216
|
+
composeTemplateUrl: stringValue(values, 'composeTemplateUrl') ?? defaultComposeTemplateUrl,
|
|
217
|
+
composeTemplateRef: stringValue(values, 'composeTemplateRef'),
|
|
218
|
+
agentSkillsTemplateUrl: installAgentSkillsTemplate
|
|
219
|
+
? stringValue(values, 'agentSkillsTemplateUrl') ?? defaultAgentSkillsTemplateUrl
|
|
220
|
+
: undefined,
|
|
221
|
+
agentSkillsTemplateRef: stringValue(values, 'agentSkillsTemplateRef'),
|
|
222
|
+
postgresVersion: stringValue(values, 'postgresVersion'),
|
|
223
|
+
httpPort: stringValue(values, 'httpPort'),
|
|
224
|
+
geventPort: stringValue(values, 'geventPort'),
|
|
225
|
+
devRepo,
|
|
226
|
+
devRepoUrl,
|
|
227
|
+
communityRepo,
|
|
228
|
+
proRepo,
|
|
229
|
+
communityRepoUrl,
|
|
230
|
+
proRepoUrl,
|
|
231
|
+
communityAddons,
|
|
232
|
+
proAddons,
|
|
233
|
+
sourceRepos,
|
|
234
|
+
target,
|
|
235
|
+
dryRun: booleanValue(values, 'dryRun', false),
|
|
236
|
+
initEmptyRepos: booleanValue(values, 'initEmptyRepos', false),
|
|
237
|
+
stage: booleanValue(values, 'stage', true),
|
|
238
|
+
createMissingRepos: booleanValue(values, 'createMissingRepos', false),
|
|
239
|
+
repoVisibility: visibilityValue(values),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
export function isHelpRequested(argv) {
|
|
243
|
+
return argv.includes('--help') || argv.includes('-h');
|
|
244
|
+
}
|
|
245
|
+
export function isVersionRequested(argv) {
|
|
246
|
+
return argv.includes('--version') || argv.includes('-v');
|
|
247
|
+
}
|