@wpmoo/toolkit 0.9.6 → 0.9.8
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/README.md +95 -443
- package/dist/cli.js +63 -13
- package/dist/cockpit/command-registry.js +1 -1
- package/dist/cockpit/daily-prompts.js +4 -3
- package/dist/cockpit/module-action-menu.js +1 -1
- package/dist/databases.js +15 -3
- package/dist/help.js +2 -0
- package/dist/prompts/index.js +18 -16
- package/dist/system-prerequisites.js +189 -0
- package/dist/templates.js +41 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,373 +1,152 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
[](https://github.com/wpmoo-org/wpmoo-toolkit/actions/workflows/ci.yml) [](https://github.com/wpmoo-org/wpmoo-toolkit) [](https://www.npmjs.com/package/@wpmoo/toolkit) [](https://codecov.io/gh/wpmoo-org/wpmoo-toolkit) [](LICENSE) [](https://github.com/wpmoo-org/wpmoo-toolkit/actions/workflows/ci.yml) [](https://github.com/wpmoo-org/wpmoo-toolkit) [](https://www.npmjs.com/package/@wpmoo/toolkit) [](https://codecov.io/gh/wpmoo-org/wpmoo-toolkit) [](LICENSE) [](https://github.com/wpmoo-org/wpmoo-toolkit) [](https://www.buymeacoffee.com/cangir) [](https://www.patreon.com/wpmoo)
|
|
4
4
|
|
|
5
5
|
# WPMoo Toolkit
|
|
6
6
|
|
|
7
|
-
WPMoo Toolkit is a
|
|
7
|
+
WPMoo Toolkit is a free, MIT-licensed CLI for creating and operating repeatable Docker Compose based Odoo development environments.
|
|
8
8
|
|
|
9
|
-
It
|
|
9
|
+
It is built for the everyday moments that tend to slow Odoo teams down: setting up a clean environment, keeping source repositories in a known layout, starting services, updating modules, testing changes, taking snapshots, restoring a database, and recovering generated files without touching product source code.
|
|
10
10
|
|
|
11
11
|
WPMoo Toolkit is an independent project and is not affiliated with, endorsed by, or sponsored by Odoo S.A. Odoo is a trademark of Odoo S.A.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Why WPMoo Exists
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
Odoo development has good building blocks. Docker Compose is familiar. OCA conventions are strong. The wider ecosystem has helped many teams think more clearly about Odoo infrastructure.
|
|
16
|
+
|
|
17
|
+
What we kept missing was a smaller, local-first workflow tool.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
We wanted a generated development repository that stayed boring and recoverable. We wanted product source code to live in its own Git repositories, not be mixed with disposable runtime files. We wanted a cockpit that remembered the daily Odoo tasks developers actually run, without asking everyone to become an infrastructure specialist just to update a module or restore a snapshot.
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
21
|
+
WPMoo is that layer. It does not try to replace the whole ecosystem. It gives an Odoo team a practical starting point, a stable folder layout, and a safer daily workflow.
|
|
22
|
+
|
|
23
|
+
## What It Solves
|
|
24
|
+
|
|
25
|
+
- Creates a repeatable Odoo development environment from a product name, Odoo version, and one or more source repositories.
|
|
26
|
+
- Keeps Odoo source repositories under `private`, `oca`, or `external` categories as Git submodules in `odoo/custom/src/`.
|
|
27
|
+
- Provides a guided terminal cockpit for services, modules, database work, diagnostics, repository management, and maintenance.
|
|
28
|
+
- Includes direct commands for automation and CI-friendly terminal workflows.
|
|
29
|
+
- Adds recovery tools such as `status`, `doctor`, `snapshot`, `restore-snapshot`, and safe reset.
|
|
30
|
+
- Refreshes generated environment files without deleting product source code.
|
|
31
|
+
|
|
32
|
+
## Development Status
|
|
33
|
+
|
|
34
|
+
> [!IMPORTANT]
|
|
35
|
+
> WPMoo Toolkit is still pre-1.0. Use it for evaluation, local trials, and feedback before relying on it for critical production workflows. Setup conventions and command behavior may still change before `1.0.0`.
|
|
25
36
|
|
|
26
37
|
## Prerequisites
|
|
27
38
|
|
|
28
39
|
- Node.js `20.17+`
|
|
29
40
|
- Git
|
|
30
|
-
- Docker
|
|
31
|
-
-
|
|
41
|
+
- Docker and Docker Compose for generated environment runtime commands
|
|
42
|
+
- Optional: GitHub CLI (`gh`) when you want setup to inspect or create GitHub repositories
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
brew install gh
|
|
35
|
-
gh auth login
|
|
36
|
-
```
|
|
44
|
+
Before environment setup starts, WPMoo checks Git, Docker, Docker Compose, and the Docker Engine. If a required tool is missing, the wizard stops before asking setup questions, shows official download links inline with the missing tools, and lets you check again or exit with `Ctrl+C`. Install the missing tools, restart your terminal if PATH changed, start Docker Desktop, then run `npx @wpmoo/toolkit` again.
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
```bash
|
|
47
|
+
brew install gh
|
|
48
|
+
gh auth login
|
|
49
|
+
```
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
environments now use the compact compose layout (`compose.yaml` with
|
|
43
|
-
`compose/<env>.yaml` overlays). Legacy root-level
|
|
44
|
-
`docker-compose_<version>.yml` layouts are still supported for compatibility.
|
|
51
|
+
GitHub CLI (`gh`) is optional. WPMoo can run local-only and source repositories can be added later.
|
|
45
52
|
|
|
46
|
-
## Quick
|
|
53
|
+
## Quick Setup
|
|
47
54
|
|
|
48
|
-
Run the guided wizard from
|
|
55
|
+
Run the guided wizard from the workspace where you keep Odoo projects:
|
|
49
56
|
|
|
50
57
|
```bash
|
|
51
58
|
npx @wpmoo/toolkit
|
|
52
59
|
```
|
|
53
60
|
|
|
54
|
-
Short
|
|
61
|
+
Short aliases are also available:
|
|
55
62
|
|
|
56
63
|
```bash
|
|
57
64
|
npx wpmoo
|
|
65
|
+
npx @wpmoo/odoo
|
|
66
|
+
npx @wpmoo/odoo-dev
|
|
58
67
|
```
|
|
59
68
|
|
|
60
|
-
Legacy package paths `npx @wpmoo/odoo` and `npx @wpmoo/odoo-dev` remain
|
|
61
|
-
available as compatibility redirects to `@wpmoo/toolkit`.
|
|
69
|
+
Legacy package paths `npx @wpmoo/odoo` and `npx @wpmoo/odoo-dev` remain available for compatibility.
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
When the current directory is not already a WPMoo environment, the CLI opens the create flow. It asks for a product slug, Odoo version, and environment folder. The default environment folder is `./<product>_dev`.
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
For non-interactive usage with repository URLs:
|
|
68
|
-
|
|
69
|
-
Direct `create` commands keep the existing repo URL options; use `--target <path>` to choose a custom folder.
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
npx @wpmoo/toolkit create \
|
|
73
|
-
--product odoo_sample_module \
|
|
74
|
-
--odoo-version 19.0 \
|
|
75
|
-
--dev-repo-url https://github.com/example-org/odoo_sample_module_dev.git \
|
|
76
|
-
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
77
|
-
--init-empty-repos
|
|
78
|
-
```
|
|
73
|
+
Choose any environment folder; the default is `./<product>_dev`. Choose local-only setup to skip Git/GitHub connection and source repo prompts. Add source repositories later from the cockpit (`Repositories` -> `add-repo`) or `npx @wpmoo/toolkit add-repo`. Direct `create` commands keep the existing repo URL options; use `--target <path>` to choose a custom folder.
|
|
79
74
|
|
|
80
|
-
|
|
75
|
+
After setup, enter the generated environment and open the cockpit:
|
|
81
76
|
|
|
82
77
|
```bash
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
78
|
+
cd <product>_dev
|
|
79
|
+
./moo
|
|
91
80
|
```
|
|
92
81
|
|
|
93
|
-
|
|
82
|
+
For non-interactive setup:
|
|
94
83
|
|
|
95
84
|
```bash
|
|
96
85
|
npx @wpmoo/toolkit create \
|
|
97
86
|
--product odoo_sample_module \
|
|
87
|
+
--odoo-version 19.0 \
|
|
98
88
|
--dev-repo-url https://github.com/example-org/odoo_sample_module_dev.git \
|
|
99
89
|
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
100
|
-
--
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## The Cockpit
|
|
104
|
-
|
|
105
|
-
Run the package with no command inside a generated environment:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
npx @wpmoo/toolkit
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
The cockpit starts with a fast environment status summary, then opens a compact menu designed for repeated local work:
|
|
112
|
-
|
|
113
|
-
```text
|
|
114
|
-
Command palette /
|
|
115
|
-
Services
|
|
116
|
-
Modules
|
|
117
|
-
Database
|
|
118
|
-
Diagnostics
|
|
119
|
-
Repositories
|
|
120
|
-
Maintenance
|
|
121
|
-
Exit
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
The UI is intentionally practical rather than decorative:
|
|
125
|
-
|
|
126
|
-
- `Command palette /` searches slash commands such as `/test`, `/logs`, `/doctor`, and `/safe-reset`.
|
|
127
|
-
- Category menus group related tasks for scanability: services, modules, database, diagnostics, repositories, and maintenance.
|
|
128
|
-
- `Esc` returns from category menus to the top-level cockpit.
|
|
129
|
-
- Empty states explain the next action, such as adding a source repo before selecting a module.
|
|
130
|
-
- Risky commands such as stopping services, resetting databases, restoring snapshots, removing repos, removing modules, and safe reset ask for explicit confirmation.
|
|
131
|
-
- Guided prompts collect common arguments for daily actions, including module names, database names, test modes, tags, snapshot names, and POT output paths.
|
|
132
|
-
|
|
133
|
-
## Cockpit Command Map
|
|
134
|
-
|
|
135
|
-
| Category | Commands |
|
|
136
|
-
| --- | --- |
|
|
137
|
-
| Services | `start`, `stop`, `restart`, `logs`, `shell` |
|
|
138
|
-
| Modules | `install`, `update`, `test`, `lint`, `pot`, `add-module`, `remove-module` |
|
|
139
|
-
| Database | `psql`, `snapshot`, `restore-snapshot`, `resetdb` |
|
|
140
|
-
| Diagnostics | `status`, `doctor` |
|
|
141
|
-
| Repositories | `add-repo`, `remove-repo` |
|
|
142
|
-
| Maintenance | `safe-reset` |
|
|
143
|
-
|
|
144
|
-
Every cockpit action maps to a direct command, or to an equivalent management command such as `/safe-reset` mapping to `reset`, for scripting and repeatable terminal workflows.
|
|
145
|
-
|
|
146
|
-
## Direct Commands
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
npx @wpmoo/toolkit --help
|
|
150
|
-
npx @wpmoo/toolkit --version
|
|
151
|
-
|
|
152
|
-
npx @wpmoo/toolkit status
|
|
153
|
-
npx @wpmoo/toolkit status --json
|
|
154
|
-
npx @wpmoo/toolkit doctor
|
|
155
|
-
npx @wpmoo/toolkit doctor --json
|
|
156
|
-
npx @wpmoo/toolkit doctor --fix
|
|
157
|
-
npx @wpmoo/toolkit source list --json
|
|
158
|
-
npx @wpmoo/toolkit add-repo --repo-url https://github.com/example-org/odoo_sample_module_reports.git
|
|
159
|
-
npx @wpmoo/toolkit remove-repo --repo odoo_sample_module_reports
|
|
160
|
-
npx @wpmoo/toolkit add-module --repo odoo_sample_module --module odoo_sample_module_base --source-type private
|
|
161
|
-
npx @wpmoo/toolkit remove-module --repo odoo_sample_module --module odoo_sample_module_base --source-type private
|
|
162
|
-
npx @wpmoo/toolkit reset --dry-run
|
|
163
|
-
npx @wpmoo/toolkit reset
|
|
164
|
-
|
|
165
|
-
npx @wpmoo/toolkit start
|
|
166
|
-
npx @wpmoo/toolkit stop
|
|
167
|
-
npx @wpmoo/toolkit restart
|
|
168
|
-
npx @wpmoo/toolkit logs odoo
|
|
169
|
-
npx @wpmoo/toolkit shell
|
|
170
|
-
npx @wpmoo/toolkit psql postgres
|
|
171
|
-
|
|
172
|
-
npx @wpmoo/toolkit install sale devel
|
|
173
|
-
npx @wpmoo/toolkit update sale devel
|
|
174
|
-
npx @wpmoo/toolkit test sale --db devel --mode update --tags /sale
|
|
175
|
-
npx @wpmoo/toolkit lint
|
|
176
|
-
npx @wpmoo/toolkit pot sale devel i18n/sale.pot
|
|
177
|
-
|
|
178
|
-
npx @wpmoo/toolkit resetdb devel sale
|
|
179
|
-
npx @wpmoo/toolkit snapshot devel before-update
|
|
180
|
-
npx @wpmoo/toolkit restore-snapshot --dry-run before-update devel
|
|
181
|
-
npx @wpmoo/toolkit restore-snapshot before-update devel
|
|
90
|
+
--init-empty-repos
|
|
182
91
|
```
|
|
183
92
|
|
|
184
|
-
|
|
93
|
+
## Main Cockpit Menu
|
|
185
94
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
A generated environment is a separate Git repository, usually named `<product>_dev`, but the wizard and `--target` can use any folder. Product source code stays in child source repositories.
|
|
95
|
+
The cockpit is the daily workspace. It starts with environment status and then shows a compact menu:
|
|
189
96
|
|
|
190
97
|
```text
|
|
191
|
-
|
|
192
|
-
|--
|
|
193
|
-
|
|
|
194
|
-
|--
|
|
195
|
-
|--
|
|
196
|
-
|--
|
|
197
|
-
|--
|
|
198
|
-
|--
|
|
199
|
-
|
|
|
200
|
-
|
|
201
|
-
|
|
|
202
|
-
|--
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|--
|
|
206
|
-
| |--
|
|
207
|
-
| `--
|
|
208
|
-
|--
|
|
209
|
-
|
|
|
210
|
-
|
|
|
211
|
-
|--
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
|
215
|
-
|
|
|
216
|
-
|
|
217
|
-
|
|
|
218
|
-
`--
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
The metadata file `.wpmoo/odoo.json` records the product slug, selected Odoo version, dev repo URL, source repos, engine, external resource refs, ports, and template configuration. Status, doctor, daily actions, and safe reset use that metadata instead of guessing from the filesystem.
|
|
226
|
-
|
|
227
|
-
## Daily `./moo` Commands
|
|
228
|
-
|
|
229
|
-
Generated environments include a local `./moo` dispatcher. It is the shortest path for everyday Compose and Odoo work:
|
|
98
|
+
WPMoo Cockpit
|
|
99
|
+
|-- Command palette /
|
|
100
|
+
| |-- search commands such as /test, /logs, /doctor, /safe-reset
|
|
101
|
+
|-- Services
|
|
102
|
+
| |-- start
|
|
103
|
+
| |-- stop
|
|
104
|
+
| |-- restart
|
|
105
|
+
| |-- logs
|
|
106
|
+
| `-- shell
|
|
107
|
+
|-- Modules
|
|
108
|
+
| |-- install
|
|
109
|
+
| |-- update
|
|
110
|
+
| |-- test
|
|
111
|
+
| |-- lint
|
|
112
|
+
| |-- pot
|
|
113
|
+
| |-- add-module
|
|
114
|
+
| `-- remove-module
|
|
115
|
+
|-- Database
|
|
116
|
+
| |-- psql
|
|
117
|
+
| |-- snapshot
|
|
118
|
+
| |-- restore-snapshot
|
|
119
|
+
| `-- resetdb
|
|
120
|
+
|-- Diagnostics
|
|
121
|
+
| |-- status
|
|
122
|
+
| `-- doctor
|
|
123
|
+
|-- Repositories
|
|
124
|
+
| |-- add-repo
|
|
125
|
+
| `-- remove-repo
|
|
126
|
+
|-- Maintenance
|
|
127
|
+
| `-- safe-reset
|
|
128
|
+
`-- Exit
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Every cockpit action maps to a direct command, so the same workflow can be used interactively or scripted:
|
|
230
132
|
|
|
231
133
|
```bash
|
|
232
|
-
cp .env.example .env
|
|
233
|
-
|
|
234
134
|
./moo start
|
|
235
135
|
./moo logs odoo
|
|
236
|
-
./moo
|
|
237
|
-
./moo
|
|
238
|
-
./moo restart
|
|
239
|
-
./moo stop
|
|
240
|
-
|
|
241
|
-
./moo install sale devel
|
|
242
|
-
./moo update sale devel
|
|
243
|
-
./moo test sale --db devel --mode update --tags /sale
|
|
244
|
-
./moo lint
|
|
245
|
-
./moo pot sale devel i18n/sale.pot
|
|
246
|
-
|
|
136
|
+
./moo update sale
|
|
137
|
+
./moo test sale
|
|
247
138
|
./moo snapshot devel before-update
|
|
248
139
|
./moo restore-snapshot --dry-run before-update devel
|
|
249
|
-
./moo restore-snapshot before-update devel
|
|
250
|
-
./moo resetdb devel sale
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
`restore-snapshot --dry-run` validates the selected snapshot and prints the
|
|
254
|
-
restore plan without changing the database or filestore. Generated environments
|
|
255
|
-
also support `WPMOO_SNAPSHOT_RETENTION_COUNT` for pruning old snapshot files.
|
|
256
|
-
When `WPMOO_ENV=stage` or `WPMOO_ENV=prod`, destructive database actions such
|
|
257
|
-
as `resetdb` and real `restore-snapshot` require `WPMOO_ALLOW_DESTRUCTIVE=1`.
|
|
258
|
-
|
|
259
|
-
Use `npx @wpmoo/toolkit ...` for package/operator commands such as `create`, `add-repo`, `remove-repo`, `add-module`, `remove-module`, `status`, `doctor`, and `reset`. Use `./moo ...` inside a generated environment for local daily Compose commands.
|
|
260
|
-
|
|
261
|
-
## Repository and Module Management
|
|
262
|
-
|
|
263
|
-
Add a source repository after local-only setup from the cockpit or direct command:
|
|
264
|
-
|
|
265
|
-
```bash
|
|
266
|
-
npx @wpmoo/toolkit add-repo \
|
|
267
|
-
--repo-url https://github.com/example-org/odoo_sample_module_reports.git \
|
|
268
|
-
--init-empty-repos
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
Pin source repositories to dedicated source directories:
|
|
272
|
-
|
|
273
|
-
```bash
|
|
274
|
-
npx @wpmoo/toolkit add-repo \
|
|
275
|
-
--repo-url https://github.com/OCA/sale-workflow.git \
|
|
276
|
-
--source-type oca
|
|
277
|
-
|
|
278
|
-
npx @wpmoo/toolkit add-repo \
|
|
279
|
-
--repo-url https://github.com/example-org/odoo_external_tool.git \
|
|
280
|
-
--source-type external
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
GitHub CLI is optional for repository setup. When it is available and authenticated, the interactive flow can:
|
|
284
|
-
|
|
285
|
-
- detect the owner or organization from the current environment;
|
|
286
|
-
- suggest repository URLs;
|
|
287
|
-
- check whether the repository is accessible;
|
|
288
|
-
- create inaccessible repositories after confirmation;
|
|
289
|
-
- initialize empty repositories with the selected Odoo branch.
|
|
290
|
-
|
|
291
|
-
Add a minimal Odoo module skeleton to a source repository:
|
|
292
|
-
|
|
293
|
-
For module actions, `--source-type` selects the source directory (`private`, `oca`, or `external`). Default is `private`.
|
|
294
|
-
|
|
295
|
-
```bash
|
|
296
|
-
npx @wpmoo/toolkit add-module \
|
|
297
|
-
--repo odoo_sample_module \
|
|
298
|
-
--module odoo_sample_module_base \
|
|
299
|
-
--source-type oca
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
Remove a module registration while keeping files:
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
npx @wpmoo/toolkit remove-module \
|
|
306
|
-
--repo odoo_sample_module \
|
|
307
|
-
--module odoo_sample_module_base \
|
|
308
|
-
--source-type oca
|
|
309
140
|
```
|
|
310
141
|
|
|
311
|
-
|
|
142
|
+
Module source actions also have direct commands. Default is `private`; pass `--source-type oca` or `--source-type external` for non-private source repositories:
|
|
312
143
|
|
|
313
144
|
```bash
|
|
314
|
-
npx @wpmoo/toolkit
|
|
315
|
-
|
|
316
|
-
--module odoo_sample_module_base \
|
|
317
|
-
--delete-files
|
|
145
|
+
npx @wpmoo/toolkit add-module --repo sale-workflow --module sale_order_line_no_discount --source-type oca
|
|
146
|
+
npx @wpmoo/toolkit remove-module --repo sale-workflow --module sale_order_line_no_discount --source-type oca
|
|
318
147
|
```
|
|
319
148
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
```bash
|
|
323
|
-
npx @wpmoo/toolkit remove-repo --repo odoo_sample_module_reports
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
WPMoo refuses to remove a source repo submodule when that submodule has uncommitted changes.
|
|
327
|
-
|
|
328
|
-
Generated environments also keep a deterministic source manifest at
|
|
329
|
-
`odoo/custom/manifests/sources.yaml`. It mirrors source submodules from
|
|
330
|
-
`.wpmoo/odoo.json` and `.gitmodules`, including source type, path, URL, branch,
|
|
331
|
-
and addon boundaries.
|
|
332
|
-
|
|
333
|
-
Inspect configured sources:
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
npx @wpmoo/toolkit source list
|
|
337
|
-
npx @wpmoo/toolkit source list --json
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
Regenerate the manifest and metadata from the current metadata/gitmodule state:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
|
-
npx @wpmoo/toolkit source sync
|
|
344
|
-
npx @wpmoo/toolkit source sync --json
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
`source add` and `source remove` are direct aliases for the same repository
|
|
348
|
-
operations:
|
|
349
|
-
|
|
350
|
-
```bash
|
|
351
|
-
npx @wpmoo/toolkit source add \
|
|
352
|
-
--repo-url https://github.com/OCA/server-tools.git \
|
|
353
|
-
--source-type oca
|
|
354
|
-
|
|
355
|
-
npx @wpmoo/toolkit source remove --repo server-tools --source-type oca
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
## Status, Doctor, and Recovery
|
|
359
|
-
|
|
360
|
-
`status` is fast and offline. It reads local metadata and files only:
|
|
361
|
-
|
|
362
|
-
```bash
|
|
363
|
-
npx @wpmoo/toolkit status
|
|
364
|
-
npx @wpmoo/toolkit status --json
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
It reports whether the environment is detected, which Odoo version is selected, how many source repos are configured, how many module candidates are present, which core files are missing, and the recommended next action.
|
|
368
|
-
|
|
369
|
-
For automation and VS Code cockpit integration, all of these commands also support
|
|
370
|
-
`--json`:
|
|
149
|
+
For automation and VS Code cockpit integration, selected commands support JSON output:
|
|
371
150
|
|
|
372
151
|
```bash
|
|
373
152
|
npx @wpmoo/toolkit status --json
|
|
@@ -378,143 +157,16 @@ npx @wpmoo/toolkit doctor --json
|
|
|
378
157
|
|
|
379
158
|
JSON output is optional; human-readable output remains the default.
|
|
380
159
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
```bash
|
|
384
|
-
npx @wpmoo/toolkit doctor
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
It validates metadata, engine support, selected compose files, source repo paths,
|
|
388
|
-
source manifest consistency, daily scripts, `.env` settings, Docker CLI access,
|
|
389
|
-
Docker Compose access, GitHub CLI authentication when available, and PostgreSQL
|
|
390
|
-
18 compatibility in compose mount targets (for mounts to
|
|
391
|
-
`/var/lib/postgresql/data` or `/var/lib/postgresql/18/docker`).
|
|
392
|
-
|
|
393
|
-
Use `doctor --fix` for safe file-level repairs. It can normalize PostgreSQL 18
|
|
394
|
-
mount targets and regenerate `odoo/custom/manifests/sources.yaml` from
|
|
395
|
-
metadata plus `.gitmodules`, then it runs doctor again and reports any remaining
|
|
396
|
-
manual issues.
|
|
397
|
-
|
|
398
|
-
Safe reset refreshes generated environment files without deleting product source code:
|
|
399
|
-
|
|
400
|
-
```bash
|
|
401
|
-
npx @wpmoo/toolkit reset --dry-run
|
|
402
|
-
npx @wpmoo/toolkit reset
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
Safe reset updates generated files such as `.wpmoo/odoo.json`, `moo`,
|
|
406
|
-
`.gitignore`, `.env.example`, generated docs, compose assets, and optional
|
|
407
|
-
Agent Skills. Compose overlays like `compose.yaml` and `compose/dev.yaml` are
|
|
408
|
-
also refreshed from the current compose template source.
|
|
409
|
-
|
|
410
|
-
Use `reset --dry-run` first when you want a deterministic preview of refreshed
|
|
411
|
-
files and cleanup warnings without writing to the environment.
|
|
412
|
-
|
|
413
|
-
It does not touch source repo folders under
|
|
414
|
-
`odoo/custom/src/private`, module source code, Git history, remotes, or
|
|
415
|
-
branches. It also preserves local runtime artifacts and custom source layout
|
|
416
|
-
content:
|
|
417
|
-
|
|
418
|
-
- `.env`, `data`, and `backups`
|
|
419
|
-
- `odoo/custom/src/oca`, `odoo/custom/src/external`, `odoo/custom/patches`,
|
|
420
|
-
`odoo/custom/manifests`, and their existing contents
|
|
421
|
-
|
|
422
|
-
Legacy compose template paths from older scaffolds can remain
|
|
423
|
-
(`docs/assets/`, `test/`, `.github/`) until you remove them manually.
|
|
160
|
+
## Documentation
|
|
424
161
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
./moo snapshot devel before-reset
|
|
429
|
-
npx @wpmoo/toolkit reset --dry-run
|
|
430
|
-
npx @wpmoo/toolkit reset
|
|
431
|
-
npx @wpmoo/toolkit doctor --fix
|
|
432
|
-
./moo restore-snapshot --dry-run before-reset devel
|
|
433
|
-
./moo restore-snapshot before-reset devel
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
## External Resources
|
|
437
|
-
|
|
438
|
-
WPMoo Toolkit keeps the package small by copying external resources into generated environments:
|
|
439
|
-
|
|
440
|
-
```text
|
|
441
|
-
gh:wpmoo-org/odoo-docker-compose
|
|
442
|
-
gh:wpmoo-org/odoo-skills
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
Use the default resources:
|
|
446
|
-
|
|
447
|
-
```bash
|
|
448
|
-
npx @wpmoo/toolkit create \
|
|
449
|
-
--product odoo_sample_module \
|
|
450
|
-
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
451
|
-
--agent-skills-template
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
Pin external resource refs:
|
|
455
|
-
|
|
456
|
-
```bash
|
|
457
|
-
npx @wpmoo/toolkit create \
|
|
458
|
-
--product odoo_sample_module \
|
|
459
|
-
--source-repo-url https://github.com/example-org/odoo_sample_module.git \
|
|
460
|
-
--compose-template-ref v0.1.0 \
|
|
461
|
-
--agent-skills-template \
|
|
462
|
-
--agent-skills-template-ref v0.1.0
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
Use local resource clones while developing the resource packages:
|
|
466
|
-
|
|
467
|
-
```bash
|
|
468
|
-
git clone https://github.com/wpmoo-org/odoo-docker-compose ../odoo-docker-compose
|
|
469
|
-
git clone https://github.com/wpmoo-org/odoo-skills ../odoo-skills
|
|
470
|
-
|
|
471
|
-
npx @wpmoo/toolkit create \
|
|
472
|
-
--engine compose \
|
|
473
|
-
--compose-template-url ../odoo-docker-compose \
|
|
474
|
-
--agent-skills-template \
|
|
475
|
-
--agent-skills-template-url ../odoo-skills \
|
|
476
|
-
--product odoo_sample_module \
|
|
477
|
-
--source-repo-url https://github.com/example-org/odoo_sample_module.git
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
More detail: [External Resources](docs/external-resources.md).
|
|
481
|
-
|
|
482
|
-
## Verification
|
|
483
|
-
|
|
484
|
-
Run local package checks from the repository root:
|
|
485
|
-
|
|
486
|
-
```bash
|
|
487
|
-
npm run typecheck
|
|
488
|
-
npm test
|
|
489
|
-
npm run test:coverage
|
|
490
|
-
npm run build
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
Generated environment behavior is covered by the operator-facing matrix in [Generated Environment Verification](docs/generated-environment-verification.md).
|
|
494
|
-
|
|
495
|
-
## Release
|
|
496
|
-
|
|
497
|
-
The normal release path uses the repository helper and GitHub Actions trusted publishing:
|
|
498
|
-
|
|
499
|
-
```bash
|
|
500
|
-
npm run release:check
|
|
501
|
-
npm run typecheck
|
|
502
|
-
npm test
|
|
503
|
-
npm run build
|
|
504
|
-
VERSION="$(node -p "require('./package.json').version")"
|
|
505
|
-
git tag -a "v$VERSION" -m "Release v$VERSION"
|
|
506
|
-
git push origin "v$VERSION"
|
|
507
|
-
```
|
|
162
|
+
- [External Resources](docs/external-resources.md)
|
|
163
|
+
- [Generated Environment Verification](docs/generated-environment-verification.md)
|
|
164
|
+
- Public documentation site: <https://wpmoo.org>
|
|
508
165
|
|
|
509
|
-
|
|
166
|
+
## License
|
|
510
167
|
|
|
511
|
-
|
|
168
|
+
WPMoo Toolkit is free software released under the [MIT License](LICENSE).
|
|
512
169
|
|
|
513
|
-
|
|
170
|
+
## Acknowledgements
|
|
514
171
|
|
|
515
|
-
|
|
516
|
-
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me a Coffee" width="250">
|
|
517
|
-
</a>
|
|
518
|
-
<a href="https://www.patreon.com/wpmoo">
|
|
519
|
-
<img src="docs/assets/patreon-donate.png" alt="Support WPMoo on Patreon" width="250">
|
|
520
|
-
</a>
|
|
172
|
+
WPMoo builds on the work of many open source projects and communities. Thanks to the maintainers and contributors behind Odoo, OCA, Docker Compose, TypeScript, Node.js, Inquirer, Vitest, VitePress, GitHub CLI, npm, and the wider Odoo developer ecosystem.
|
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { confirmCockpitCommandRisk } from './cockpit/safety.js';
|
|
|
14
14
|
import { detectDevelopmentEnvironment } from './environment.js';
|
|
15
15
|
import { commandOdooVersion } from './environment-version.js';
|
|
16
16
|
import { defaultAgentSkillsTemplateUrl } from './external-templates.js';
|
|
17
|
-
import { listEnvironmentDatabases } from './databases.js';
|
|
17
|
+
import { listEnvironmentDatabases, normalizeDatabaseListResult } from './databases.js';
|
|
18
18
|
import { isDailyActionCommand, runDailyAction, runDailyActionWithStyledOutput } from './daily-actions.js';
|
|
19
19
|
import { getDoctorReport, runDoctor } from './doctor.js';
|
|
20
20
|
import { getOriginUrl, realGit } from './git.js';
|
|
@@ -33,6 +33,7 @@ import { backupTargetPath, expectedTargetConfirmation, inspectEnvironmentTarget,
|
|
|
33
33
|
import { getGitHubPrerequisiteStatus, renderGitHubPrerequisiteGuidance, } from './github-prerequisites.js';
|
|
34
34
|
import { checkGitHubRepositories, createGitHubRepositories, manualCreateCommands, } from './repository-preflight.js';
|
|
35
35
|
import { scaffold } from './scaffold.js';
|
|
36
|
+
import { getSystemPrerequisiteStatus, renderSystemPrerequisiteGuidance, } from './system-prerequisites.js';
|
|
36
37
|
import { confirmPrompt, introPrompt, isPromptCancel, notePrompt, outroPrompt, selectPrompt, textPrompt } from './prompts/index.js';
|
|
37
38
|
import { renderBanner } from './templates.js';
|
|
38
39
|
import { checkForUpdate, isUpdateCheckSkipped, restartCli } from './update-check.js';
|
|
@@ -216,6 +217,11 @@ function clearCockpitScreen() {
|
|
|
216
217
|
process.stdout.write('\u001B[2J\u001B[H');
|
|
217
218
|
}
|
|
218
219
|
}
|
|
220
|
+
function clearPrerequisiteScreen() {
|
|
221
|
+
if (process.stdout.isTTY) {
|
|
222
|
+
process.stdout.write('\u001B[3J\u001B[2J\u001B[H');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
219
225
|
const ANSI_ACTION = '\u001B[38;2;226;184;96m';
|
|
220
226
|
const ANSI_SUCCESS = '\u001B[32m';
|
|
221
227
|
const ANSI_DEFAULT_FOREGROUND = '\u001B[39m';
|
|
@@ -834,7 +840,7 @@ async function removeModuleOptionsFromPrompts(showIntro = true, cancelAction = '
|
|
|
834
840
|
throw new Error('No Odoo modules found');
|
|
835
841
|
}
|
|
836
842
|
const deleteFiles = await confirmPrompt({
|
|
837
|
-
message: menuPromptMessage('Delete module files too?
|
|
843
|
+
message: menuPromptMessage('Delete module files too?', cancelAction),
|
|
838
844
|
active: 'Y',
|
|
839
845
|
inactive: 'n',
|
|
840
846
|
initialValue: false,
|
|
@@ -927,6 +933,40 @@ async function ensureGitHubRepositories(options, interactive) {
|
|
|
927
933
|
handleCancel(visibility, 'exit');
|
|
928
934
|
await createGitHubRepositories(missing, visibility);
|
|
929
935
|
}
|
|
936
|
+
async function ensureSystemPrerequisites(interactive) {
|
|
937
|
+
while (true) {
|
|
938
|
+
const status = await getSystemPrerequisiteStatus();
|
|
939
|
+
if (status.ok) {
|
|
940
|
+
return true;
|
|
941
|
+
}
|
|
942
|
+
const guidance = renderSystemPrerequisiteGuidance(status);
|
|
943
|
+
if (!interactive) {
|
|
944
|
+
throw new Error(guidance);
|
|
945
|
+
}
|
|
946
|
+
clearPrerequisiteScreen();
|
|
947
|
+
console.log(renderStartupBanner());
|
|
948
|
+
console.log(renderVersionTag());
|
|
949
|
+
console.log();
|
|
950
|
+
console.log(guidance);
|
|
951
|
+
console.log();
|
|
952
|
+
const action = await selectPrompt({
|
|
953
|
+
message: 'If you have installed the prerequisites',
|
|
954
|
+
options: [
|
|
955
|
+
{
|
|
956
|
+
value: 'check-again',
|
|
957
|
+
label: `${renderActionText('Check again')}${dim(' (Enter to re-check again)')}`,
|
|
958
|
+
},
|
|
959
|
+
],
|
|
960
|
+
initialValue: 'check-again',
|
|
961
|
+
loop: false,
|
|
962
|
+
navigationHelp: 'exit',
|
|
963
|
+
});
|
|
964
|
+
handleCancel(action, 'exit');
|
|
965
|
+
if (action === 'check-again') {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
930
970
|
async function ensureNonInteractiveCreateTarget(options) {
|
|
931
971
|
if (options.dryRun) {
|
|
932
972
|
return;
|
|
@@ -943,7 +983,7 @@ async function ensureNonInteractiveCreateTarget(options) {
|
|
|
943
983
|
}
|
|
944
984
|
throw new Error(renderForeignEnvironmentTargetWarning(state));
|
|
945
985
|
}
|
|
946
|
-
async function finishCreateFlow(result, cwd, interactive) {
|
|
986
|
+
async function finishCreateFlow(result, cwd, interactive, checkSystemPrerequisites = true) {
|
|
947
987
|
if (result.kind === 'cancelled') {
|
|
948
988
|
outroPrompt('Create flow cancelled.');
|
|
949
989
|
return;
|
|
@@ -957,6 +997,9 @@ async function finishCreateFlow(result, cwd, interactive) {
|
|
|
957
997
|
return;
|
|
958
998
|
}
|
|
959
999
|
const { options } = result;
|
|
1000
|
+
if (!options.dryRun && checkSystemPrerequisites && !(await ensureSystemPrerequisites(interactive))) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
960
1003
|
await ensureGitHubRepositories(options, interactive);
|
|
961
1004
|
const scaffoldResult = await scaffold(options);
|
|
962
1005
|
if (options.dryRun) {
|
|
@@ -1014,7 +1057,7 @@ function moduleActionTitle(action) {
|
|
|
1014
1057
|
if (action === 'test')
|
|
1015
1058
|
return 'Test module';
|
|
1016
1059
|
if (action === 'lint')
|
|
1017
|
-
return 'Run lint';
|
|
1060
|
+
return 'Run environment lint';
|
|
1018
1061
|
if (action === 'delete')
|
|
1019
1062
|
return 'Delete module';
|
|
1020
1063
|
return 'Module action';
|
|
@@ -1025,7 +1068,7 @@ function moduleActionCompletedLabel(action) {
|
|
|
1025
1068
|
if (action === 'test')
|
|
1026
1069
|
return 'Test';
|
|
1027
1070
|
if (action === 'lint')
|
|
1028
|
-
return '
|
|
1071
|
+
return 'Environment lint';
|
|
1029
1072
|
return 'Action';
|
|
1030
1073
|
}
|
|
1031
1074
|
function commandActionTitle(command) {
|
|
@@ -1034,7 +1077,7 @@ function commandActionTitle(command) {
|
|
|
1034
1077
|
if (command === 'test')
|
|
1035
1078
|
return 'Test module';
|
|
1036
1079
|
if (command === 'lint')
|
|
1037
|
-
return 'Run lint';
|
|
1080
|
+
return 'Run environment lint';
|
|
1038
1081
|
if (command === 'pot')
|
|
1039
1082
|
return 'Generate POT';
|
|
1040
1083
|
return command;
|
|
@@ -1047,7 +1090,7 @@ function commandCompletedLabel(command) {
|
|
|
1047
1090
|
if (command === 'test')
|
|
1048
1091
|
return 'Test';
|
|
1049
1092
|
if (command === 'lint')
|
|
1050
|
-
return '
|
|
1093
|
+
return 'Environment lint';
|
|
1051
1094
|
if (command === 'pot')
|
|
1052
1095
|
return 'Generate POT';
|
|
1053
1096
|
return command;
|
|
@@ -1065,7 +1108,8 @@ function dailyActionSelectedLabel(command, argv) {
|
|
|
1065
1108
|
return undefined;
|
|
1066
1109
|
}
|
|
1067
1110
|
async function selectDatabaseArg(cwd, message, fallback, options = {}) {
|
|
1068
|
-
const
|
|
1111
|
+
const databaseResult = normalizeDatabaseListResult(await listEnvironmentDatabases(cwd, options));
|
|
1112
|
+
const databases = databaseResult.databases;
|
|
1069
1113
|
if (databases.length > 0) {
|
|
1070
1114
|
const selected = await selectPrompt({
|
|
1071
1115
|
message: menuPromptMessage(message, 'back'),
|
|
@@ -1081,7 +1125,7 @@ async function selectDatabaseArg(cwd, message, fallback, options = {}) {
|
|
|
1081
1125
|
}
|
|
1082
1126
|
}
|
|
1083
1127
|
return asString(await textPrompt({
|
|
1084
|
-
message: menuPromptMessage(message
|
|
1128
|
+
message: menuPromptMessage(databaseResult.ok ? message : `${message} (database list unavailable; enter manually)`, 'back'),
|
|
1085
1129
|
defaultValue: fallback,
|
|
1086
1130
|
placeholder: fallback,
|
|
1087
1131
|
}), fallback, 'back');
|
|
@@ -1169,12 +1213,12 @@ async function runSelectedModuleDailyAction(action, module, cwd) {
|
|
|
1169
1213
|
if (!command) {
|
|
1170
1214
|
return false;
|
|
1171
1215
|
}
|
|
1172
|
-
return runDailyActionResultPage(command, moduleDailyActionArgs(action, module), cwd, moduleActionTitle(action), module.moduleName, moduleActionCompletedLabel(action));
|
|
1216
|
+
return runDailyActionResultPage(command, moduleDailyActionArgs(action, module), cwd, moduleActionTitle(action), action === 'lint' ? undefined : module.moduleName, moduleActionCompletedLabel(action));
|
|
1173
1217
|
}
|
|
1174
1218
|
async function runSelectedModuleAction(action, module, cwd) {
|
|
1175
1219
|
if (action === 'delete') {
|
|
1176
1220
|
const deleteFiles = await confirmPrompt({
|
|
1177
|
-
message: menuPromptMessage('Delete module files too?
|
|
1221
|
+
message: menuPromptMessage('Delete module files too?', 'back'),
|
|
1178
1222
|
active: 'Y',
|
|
1179
1223
|
inactive: 'n',
|
|
1180
1224
|
initialValue: false,
|
|
@@ -1379,7 +1423,10 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
1379
1423
|
const detection = await detectDevelopmentEnvironment(cwd);
|
|
1380
1424
|
if (!detection.isEnvironment) {
|
|
1381
1425
|
await showStartup(argv, skipUpdateCheck);
|
|
1382
|
-
|
|
1426
|
+
if (!(await ensureSystemPrerequisites(true))) {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
await finishCreateFlow(await optionsFromPrompts(), cwd, true, false);
|
|
1383
1430
|
return;
|
|
1384
1431
|
}
|
|
1385
1432
|
let lastStatus = 'Last: Ready';
|
|
@@ -1546,7 +1593,10 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
|
|
|
1546
1593
|
await finishCreateFlow({ kind: 'create', options }, cwd, false);
|
|
1547
1594
|
return;
|
|
1548
1595
|
}
|
|
1549
|
-
|
|
1596
|
+
if (!(await ensureSystemPrerequisites(true))) {
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
await finishCreateFlow(await optionsFromPrompts(), cwd, true, false);
|
|
1550
1600
|
}
|
|
1551
1601
|
export function isCliEntrypoint(metaUrl, argvPath = process.argv[1]) {
|
|
1552
1602
|
if (!argvPath)
|
|
@@ -41,7 +41,7 @@ export const cockpitCommands = [
|
|
|
41
41
|
dailyCommand('install', 'modules', 'Install module', 'Install one or more Odoo modules into a database.', ['install module']),
|
|
42
42
|
dailyCommand('update', 'modules', 'Update module', 'Update one or more Odoo modules in a database.', ['upgrade']),
|
|
43
43
|
dailyCommand('test', 'modules', 'Run tests', 'Run Odoo tests for one or more modules.', ['tests', 'pytest']),
|
|
44
|
-
dailyCommand('lint', 'modules', 'Run lint', 'Run the configured
|
|
44
|
+
dailyCommand('lint', 'modules', 'Run environment lint', 'Run the configured environment lint checks.', ['check', 'quality']),
|
|
45
45
|
dailyCommand('pot', 'modules', 'Generate POT', 'Generate translation template files for a module.', ['translation', 'i18n']),
|
|
46
46
|
dailyCommand('psql', 'database', 'Open psql', 'Open a PostgreSQL prompt for an environment database.', ['postgres', 'sql']),
|
|
47
47
|
dailyCommand('snapshot', 'database', 'Create snapshot', 'Create a database snapshot.', ['backup', 'dump']),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listEnvironmentDatabases } from '../databases.js';
|
|
1
|
+
import { listEnvironmentDatabases, normalizeDatabaseListResult, } from '../databases.js';
|
|
2
2
|
import { listModulesInSourceRepo } from '../module-actions.js';
|
|
3
3
|
import { listModuleRepos } from '../repo-actions.js';
|
|
4
4
|
import { listSources } from '../source-actions.js';
|
|
@@ -84,7 +84,8 @@ async function optionalTextArg(deps, message, fallback) {
|
|
|
84
84
|
}), fallback, deps);
|
|
85
85
|
}
|
|
86
86
|
async function databaseArg(cwd, deps, message, fallback, options = {}) {
|
|
87
|
-
const
|
|
87
|
+
const databaseResult = normalizeDatabaseListResult(await deps.databases(cwd, options));
|
|
88
|
+
const databases = databaseResult.databases;
|
|
88
89
|
if (databases.length > 0) {
|
|
89
90
|
const selected = await deps.list({
|
|
90
91
|
message: menuPromptMessage(message, 'back'),
|
|
@@ -99,7 +100,7 @@ async function databaseArg(cwd, deps, message, fallback, options = {}) {
|
|
|
99
100
|
return String(selected);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
|
-
return optionalTextArg(deps, message
|
|
103
|
+
return optionalTextArg(deps, databaseResult.ok ? message : `${message} (database list unavailable; enter manually)`, fallback);
|
|
103
104
|
}
|
|
104
105
|
async function optionalModules(cwd, deps) {
|
|
105
106
|
const modules = await detectedModules(cwd);
|
|
@@ -4,7 +4,7 @@ const moduleActions = [
|
|
|
4
4
|
{ id: 'delete', label: 'Delete module' },
|
|
5
5
|
{ id: 'update', label: 'Update' },
|
|
6
6
|
{ id: 'test', label: 'Test' },
|
|
7
|
-
{ id: 'lint', label: '
|
|
7
|
+
{ id: 'lint', label: 'Run environment lint' },
|
|
8
8
|
];
|
|
9
9
|
function defaultCancelHandler(value, action) {
|
|
10
10
|
handlePromptCancel(isPromptCancel(value), action);
|
package/dist/databases.js
CHANGED
|
@@ -22,6 +22,12 @@ export function parseDatabaseListOutput(output, options = {}) {
|
|
|
22
22
|
}
|
|
23
23
|
return databases;
|
|
24
24
|
}
|
|
25
|
+
export function normalizeDatabaseListResult(result) {
|
|
26
|
+
if (Array.isArray(result)) {
|
|
27
|
+
return { ok: true, databases: result };
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
25
31
|
export async function listEnvironmentDatabases(cwd, options = {}) {
|
|
26
32
|
const queryLiteral = JSON.stringify(listDatabasesQuery);
|
|
27
33
|
const command = [
|
|
@@ -32,15 +38,21 @@ export async function listEnvironmentDatabases(cwd, options = {}) {
|
|
|
32
38
|
return new Promise((resolve) => {
|
|
33
39
|
const child = spawn('bash', ['-lc', command], {
|
|
34
40
|
cwd,
|
|
35
|
-
stdio: ['ignore', 'pipe', '
|
|
41
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
36
42
|
});
|
|
37
43
|
let output = '';
|
|
44
|
+
let errorOutput = '';
|
|
38
45
|
child.stdout?.on('data', (chunk) => {
|
|
39
46
|
output += chunk.toString('utf8');
|
|
40
47
|
});
|
|
41
|
-
child.on('
|
|
48
|
+
child.stderr?.on('data', (chunk) => {
|
|
49
|
+
errorOutput += chunk.toString('utf8');
|
|
50
|
+
});
|
|
51
|
+
child.on('error', (error) => resolve({ ok: false, databases: [], error: error.message }));
|
|
42
52
|
child.on('close', (code) => {
|
|
43
|
-
resolve(code === 0
|
|
53
|
+
resolve(code === 0
|
|
54
|
+
? { ok: true, databases: parseDatabaseListOutput(output, options) }
|
|
55
|
+
: { ok: false, databases: [], error: errorOutput.trim() || `Database list command exited with ${code}` });
|
|
44
56
|
});
|
|
45
57
|
});
|
|
46
58
|
}
|
package/dist/help.js
CHANGED
|
@@ -91,6 +91,8 @@ Cockpit:
|
|
|
91
91
|
|
|
92
92
|
Wizard local-only path:
|
|
93
93
|
Run npx @wpmoo/toolkit from a workspace directory to open the create wizard.
|
|
94
|
+
Before setup starts, WPMoo checks Git, Docker, Docker Compose, and Docker Engine.
|
|
95
|
+
If required tools are missing, WPMoo offers installer guidance before writing files.
|
|
94
96
|
Choose any environment folder; the default is ./<product>_dev.
|
|
95
97
|
Skip Git/GitHub connection to create a local-only environment.
|
|
96
98
|
Add source repos later from the cockpit or with add-repo.
|
package/dist/prompts/index.js
CHANGED
|
@@ -129,22 +129,27 @@ function renderedNavigationWarning(navigationWarning) {
|
|
|
129
129
|
const warning = typeof navigationWarning === 'function' ? navigationWarning() : navigationWarning;
|
|
130
130
|
return warning ? `\u001B[2m\u001B[38;2;226;184;96m${warning}\u001B[0m` : undefined;
|
|
131
131
|
}
|
|
132
|
-
function hiddenSelectTheme(disabledError, navigationHelp = 'exit', navigationWarning) {
|
|
133
|
-
const keysHelpTip = navigationHelp === 'back'
|
|
132
|
+
function hiddenSelectTheme(disabledError, navigationHelp = 'exit', navigationWarning, hideMessage = true) {
|
|
133
|
+
const keysHelpTip = navigationHelp === 'back'
|
|
134
|
+
? '↑↓ navigate • ⏎ select • Esc to go back'
|
|
135
|
+
: '↑↓ navigate • ⏎ select • Ctrl+C exit';
|
|
136
|
+
const style = {
|
|
137
|
+
highlight: (text) => text,
|
|
138
|
+
disabled: (text) => styleText('dim', text.replace(/ \(disabled\)$/u, ''), { validateStream: false }),
|
|
139
|
+
keysHelpTip: () => {
|
|
140
|
+
const warning = renderedNavigationWarning(navigationWarning);
|
|
141
|
+
return warning ? `${warning}\n${keysHelpTip}` : keysHelpTip;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
if (hideMessage) {
|
|
145
|
+
style.message = () => '';
|
|
146
|
+
}
|
|
134
147
|
return {
|
|
135
148
|
prefix: '',
|
|
136
149
|
icon: {
|
|
137
150
|
cursor: '\u001B[38;2;226;184;96m❯\u001B[39m',
|
|
138
151
|
},
|
|
139
|
-
style
|
|
140
|
-
message: () => '',
|
|
141
|
-
highlight: (text) => text,
|
|
142
|
-
disabled: (text) => styleText('dim', text.replace(/ \(disabled\)$/u, ''), { validateStream: false }),
|
|
143
|
-
keysHelpTip: () => {
|
|
144
|
-
const warning = renderedNavigationWarning(navigationWarning);
|
|
145
|
-
return warning ? `${warning}\n${keysHelpTip}` : keysHelpTip;
|
|
146
|
-
},
|
|
147
|
-
},
|
|
152
|
+
style,
|
|
148
153
|
i18n: disabledError ? { disabledError } : undefined,
|
|
149
154
|
};
|
|
150
155
|
}
|
|
@@ -157,13 +162,10 @@ function withHiddenSelectMessage(config) {
|
|
|
157
162
|
return config;
|
|
158
163
|
}
|
|
159
164
|
const { disabledError, hideMessage: _hideMessage, navigationHelp, navigationWarning, escapeBehavior: _escapeBehavior, ...inquirerConfig } = config;
|
|
160
|
-
if (!config.hideMessage) {
|
|
161
|
-
return inquirerConfig;
|
|
162
|
-
}
|
|
163
165
|
return {
|
|
164
166
|
...inquirerConfig,
|
|
165
|
-
message: '',
|
|
166
|
-
theme: hiddenSelectTheme(disabledError, navigationHelp, navigationWarning),
|
|
167
|
+
message: config.hideMessage ? '' : inquirerConfig.message,
|
|
168
|
+
theme: hiddenSelectTheme(disabledError, navigationHelp, navigationWarning, Boolean(config.hideMessage)),
|
|
167
169
|
};
|
|
168
170
|
}
|
|
169
171
|
function asInquirerConfirmConfig(options) {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
const minimumNodeVersion = '20.17.0';
|
|
3
|
+
const ANSI_RESET = '\u001B[0m';
|
|
4
|
+
const ANSI_DEFAULT_FOREGROUND = '\u001B[39m';
|
|
5
|
+
const ANSI_DIM_YELLOW = '\u001B[2m\u001B[38;2;226;184;96m';
|
|
6
|
+
const ANSI_STRONG_YELLOW = '\u001B[38;2;226;184;96m';
|
|
7
|
+
const ANSI_CYAN = '\u001B[36m';
|
|
8
|
+
const ANSI_GREEN = '\u001B[32m';
|
|
9
|
+
const ANSI_LIGHT_GREEN = '\u001B[38;2;125;231;152m';
|
|
10
|
+
const ANSI_RED = '\u001B[38;2;224;92;120m';
|
|
11
|
+
const ANSI_DIM = '\u001B[2m';
|
|
12
|
+
export const realSystemCommandRunner = (command, args) => new Promise((resolve, reject) => {
|
|
13
|
+
execFile(command, args, { windowsHide: true }, (error, stdout, stderr) => {
|
|
14
|
+
if (error) {
|
|
15
|
+
reject(error);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
resolve({ stdout, stderr });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
function parseVersionParts(value) {
|
|
22
|
+
const [major = '0', minor = '0', patch = '0'] = value.replace(/^v/u, '').split('.');
|
|
23
|
+
return [Number(major) || 0, Number(minor) || 0, Number(patch) || 0];
|
|
24
|
+
}
|
|
25
|
+
function isNodeVersionSupported(version) {
|
|
26
|
+
const current = parseVersionParts(version);
|
|
27
|
+
const minimum = parseVersionParts(minimumNodeVersion);
|
|
28
|
+
for (let index = 0; index < minimum.length; index += 1) {
|
|
29
|
+
if (current[index] > minimum[index])
|
|
30
|
+
return true;
|
|
31
|
+
if (current[index] < minimum[index])
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function forcedMissingTools(env) {
|
|
37
|
+
return new Set((env.WPMOO_TEST_MISSING_TOOLS ?? '')
|
|
38
|
+
.split(/[,\s]+/u)
|
|
39
|
+
.map((tool) => tool.trim().toLowerCase())
|
|
40
|
+
.filter(Boolean));
|
|
41
|
+
}
|
|
42
|
+
function issueForCheck(check) {
|
|
43
|
+
if (check.status === 'found')
|
|
44
|
+
return undefined;
|
|
45
|
+
return { tool: check.tool, reason: check.status };
|
|
46
|
+
}
|
|
47
|
+
async function checkCommand(runner, tool, label, command, args, forcedMissing, missingAlias = tool) {
|
|
48
|
+
if (forcedMissing.has(tool) || forcedMissing.has(missingAlias)) {
|
|
49
|
+
return { tool, label, status: 'missing' };
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const result = await runner(command, args);
|
|
53
|
+
return {
|
|
54
|
+
tool,
|
|
55
|
+
label,
|
|
56
|
+
status: 'found',
|
|
57
|
+
detail: result.stdout.trim() || undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { tool, label, status: 'missing' };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function checkDockerEngine(runner, forcedMissing) {
|
|
65
|
+
if (forcedMissing.has('docker-engine')) {
|
|
66
|
+
return { tool: 'docker-engine', label: 'Docker Engine', status: 'not-running' };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const result = await runner('docker', ['info', '--format', '{{.ServerVersion}}']);
|
|
70
|
+
return {
|
|
71
|
+
tool: 'docker-engine',
|
|
72
|
+
label: 'Docker Engine',
|
|
73
|
+
status: 'found',
|
|
74
|
+
detail: result.stdout.trim() || undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return { tool: 'docker-engine', label: 'Docker Engine', status: 'not-running' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function getSystemPrerequisiteStatus(options = {}) {
|
|
82
|
+
const runner = options.runner ?? realSystemCommandRunner;
|
|
83
|
+
const env = options.env ?? process.env;
|
|
84
|
+
const forcedMissing = forcedMissingTools(env);
|
|
85
|
+
const checks = [];
|
|
86
|
+
const nodeVersion = options.nodeVersion ?? process.versions.node;
|
|
87
|
+
checks.push({
|
|
88
|
+
tool: 'node',
|
|
89
|
+
label: 'Node.js 20+',
|
|
90
|
+
status: isNodeVersionSupported(nodeVersion) ? 'found' : 'unsupported-version',
|
|
91
|
+
detail: `v${nodeVersion}`,
|
|
92
|
+
});
|
|
93
|
+
checks.push(await checkCommand(runner, 'git', 'Git', 'git', ['--version'], forcedMissing));
|
|
94
|
+
const dockerCheck = await checkCommand(runner, 'docker', 'Docker Desktop', 'docker', ['--version'], forcedMissing, 'docker-desktop');
|
|
95
|
+
checks.push(dockerCheck);
|
|
96
|
+
if (dockerCheck.status === 'found') {
|
|
97
|
+
checks.push(await checkCommand(runner, 'docker-compose', 'Docker Compose', 'docker', ['compose', 'version'], forcedMissing, 'compose'));
|
|
98
|
+
checks.push(await checkDockerEngine(runner, forcedMissing));
|
|
99
|
+
}
|
|
100
|
+
const issues = checks.map(issueForCheck).filter((issue) => Boolean(issue));
|
|
101
|
+
return {
|
|
102
|
+
ok: issues.length === 0,
|
|
103
|
+
checks,
|
|
104
|
+
issues,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function statusLabel(check) {
|
|
108
|
+
if (check.status === 'found')
|
|
109
|
+
return 'ok';
|
|
110
|
+
if (check.status === 'not-running')
|
|
111
|
+
return 'Not running';
|
|
112
|
+
if (check.status === 'unsupported-version')
|
|
113
|
+
return 'Unsupported version';
|
|
114
|
+
return 'Missing';
|
|
115
|
+
}
|
|
116
|
+
function hasIssue(status, tool) {
|
|
117
|
+
return status.issues.some((issue) => issue.tool === tool);
|
|
118
|
+
}
|
|
119
|
+
function supportsAnsi() {
|
|
120
|
+
return Boolean(process.stdout.isTTY) && process.env.NO_COLOR === undefined;
|
|
121
|
+
}
|
|
122
|
+
function ansi(value, open, close = ANSI_DEFAULT_FOREGROUND) {
|
|
123
|
+
if (!supportsAnsi())
|
|
124
|
+
return value;
|
|
125
|
+
return `${open}${value}${close}`;
|
|
126
|
+
}
|
|
127
|
+
function dim(value) {
|
|
128
|
+
return ansi(value, ANSI_DIM, ANSI_RESET);
|
|
129
|
+
}
|
|
130
|
+
function cyan(value) {
|
|
131
|
+
return ansi(value, ANSI_CYAN);
|
|
132
|
+
}
|
|
133
|
+
function green(value) {
|
|
134
|
+
return ansi(value, ANSI_GREEN);
|
|
135
|
+
}
|
|
136
|
+
function red(value) {
|
|
137
|
+
return ansi(value, ANSI_RED);
|
|
138
|
+
}
|
|
139
|
+
function mutedWarning(value) {
|
|
140
|
+
return ansi(value, ANSI_DIM_YELLOW, ANSI_RESET);
|
|
141
|
+
}
|
|
142
|
+
function yellow(value) {
|
|
143
|
+
return ansi(value, ANSI_STRONG_YELLOW);
|
|
144
|
+
}
|
|
145
|
+
function okText() {
|
|
146
|
+
return ansi('ok', ANSI_LIGHT_GREEN);
|
|
147
|
+
}
|
|
148
|
+
function downloadUrlForCheck(check) {
|
|
149
|
+
if (check.status === 'found') {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (check.tool === 'node') {
|
|
153
|
+
return 'https://nodejs.org/en/download';
|
|
154
|
+
}
|
|
155
|
+
if (check.tool === 'git') {
|
|
156
|
+
return 'https://git-scm.com/downloads';
|
|
157
|
+
}
|
|
158
|
+
if (check.tool === 'docker' || check.tool === 'docker-compose') {
|
|
159
|
+
return 'https://www.docker.com/products/docker-desktop/';
|
|
160
|
+
}
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
function renderStatusLine(check) {
|
|
164
|
+
const symbol = check.status === 'found' ? green('✓') : red('✕');
|
|
165
|
+
const url = downloadUrlForCheck(check);
|
|
166
|
+
const status = check.status === 'found' ? okText() : url ? `${yellow('↗')} ${cyan(url)}` : dim(statusLabel(check));
|
|
167
|
+
return `${symbol} ${cyan(check.label.padEnd(18))} ${status}`;
|
|
168
|
+
}
|
|
169
|
+
export function renderSystemPrerequisiteGuidance(status) {
|
|
170
|
+
if (status.ok) {
|
|
171
|
+
return 'All required system prerequisites are available.';
|
|
172
|
+
}
|
|
173
|
+
const lines = [
|
|
174
|
+
'Required tools before environment setup starts',
|
|
175
|
+
'',
|
|
176
|
+
...status.checks.map(renderStatusLine),
|
|
177
|
+
'',
|
|
178
|
+
];
|
|
179
|
+
if (hasIssue(status, 'docker-engine')) {
|
|
180
|
+
lines.push(mutedWarning('Docker Desktop is installed, but Docker Engine is not running.'));
|
|
181
|
+
lines.push(mutedWarning('Start Docker Desktop, then check again.'));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
lines.push(mutedWarning('Environment setup has not started yet.'));
|
|
185
|
+
lines.push(mutedWarning('Install the missing tools, restart your terminal if PATH changed,'));
|
|
186
|
+
lines.push(mutedWarning('start Docker Desktop, then run WPMoo Toolkit again.'));
|
|
187
|
+
}
|
|
188
|
+
return lines.join('\n');
|
|
189
|
+
}
|
package/dist/templates.js
CHANGED
|
@@ -285,6 +285,9 @@ const ANSI_WARNING = '\u001B[33m';
|
|
|
285
285
|
const ANSI_DEFAULT_FOREGROUND = '\u001B[39m';
|
|
286
286
|
const ANSI_RESET = '\u001B[0m';
|
|
287
287
|
const BANNER_TAGLINE = 'Development, staging and production workflows for Odoo projects.';
|
|
288
|
+
function shouldRenderBannerColor(options) {
|
|
289
|
+
return options.color ?? process.env.NO_COLOR === undefined;
|
|
290
|
+
}
|
|
288
291
|
function gradientColor(column, width) {
|
|
289
292
|
const ratio = width <= 1 ? 0 : column / (width - 1);
|
|
290
293
|
const [startR, startG, startB] = BANNER_GRADIENT_START;
|
|
@@ -294,7 +297,10 @@ function gradientColor(column, width) {
|
|
|
294
297
|
const b = Math.round(startB + (endB - startB) * ratio);
|
|
295
298
|
return `\u001B[38;2;${r};${g};${b}m`;
|
|
296
299
|
}
|
|
297
|
-
function applyBannerGradient(banner) {
|
|
300
|
+
function applyBannerGradient(banner, color) {
|
|
301
|
+
if (!color) {
|
|
302
|
+
return banner;
|
|
303
|
+
}
|
|
298
304
|
const lines = banner.split('\n');
|
|
299
305
|
return lines
|
|
300
306
|
.map((line) => {
|
|
@@ -305,28 +311,40 @@ function applyBannerGradient(banner) {
|
|
|
305
311
|
})
|
|
306
312
|
.join('\n');
|
|
307
313
|
}
|
|
308
|
-
function renderDimInfo(value) {
|
|
314
|
+
function renderDimInfo(value, color) {
|
|
315
|
+
if (!color)
|
|
316
|
+
return value;
|
|
309
317
|
return `${ANSI_DIM}${ANSI_INFO}${value}${ANSI_RESET}`;
|
|
310
318
|
}
|
|
311
|
-
function renderMetaInfo(value) {
|
|
319
|
+
function renderMetaInfo(value, color) {
|
|
320
|
+
if (!color)
|
|
321
|
+
return value;
|
|
312
322
|
return `${ANSI_META}${value}${ANSI_RESET}`;
|
|
313
323
|
}
|
|
314
|
-
function renderSuccessInfo(value) {
|
|
324
|
+
function renderSuccessInfo(value, color) {
|
|
325
|
+
if (!color)
|
|
326
|
+
return value;
|
|
315
327
|
return `${ANSI_SUCCESS}${value}${ANSI_DEFAULT_FOREGROUND}`;
|
|
316
328
|
}
|
|
317
|
-
function renderErrorInfo(value) {
|
|
329
|
+
function renderErrorInfo(value, color) {
|
|
330
|
+
if (!color)
|
|
331
|
+
return value;
|
|
318
332
|
return `${ANSI_ERROR}${value}${ANSI_DEFAULT_FOREGROUND}`;
|
|
319
333
|
}
|
|
320
|
-
function renderWarningInfo(value) {
|
|
334
|
+
function renderWarningInfo(value, color) {
|
|
335
|
+
if (!color)
|
|
336
|
+
return value;
|
|
321
337
|
return `${ANSI_WARNING}${value}${ANSI_DEFAULT_FOREGROUND}`;
|
|
322
338
|
}
|
|
323
|
-
function renderTaglineInfo(value) {
|
|
339
|
+
function renderTaglineInfo(value, color) {
|
|
340
|
+
if (!color)
|
|
341
|
+
return value;
|
|
324
342
|
return `${ANSI_TAGLINE}${value}${ANSI_RESET}`;
|
|
325
343
|
}
|
|
326
|
-
function renderBannerDetail(value) {
|
|
344
|
+
function renderBannerDetail(value, color) {
|
|
327
345
|
const match = /^(Environment|Status|Last):(.*)$/u.exec(value);
|
|
328
346
|
if (!match) {
|
|
329
|
-
return renderDimInfo(value);
|
|
347
|
+
return renderDimInfo(value, color);
|
|
330
348
|
}
|
|
331
349
|
const label = match[1];
|
|
332
350
|
const detail = match[2] ?? '';
|
|
@@ -336,36 +354,37 @@ function renderBannerDetail(value) {
|
|
|
336
354
|
const marker = statusMatch[1] ?? '';
|
|
337
355
|
const message = statusMatch[2] ?? '';
|
|
338
356
|
const renderMarker = message === 'Services running' ? renderSuccessInfo : renderWarningInfo;
|
|
339
|
-
return `${renderMetaInfo(`${label}
|
|
357
|
+
return `${renderMetaInfo(`${label}:`, color)} ${renderMarker(marker, color)}${renderTaglineInfo(` ${message}`, color)}`;
|
|
340
358
|
}
|
|
341
359
|
}
|
|
342
360
|
if (label === 'Last') {
|
|
343
361
|
const completedMatch = /^(.*?)( ✓ completed)$/u.exec(detail);
|
|
344
362
|
if (completedMatch) {
|
|
345
|
-
return `${renderMetaInfo(`${label}
|
|
363
|
+
return `${renderMetaInfo(`${label}:`, color)}${renderDimInfo(completedMatch[1] ?? '', color)}${renderSuccessInfo(completedMatch[2] ?? '', color)}`;
|
|
346
364
|
}
|
|
347
365
|
const errorMatch = /^(.*?)( ✗ Error)(: .*)?$/u.exec(detail);
|
|
348
366
|
if (errorMatch) {
|
|
349
367
|
return [
|
|
350
|
-
renderMetaInfo(`${label}
|
|
351
|
-
renderDimInfo(errorMatch[1] ?? ''),
|
|
352
|
-
renderErrorInfo(errorMatch[2] ?? ''),
|
|
353
|
-
renderTaglineInfo(errorMatch[3] ?? ''),
|
|
368
|
+
renderMetaInfo(`${label}:`, color),
|
|
369
|
+
renderDimInfo(errorMatch[1] ?? '', color),
|
|
370
|
+
renderErrorInfo(errorMatch[2] ?? '', color),
|
|
371
|
+
renderTaglineInfo(errorMatch[3] ?? '', color),
|
|
354
372
|
].join('');
|
|
355
373
|
}
|
|
356
374
|
}
|
|
357
|
-
return `${renderMetaInfo(`${label}
|
|
375
|
+
return `${renderMetaInfo(`${label}:`, color)}${renderDimInfo(detail, color)}`;
|
|
358
376
|
}
|
|
359
377
|
export function renderBanner(details = [], options = {}) {
|
|
360
|
-
const
|
|
378
|
+
const color = shouldRenderBannerColor(options);
|
|
379
|
+
const title = `${applyBannerGradient('WPMoo Toolkit', color)}${options.version ? ` ${renderDimInfo(options.version, color)}` : ''}`;
|
|
361
380
|
const header = [
|
|
362
381
|
title,
|
|
363
|
-
applyBannerGradient('Workflow Platform · Micro Object Oriented'),
|
|
364
|
-
renderTaglineInfo(BANNER_TAGLINE),
|
|
365
|
-
applyBannerGradient('━'.repeat(BANNER_TAGLINE.length)),
|
|
382
|
+
applyBannerGradient('Workflow Platform · Micro Object Oriented', color),
|
|
383
|
+
renderTaglineInfo(BANNER_TAGLINE, color),
|
|
384
|
+
applyBannerGradient('━'.repeat(BANNER_TAGLINE.length), color),
|
|
366
385
|
].join('\n');
|
|
367
|
-
const detailsBlock = details.length > 0 ? `\n${details.map((line) => renderBannerDetail(line)).join('\n')}` : '';
|
|
368
|
-
return `\n${ANSI_BOLD}${header}${ANSI_RESET}${detailsBlock}`;
|
|
386
|
+
const detailsBlock = details.length > 0 ? `\n${details.map((line) => renderBannerDetail(line, color)).join('\n')}` : '';
|
|
387
|
+
return color ? `\n${ANSI_BOLD}${header}${ANSI_RESET}${detailsBlock}` : `\n${header}${detailsBlock}`;
|
|
369
388
|
}
|
|
370
389
|
export function renderGitignore() {
|
|
371
390
|
return `# macOS/editor noise
|