@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.
Files changed (46) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +519 -0
  3. package/dist/addons-yaml.js +59 -0
  4. package/dist/args.js +259 -0
  5. package/dist/cli.js +1039 -0
  6. package/dist/cockpit/command-palette.js +23 -0
  7. package/dist/cockpit/command-registry.js +91 -0
  8. package/dist/cockpit/daily-prompts.js +177 -0
  9. package/dist/cockpit/menu.js +99 -0
  10. package/dist/cockpit/safety.js +22 -0
  11. package/dist/compose-layout.js +118 -0
  12. package/dist/daily-actions.js +190 -0
  13. package/dist/doctor.js +519 -0
  14. package/dist/environment-context.js +10 -0
  15. package/dist/environment-version.js +5 -0
  16. package/dist/environment.js +136 -0
  17. package/dist/external-assets.js +153 -0
  18. package/dist/external-templates.js +86 -0
  19. package/dist/git.js +98 -0
  20. package/dist/github.js +87 -0
  21. package/dist/help.js +157 -0
  22. package/dist/menu-navigation.js +67 -0
  23. package/dist/module-actions.js +114 -0
  24. package/dist/odoo-versions.js +1 -0
  25. package/dist/path-validation.js +50 -0
  26. package/dist/prompt-copy.js +8 -0
  27. package/dist/prompt-repositories.js +34 -0
  28. package/dist/prompts/index.js +174 -0
  29. package/dist/repo-actions.js +158 -0
  30. package/dist/repo-url.js +27 -0
  31. package/dist/repository-preflight.js +46 -0
  32. package/dist/safe-reset.js +217 -0
  33. package/dist/scaffold.js +161 -0
  34. package/dist/source-actions.js +65 -0
  35. package/dist/source-manifest.js +338 -0
  36. package/dist/status.js +239 -0
  37. package/dist/templates.js +758 -0
  38. package/dist/types.js +1 -0
  39. package/dist/update-check.js +106 -0
  40. package/dist/version.js +19 -0
  41. package/docs/assets/patreon-donate.png +0 -0
  42. package/docs/assets/wpmoo-banner.png +0 -0
  43. package/docs/external-resources.md +136 -0
  44. package/docs/generated-environment-verification.md +140 -0
  45. package/docs/handoff.md +29 -0
  46. package/package.json +65 -0
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,106 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { execa } from 'execa';
3
+ export const realNpm = {
4
+ async run(args) {
5
+ const result = await execa('npm', args, args[0] === 'view' ? { timeout: 5000 } : {});
6
+ return { stdout: result.stdout, stderr: result.stderr };
7
+ },
8
+ };
9
+ function truthyEnv(value) {
10
+ return value !== undefined && ['1', 'true', 'yes', 'y'].includes(value.toLowerCase().trim());
11
+ }
12
+ export function isUpdateCheckSkipped(argv, env = process.env) {
13
+ if (argv.includes('--no-update-check'))
14
+ return true;
15
+ if (truthyEnv(env.WPMOO_SKIP_UPDATE_CHECK))
16
+ return true;
17
+ return false;
18
+ }
19
+ function numericParts(version) {
20
+ return version
21
+ .replace(/^v/, '')
22
+ .split('-', 1)[0]
23
+ .split('.')
24
+ .map((part) => Number.parseInt(part, 10) || 0);
25
+ }
26
+ export function compareVersions(currentVersion, latestVersion) {
27
+ const current = numericParts(currentVersion);
28
+ const latest = numericParts(latestVersion);
29
+ const length = Math.max(current.length, latest.length);
30
+ for (let index = 0; index < length; index += 1) {
31
+ const currentPart = current[index] ?? 0;
32
+ const latestPart = latest[index] ?? 0;
33
+ if (currentPart !== latestPart) {
34
+ return currentPart - latestPart;
35
+ }
36
+ }
37
+ return 0;
38
+ }
39
+ function parseNpmPackageInfo(stdout) {
40
+ const trimmed = stdout.trim();
41
+ if (!trimmed) {
42
+ throw new Error('npm did not return package metadata');
43
+ }
44
+ try {
45
+ const parsed = JSON.parse(trimmed);
46
+ if (typeof parsed === 'object' && parsed !== null) {
47
+ const record = parsed;
48
+ const version = typeof record.version === 'string' ? record.version.trim() : '';
49
+ const dist = record.dist;
50
+ const nestedTarball = typeof dist === 'object' && dist !== null && typeof dist.tarball === 'string'
51
+ ? String(dist.tarball).trim()
52
+ : '';
53
+ const dottedTarball = typeof record['dist.tarball'] === 'string' ? record['dist.tarball'].trim() : '';
54
+ const tarball = nestedTarball || dottedTarball;
55
+ if (version && tarball) {
56
+ return { version, tarball };
57
+ }
58
+ }
59
+ }
60
+ catch {
61
+ // Fall through to the validation error below.
62
+ }
63
+ throw new Error('npm did not return a package version and tarball');
64
+ }
65
+ async function viewPackageInfo(packageSpecValue, runner) {
66
+ const result = await runner.run(['view', packageSpecValue, 'version', 'dist.tarball', '--json']);
67
+ return parseNpmPackageInfo(result.stdout);
68
+ }
69
+ export async function checkForUpdate(packageName, currentVersion, runner = realNpm) {
70
+ try {
71
+ const latest = await viewPackageInfo(packageSpec(packageName, 'latest'), runner);
72
+ if (compareVersions(currentVersion, latest.version) < 0) {
73
+ const exact = await viewPackageInfo(packageSpec(packageName, latest.version), runner);
74
+ if (exact.version !== latest.version || !exact.tarball) {
75
+ throw new Error(`npm metadata for ${packageSpec(packageName, latest.version)} did not validate`);
76
+ }
77
+ return { status: 'update-available', currentVersion, latestVersion: exact.version, tarball: exact.tarball };
78
+ }
79
+ return { status: 'current', currentVersion, latestVersion: latest.version };
80
+ }
81
+ catch {
82
+ return { status: 'unavailable', currentVersion };
83
+ }
84
+ }
85
+ export function packageSpec(packageName, version) {
86
+ return `${packageName}@${version}`;
87
+ }
88
+ export async function installLatestPackage(packageName, version, runner = realNpm) {
89
+ await runner.run(['install', '-g', packageSpec(packageName, version)]);
90
+ }
91
+ export function restartArgs(packageName, version, argv) {
92
+ return ['exec', '--yes', '--package', packageSpec(packageName, version), '--', 'wpmoo', ...argv];
93
+ }
94
+ export function restartEnvironment(env = process.env) {
95
+ return { ...env, WPMOO_SKIP_UPDATE_CHECK: '1' };
96
+ }
97
+ export async function restartCli(packageName, version, argv) {
98
+ const child = spawn('npm', restartArgs(packageName, version, argv), {
99
+ env: restartEnvironment(),
100
+ stdio: 'inherit',
101
+ });
102
+ return new Promise((resolve, reject) => {
103
+ child.on('error', reject);
104
+ child.on('exit', (code) => resolve(code));
105
+ });
106
+ }
@@ -0,0 +1,19 @@
1
+ import { readFileSync } from 'node:fs';
2
+ function readPackageJson() {
3
+ return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
4
+ }
5
+ export function packageVersion() {
6
+ return readPackageJson().version;
7
+ }
8
+ export function renderVersion() {
9
+ const packageJson = readPackageJson();
10
+ return `${packageJson.name} ${packageJson.version}`;
11
+ }
12
+ export function packageName() {
13
+ return readPackageJson().name;
14
+ }
15
+ export function renderVersionTag(latestVersion) {
16
+ const current = packageVersion();
17
+ const updateSuffix = latestVersion ? ` -> v.${latestVersion} available` : '';
18
+ return `\u001B[33mv.${current}${updateSuffix}\u001B[0m`;
19
+ }
Binary file
Binary file
@@ -0,0 +1,136 @@
1
+ # External Resources
2
+
3
+ WPMoo does not embed large Docker Compose resources or Agent Skill text inside
4
+ the TypeScript CLI. The CLI copies standalone external resources from their own
5
+ repositories/packages into generated environments, while those resources can also
6
+ be used independently.
7
+
8
+ ## Repositories/packages
9
+
10
+ ```text
11
+ gh:wpmoo-org/odoo-docker-compose
12
+ npm:@wpmoo/odoo-skills
13
+ gh:wpmoo-org/odoo-skills
14
+ ```
15
+
16
+ ## Compose resource
17
+
18
+ `wpmoo-org/odoo-docker-compose` now exposes a compact generated-environment payload
19
+ under `resources/generated-env/`:
20
+
21
+ ```text
22
+ compose.yaml
23
+ compose/dev.yaml
24
+ compose/stage.yaml
25
+ compose/prod.yaml
26
+ config/odoo/odoo.conf
27
+ resources/odoo/entrypoint.sh
28
+ ```
29
+
30
+ `@wpmoo/toolkit` prefers that compact payload first when copying compose assets.
31
+ For pinned older refs that do not provide `resources/generated-env/`, the CLI
32
+ falls back to the legacy repository-root layout (`docker-compose_<version>.yml`
33
+ and related files) for compatibility.
34
+
35
+ Standalone usage with the compact payload:
36
+
37
+ ```bash
38
+ git clone https://github.com/wpmoo-org/odoo-docker-compose
39
+ cd odoo-docker-compose
40
+ mkdir -p ../my_product_dev
41
+ cp -R resources/generated-env/. ../my_product_dev/
42
+ cp .env.example ../my_product_dev/.env
43
+ cd ../my_product_dev
44
+ ./scripts/up.sh
45
+ ```
46
+
47
+ WPMoo CLI usage with the default remote source:
48
+
49
+ ```bash
50
+ npx @wpmoo/toolkit create \
51
+ --engine compose \
52
+ --product my_product \
53
+ --source-repo-url https://github.com/example-org/my_product.git
54
+ ```
55
+
56
+ During resource development, use a local clone:
57
+
58
+ ```bash
59
+ git clone https://github.com/wpmoo-org/odoo-docker-compose ../odoo-docker-compose
60
+
61
+ npx @wpmoo/toolkit create \
62
+ --engine compose \
63
+ --compose-template-url ../odoo-docker-compose \
64
+ --product my_product \
65
+ --source-repo-url https://github.com/example-org/my_product.git
66
+ ```
67
+
68
+ ## Agent Skills resource
69
+
70
+ `@wpmoo/odoo-skills` is generic and intentionally not project-specific:
71
+
72
+ ```text
73
+ skills/odoo-oca/SKILL.md
74
+ skills/odoo-open-core/SKILL.md
75
+ skills/odoo-porting/SKILL.md
76
+ ```
77
+
78
+ Standalone Pi package usage:
79
+
80
+ ```bash
81
+ pi install npm:@wpmoo/odoo-skills
82
+ ```
83
+
84
+ Standalone npx project-local install:
85
+
86
+ ```bash
87
+ npx @wpmoo/odoo-skills --target /path/to/project
88
+ ```
89
+
90
+ WPMoo CLI can also copy those skills into generated environments from the default
91
+ remote source:
92
+
93
+ ```bash
94
+ npx @wpmoo/toolkit create \
95
+ --product my_product \
96
+ --source-repo-url https://github.com/example-org/my_product.git \
97
+ --agent-skills-template
98
+ ```
99
+
100
+ During skill development, use a local clone:
101
+
102
+ ```bash
103
+ git clone https://github.com/wpmoo-org/odoo-skills ../odoo-skills
104
+
105
+ npx @wpmoo/toolkit create \
106
+ --product my_product \
107
+ --source-repo-url https://github.com/example-org/my_product.git \
108
+ --agent-skills-template \
109
+ --agent-skills-template-url ../odoo-skills
110
+ ```
111
+
112
+ Generated project-local skills are placed under:
113
+
114
+ ```text
115
+ .agents/skills/
116
+ ```
117
+
118
+ Project or module-specific guidance should live in that project/module's own
119
+ `AGENTS.md` or custom skill files.
120
+
121
+ ## References and pins
122
+
123
+ Remote Git sources can be pinned with refs:
124
+
125
+ ```bash
126
+ npx @wpmoo/toolkit create \
127
+ --engine compose \
128
+ --compose-template-ref v0.1.0 \
129
+ --agent-skills-template \
130
+ --agent-skills-template-ref v0.1.0 \
131
+ --product my_product \
132
+ --source-repo-url https://github.com/example-org/my_product.git
133
+ ```
134
+
135
+ The CLI supports local directories and Git-style resource sources such as
136
+ `gh:owner/repo`, HTTPS Git URLs, and SSH Git URLs.
@@ -0,0 +1,140 @@
1
+ # Generated Environment Verification
2
+
3
+ ## Scope
4
+
5
+ This matrix covers disposable generated development environments only. It is for
6
+ local and CI verification of generated artifacts and command behavior. It does
7
+ not validate staging or production deployments.
8
+
9
+ ## Command split
10
+
11
+ - Use `npx @wpmoo/toolkit ...` for package/operator commands (`create`,
12
+ `add-repo`, `remove-repo`, `add-module`, `remove-module`, `doctor`, `reset`).
13
+ - Use `./moo ...` inside a generated environment for daily local compose
14
+ actions delegated to `./scripts/*.sh`.
15
+
16
+ ## Verification matrix
17
+
18
+ | Area | Contract | Primary command(s) |
19
+ | --- | --- | --- |
20
+ | Scaffold files and metadata | Generated environment includes expected files and `.wpmoo/odoo.json` metadata. | `npx @wpmoo/toolkit create ...` |
21
+ | Compose resource files | Compact compose layout is present (`compose.yaml` + environment overlays under `compose/`), plus config/resources/scripts. | `npx @wpmoo/toolkit create ...` |
22
+ | `./moo` delegation | `./moo` dispatches fixed daily actions to the matching script and preserves argument pass-through. | `./moo <action> ...` |
23
+ | Doctor checks | Metadata, compose files, scripts, source repo paths, and local tooling checks behave as expected. | `npx @wpmoo/toolkit doctor` or `./moo doctor` |
24
+ | Doctor safe fixes | Safe file-level fixes are applied only with `--fix`, then doctor runs again and reports any remaining manual issues. | `npx @wpmoo/toolkit doctor --fix` |
25
+ | Generated Postgres checks | For PostgreSQL 18 environments, doctor validates db mount targets avoid old PG image-specific paths and can normalize safe targets with `--fix`. | `npx @wpmoo/toolkit doctor`, `npx @wpmoo/toolkit doctor --fix` |
26
+ | Source repo add/remove | Source repository registration and submodule lifecycle behave correctly. | `npx @wpmoo/toolkit add-repo ...`, `npx @wpmoo/toolkit remove-repo ...` |
27
+ | Source manifest sync | Source repo metadata, `.gitmodules`, and `odoo/custom/manifests/sources.yaml` stay aligned. | `npx @wpmoo/toolkit source list`, `npx @wpmoo/toolkit source sync` |
28
+ | Module add/remove | Module registration changes are applied to the selected source repo config. | `npx @wpmoo/toolkit add-module ...`, `npx @wpmoo/toolkit remove-module ...` |
29
+ | Safe reset | Generated files are refreshed (including `compose.yaml` overlays and env example) without deleting source module code. Local runtime/data directories and custom source layout content are preserved; legacy user-editable paths from older templates may remain and are reported for manual cleanup. | `npx @wpmoo/toolkit reset --dry-run`, `npx @wpmoo/toolkit reset` |
30
+ | Snapshot/restore and lint/pot | These actions are delegated by `./moo` to compose scripts. Restore preview, snapshot retention, and stage/prod destructive guards are preserved by the package argument layer. | `./moo snapshot ...`, `./moo restore-snapshot --dry-run ...`, `./moo restore-snapshot ...`, `./moo lint`, `./moo pot ...` |
31
+
32
+ ## Compact compose checks
33
+
34
+ Verify the generated environment includes at least:
35
+
36
+ ```text
37
+ compose.yaml
38
+ compose/dev.yaml
39
+ compose/stage.yaml
40
+ compose/prod.yaml
41
+ config/odoo/odoo.conf
42
+ resources/odoo/entrypoint.sh
43
+ ```
44
+
45
+ Default local development uses `compose.yaml` plus `compose/dev.yaml`.
46
+ `WPMOO_ENV=stage` or `WPMOO_ENV=prod` must only be used after production-grade
47
+ secrets and volumes are configured.
48
+
49
+ When `WPMOO_ENV=stage` or `WPMOO_ENV=prod`, generated compose scripts refuse
50
+ destructive database actions such as `resetdb` and real `restore-snapshot`
51
+ unless `.env` explicitly sets `WPMOO_ALLOW_DESTRUCTIVE=1`.
52
+
53
+ For PostgreSQL 18 environments (including `POSTGRES_IMAGE=postgres:18`), ensure db
54
+ volume and tmpfs mount targets use `/var/lib/postgresql` directly:
55
+
56
+ ```text
57
+ - volumes:
58
+ - db_data:/var/lib/postgresql
59
+ ```
60
+
61
+ Paths such as `/var/lib/postgresql/data` and `/var/lib/postgresql/18/docker` are
62
+ no longer accepted by the package `doctor` check.
63
+
64
+ `doctor --fix` may rewrite these safe mount targets to `/var/lib/postgresql`.
65
+ It does not upgrade existing database data; if a real PostgreSQL major upgrade
66
+ is involved, use PostgreSQL upgrade tooling first.
67
+
68
+ ## Safe reset policy
69
+
70
+ Safe reset intentionally avoids deleting user-editable legacy paths from old
71
+ compose templates. Preview output must warn when these paths may remain:
72
+
73
+ ```text
74
+ docs/assets/
75
+ test/
76
+ .github/
77
+ ```
78
+
79
+ In addition, safe reset preserves local runtime and source-data state while refreshing
80
+ generated and compose assets:
81
+
82
+ ```text
83
+ .env
84
+ data/
85
+ backups/
86
+ odoo/custom/src/oca/
87
+ odoo/custom/src/external/
88
+ odoo/custom/patches/
89
+ odoo/custom/manifests/
90
+ ```
91
+
92
+ Run `npx @wpmoo/toolkit reset --dry-run` before writing changes when you need to
93
+ review the generated file refresh plan.
94
+
95
+ ## Snapshot policy
96
+
97
+ Use restore preview before a destructive restore:
98
+
99
+ ```bash
100
+ ./moo restore-snapshot --dry-run <snapshot-name> [db]
101
+ ```
102
+
103
+ `WPMOO_SNAPSHOT_RETENTION_COUNT` may be set to a positive integer to prune old
104
+ snapshot manifests and their matching dump/filestore files after a new snapshot
105
+ is written.
106
+
107
+ ## Source manifest checks
108
+
109
+ Generated environments include `odoo/custom/manifests/sources.yaml`. The manifest
110
+ records each source repository's type (`private`, `oca`, or `external`), path,
111
+ URL, Odoo branch, and addon boundaries.
112
+
113
+ Use `source list` to inspect the current manifest view:
114
+
115
+ ```bash
116
+ npx @wpmoo/toolkit source list
117
+ ```
118
+
119
+ Use `source sync` after manual submodule or metadata repair to regenerate the
120
+ manifest and normalize `.wpmoo/odoo.json` source entries:
121
+
122
+ ```bash
123
+ npx @wpmoo/toolkit source sync
124
+ ```
125
+
126
+ `doctor` fails when manifest entries, metadata entries, and registered source
127
+ submodule paths diverge. `doctor --fix` can regenerate
128
+ `odoo/custom/manifests/sources.yaml` from metadata plus `.gitmodules` when the
129
+ manifest is missing, unreadable, or stale.
130
+
131
+ ## Local verification commands
132
+
133
+ Run from the `wpmoo-toolkit` repository root:
134
+
135
+ ```bash
136
+ npm run typecheck
137
+ npm test
138
+ npm run test:coverage
139
+ npm run build
140
+ ```
@@ -0,0 +1,29 @@
1
+ # Historical Handoff Notes
2
+
3
+ This file is an archive pointer for pre-0.8.37 handoff notes. Those notes
4
+ included a stale direct publish attempt and must not be used as current release
5
+ guidance.
6
+
7
+ Current release path:
8
+
9
+ ```bash
10
+ npm run release:check
11
+ npm run typecheck
12
+ npm test
13
+ npm run build
14
+ VERSION="$(node -p "require('./package.json').version")"
15
+ git tag -a "v$VERSION" -m "Release v$VERSION"
16
+ git push origin "v$VERSION"
17
+ ```
18
+
19
+ If `npm run release:check` bumps `package.json` and `package-lock.json`, commit
20
+ and push that version bump first, then rerun the release check before tagging.
21
+
22
+ Publishing is handled by the `Publish` GitHub Actions workflow through npm
23
+ Trusted Publishing after the tag is pushed. Do not run `npm publish` manually
24
+ unless a coordinator explicitly requests a fallback.
25
+
26
+ Current command standard:
27
+
28
+ - Use `npx @wpmoo/toolkit ...` for package/operator commands.
29
+ - Use generated environment `./moo ...` for local compose daily commands.
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@wpmoo/toolkit",
3
+ "version": "0.9.0",
4
+ "description": "WPMoo Toolkit for development, staging, and production lifecycle workflows.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/wpmoo-org/wpmoo-toolkit.git"
9
+ },
10
+ "homepage": "https://github.com/wpmoo-org/wpmoo-toolkit",
11
+ "bugs": {
12
+ "url": "https://github.com/wpmoo-org/wpmoo-toolkit/issues"
13
+ },
14
+ "readmeFilename": "README.md",
15
+ "main": "./dist/cli.js",
16
+ "exports": "./dist/cli.js",
17
+ "keywords": [
18
+ "odoo",
19
+ "wpmoo",
20
+ "wpmoo toolkit",
21
+ "toolkit",
22
+ "odoo development",
23
+ "odoo cli",
24
+ "odoo lifecycle",
25
+ "odoo dev workflow",
26
+ "odoo staging workflow",
27
+ "odoo production workflow",
28
+ "odoo docker",
29
+ "odoo docker compose",
30
+ "odoo skills"
31
+ ],
32
+ "bin": {
33
+ "wpmoo": "dist/cli.js"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "docs/assets",
38
+ "docs/*.md"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20.17"
42
+ },
43
+ "scripts": {
44
+ "prebuild": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "release:check": "bash scripts/release-check.sh",
47
+ "smoke:published": "bash scripts/smoke-published.sh",
48
+ "test": "vitest run",
49
+ "test:coverage": "vitest run --coverage",
50
+ "typecheck": "tsc -p tsconfig.json --noEmit"
51
+ },
52
+ "dependencies": {
53
+ "@inquirer/prompts": "^8.4.3",
54
+ "@inquirer/search": "^4.1.9",
55
+ "@inquirer/select": "^5.1.5",
56
+ "execa": "^9.6.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^24.0.0",
60
+ "@vitest/coverage-v8": "^3.2.4",
61
+ "typescript": "^5.8.0",
62
+ "vitest": "^3.0.0"
63
+ },
64
+ "license": "MIT"
65
+ }