@wpmoo/toolkit 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +519 -0
- package/dist/addons-yaml.js +59 -0
- package/dist/args.js +259 -0
- package/dist/cli.js +1039 -0
- package/dist/cockpit/command-palette.js +23 -0
- package/dist/cockpit/command-registry.js +91 -0
- package/dist/cockpit/daily-prompts.js +177 -0
- package/dist/cockpit/menu.js +99 -0
- package/dist/cockpit/safety.js +22 -0
- package/dist/compose-layout.js +118 -0
- package/dist/daily-actions.js +190 -0
- package/dist/doctor.js +519 -0
- package/dist/environment-context.js +10 -0
- package/dist/environment-version.js +5 -0
- package/dist/environment.js +136 -0
- package/dist/external-assets.js +153 -0
- package/dist/external-templates.js +86 -0
- package/dist/git.js +98 -0
- package/dist/github.js +87 -0
- package/dist/help.js +157 -0
- package/dist/menu-navigation.js +67 -0
- package/dist/module-actions.js +114 -0
- package/dist/odoo-versions.js +1 -0
- package/dist/path-validation.js +50 -0
- package/dist/prompt-copy.js +8 -0
- package/dist/prompt-repositories.js +34 -0
- package/dist/prompts/index.js +174 -0
- package/dist/repo-actions.js +158 -0
- package/dist/repo-url.js +27 -0
- package/dist/repository-preflight.js +46 -0
- package/dist/safe-reset.js +217 -0
- package/dist/scaffold.js +161 -0
- package/dist/source-actions.js +65 -0
- package/dist/source-manifest.js +338 -0
- package/dist/status.js +239 -0
- package/dist/templates.js +758 -0
- package/dist/types.js +1 -0
- package/dist/update-check.js +106 -0
- package/dist/version.js +19 -0
- package/docs/assets/patreon-donate.png +0 -0
- package/docs/assets/wpmoo-banner.png +0 -0
- package/docs/external-resources.md +136 -0
- package/docs/generated-environment-verification.md +140 -0
- package/docs/handoff.md +29 -0
- package/package.json +65 -0
package/dist/args.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
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 { dailyActionCommands } from './daily-actions.js';
|
|
6
|
+
import { validateAddonName, validateRepoPath } from './path-validation.js';
|
|
7
|
+
import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-url.js';
|
|
8
|
+
const commandNames = new Set([
|
|
9
|
+
'status',
|
|
10
|
+
'create',
|
|
11
|
+
'add-repo',
|
|
12
|
+
'remove-repo',
|
|
13
|
+
'add-module',
|
|
14
|
+
'remove-module',
|
|
15
|
+
'source',
|
|
16
|
+
'reset',
|
|
17
|
+
'doctor',
|
|
18
|
+
...dailyActionCommands,
|
|
19
|
+
]);
|
|
20
|
+
const internalFlags = new Set(['--no-update-check']);
|
|
21
|
+
export function isUpdateCheckFlag(arg) {
|
|
22
|
+
return internalFlags.has(arg);
|
|
23
|
+
}
|
|
24
|
+
export function stripInternalFlags(argv) {
|
|
25
|
+
return argv.filter((arg) => !isUpdateCheckFlag(arg));
|
|
26
|
+
}
|
|
27
|
+
export function commandFromArgs(argv) {
|
|
28
|
+
if (argv.length === 0) {
|
|
29
|
+
return { command: 'menu', argv: [] };
|
|
30
|
+
}
|
|
31
|
+
const [first, ...rest] = argv;
|
|
32
|
+
if (commandNames.has(first)) {
|
|
33
|
+
return { command: first, argv: rest };
|
|
34
|
+
}
|
|
35
|
+
if (first.startsWith('--') || first === '-h' || first === '-v') {
|
|
36
|
+
return { command: 'create', argv };
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Unknown command: ${first}`);
|
|
39
|
+
}
|
|
40
|
+
export function parseArgs(argv) {
|
|
41
|
+
const values = {};
|
|
42
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
43
|
+
const arg = argv[index];
|
|
44
|
+
if (!arg.startsWith('--')) {
|
|
45
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
46
|
+
}
|
|
47
|
+
const [rawKey, inlineValue] = arg.slice(2).split('=', 2);
|
|
48
|
+
const key = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
49
|
+
if (inlineValue !== undefined) {
|
|
50
|
+
values[key] = inlineValue;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const next = argv[index + 1];
|
|
54
|
+
if (!next || next.startsWith('--')) {
|
|
55
|
+
values[key] = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
values[key] = next;
|
|
59
|
+
index += 1;
|
|
60
|
+
}
|
|
61
|
+
return { values };
|
|
62
|
+
}
|
|
63
|
+
function stringValue(values, key) {
|
|
64
|
+
const value = values[key];
|
|
65
|
+
return typeof value === 'string' ? value : undefined;
|
|
66
|
+
}
|
|
67
|
+
function listValue(value, fallback) {
|
|
68
|
+
if (!value)
|
|
69
|
+
return fallback;
|
|
70
|
+
return value
|
|
71
|
+
.split(',')
|
|
72
|
+
.map((item) => item.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
function validateAddons(addons) {
|
|
76
|
+
return addons.map(validateAddonName);
|
|
77
|
+
}
|
|
78
|
+
function valueAfter(argv, index, key) {
|
|
79
|
+
const arg = argv[index];
|
|
80
|
+
const inlineValue = arg.includes('=') ? arg.slice(arg.indexOf('=') + 1) : undefined;
|
|
81
|
+
if (inlineValue !== undefined) {
|
|
82
|
+
return { value: inlineValue, nextIndex: index };
|
|
83
|
+
}
|
|
84
|
+
const next = argv[index + 1];
|
|
85
|
+
if (!next || next.startsWith('--')) {
|
|
86
|
+
throw new Error(`Missing value for --${key}`);
|
|
87
|
+
}
|
|
88
|
+
return { value: next, nextIndex: index + 1 };
|
|
89
|
+
}
|
|
90
|
+
function parseSourceRepos(argv) {
|
|
91
|
+
const repos = [];
|
|
92
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
93
|
+
const arg = argv[index];
|
|
94
|
+
if (!arg.startsWith('--'))
|
|
95
|
+
continue;
|
|
96
|
+
const rawKey = arg.slice(2).split('=', 1)[0];
|
|
97
|
+
const key = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
98
|
+
if (key === 'sourceRepoUrl') {
|
|
99
|
+
const parsed = valueAfter(argv, index, rawKey);
|
|
100
|
+
repos.push({ url: normalizeRepositoryUrl(parsed.value) });
|
|
101
|
+
index = parsed.nextIndex;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (key === 'sourcePath') {
|
|
105
|
+
const current = repos.at(-1);
|
|
106
|
+
if (!current)
|
|
107
|
+
throw new Error('--source-path must follow --source-repo-url');
|
|
108
|
+
const parsed = valueAfter(argv, index, rawKey);
|
|
109
|
+
current.path = parsed.value;
|
|
110
|
+
index = parsed.nextIndex;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (key === 'sourceAddons') {
|
|
114
|
+
const current = repos.at(-1);
|
|
115
|
+
if (!current)
|
|
116
|
+
throw new Error('--source-addons must follow --source-repo-url');
|
|
117
|
+
const parsed = valueAfter(argv, index, rawKey);
|
|
118
|
+
current.addons = listValue(parsed.value, []);
|
|
119
|
+
index = parsed.nextIndex;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return repos.map((repo) => {
|
|
123
|
+
const path = validateRepoPath(repo.path?.trim() || inferRepoPath(repo.url));
|
|
124
|
+
return {
|
|
125
|
+
url: repo.url,
|
|
126
|
+
path,
|
|
127
|
+
addons: validateAddons(repo.addons?.length ? repo.addons : [path]),
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function booleanValue(values, key, fallback) {
|
|
132
|
+
const value = values[key];
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
return fallback;
|
|
135
|
+
if (typeof value === 'boolean')
|
|
136
|
+
return value;
|
|
137
|
+
const normalized = value.toLowerCase().trim();
|
|
138
|
+
if (['true', '1', 'yes', 'y'].includes(normalized))
|
|
139
|
+
return true;
|
|
140
|
+
if (['false', '0', 'no', 'n'].includes(normalized))
|
|
141
|
+
return false;
|
|
142
|
+
throw new Error(`Invalid boolean value for --${key}: ${value}`);
|
|
143
|
+
}
|
|
144
|
+
function visibilityValue(values) {
|
|
145
|
+
const value = stringValue(values, 'repoVisibility') ?? 'private';
|
|
146
|
+
if (value === 'private' || value === 'public') {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Invalid value for --repo-visibility: ${value}`);
|
|
150
|
+
}
|
|
151
|
+
function engineValue(values) {
|
|
152
|
+
const value = stringValue(values, 'engine') ?? 'compose';
|
|
153
|
+
if (value === 'compose') {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Invalid value for --engine: ${value}`);
|
|
157
|
+
}
|
|
158
|
+
function assertNoRemovedDevelopmentPackFlags(argv) {
|
|
159
|
+
for (const arg of argv) {
|
|
160
|
+
const rawKey = arg.startsWith('--') ? arg.slice(2).split('=', 1)[0] : '';
|
|
161
|
+
if (rawKey === 'pack' || rawKey === 'no-packs') {
|
|
162
|
+
throw new Error('Optional development packs were removed. See the WPMoo Development Guidelines in README.md.');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export function defaultTargetForProduct(product, cwd = process.cwd()) {
|
|
167
|
+
const devRepo = `${product}_dev`;
|
|
168
|
+
return basename(cwd) === devRepo ? cwd : resolve(cwd, devRepo);
|
|
169
|
+
}
|
|
170
|
+
export function optionsFromArgs(argv) {
|
|
171
|
+
assertNoRemovedDevelopmentPackFlags(argv);
|
|
172
|
+
const { values } = parseArgs(argv);
|
|
173
|
+
const product = stringValue(values, 'product');
|
|
174
|
+
if (!product) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const parsedSourceRepos = parseSourceRepos(argv);
|
|
178
|
+
const hasLegacySourceConfig = [
|
|
179
|
+
'org',
|
|
180
|
+
'communityRepo',
|
|
181
|
+
'communityRepoUrl',
|
|
182
|
+
'communityAddons',
|
|
183
|
+
'proRepo',
|
|
184
|
+
'proRepoUrl',
|
|
185
|
+
'proAddons',
|
|
186
|
+
].some((key) => values[key] !== undefined);
|
|
187
|
+
if (parsedSourceRepos.length === 0 && !hasLegacySourceConfig) {
|
|
188
|
+
throw new Error('Missing --source-repo-url. Provide at least one source repository URL.');
|
|
189
|
+
}
|
|
190
|
+
const org = stringValue(values, 'org') ?? inferGitHubOwner(parsedSourceRepos[0]?.url ?? '') ?? 'example-org';
|
|
191
|
+
const odooVersion = stringValue(values, 'odooVersion') ?? supportedOdooVersions[0];
|
|
192
|
+
const engine = engineValue(values);
|
|
193
|
+
const installAgentSkillsTemplate = booleanValue(values, 'agentSkillsTemplate', values.agentSkillsTemplateUrl !== undefined);
|
|
194
|
+
const devRepoUrl = normalizeRepositoryUrl(stringValue(values, 'devRepoUrl') ?? `https://github.com/${org}/${product}_dev.git`);
|
|
195
|
+
const devRepo = stringValue(values, 'devRepo') ?? inferRepoPath(devRepoUrl);
|
|
196
|
+
const communityRepo = validateRepoPath(stringValue(values, 'communityRepo') ?? product);
|
|
197
|
+
const proRepo = validateRepoPath(stringValue(values, 'proRepo') ?? `${product}_pro`);
|
|
198
|
+
const targetValue = stringValue(values, 'target');
|
|
199
|
+
const target = targetValue ? resolve(targetValue) : defaultTargetForProduct(product);
|
|
200
|
+
const communityRepoUrl = normalizeRepositoryUrl(stringValue(values, 'communityRepoUrl') ?? `https://github.com/${org}/${communityRepo}.git`);
|
|
201
|
+
const proRepoUrl = normalizeRepositoryUrl(stringValue(values, 'proRepoUrl') ?? `https://github.com/${org}/${proRepo}.git`);
|
|
202
|
+
const communityAddons = validateAddons(listValue(stringValue(values, 'communityAddons'), defaultCommunityAddons(product)));
|
|
203
|
+
const proAddons = validateAddons(listValue(stringValue(values, 'proAddons'), defaultProAddons(product)));
|
|
204
|
+
const hasExplicitProRepo = values.proRepo !== undefined || values.proRepoUrl !== undefined || values.proAddons !== undefined;
|
|
205
|
+
const sourceRepos = parsedSourceRepos.length > 0
|
|
206
|
+
? parsedSourceRepos
|
|
207
|
+
: [
|
|
208
|
+
{
|
|
209
|
+
url: communityRepoUrl,
|
|
210
|
+
path: communityRepo,
|
|
211
|
+
addons: communityAddons,
|
|
212
|
+
},
|
|
213
|
+
...(hasExplicitProRepo
|
|
214
|
+
? [
|
|
215
|
+
{
|
|
216
|
+
url: proRepoUrl,
|
|
217
|
+
path: proRepo,
|
|
218
|
+
addons: proAddons,
|
|
219
|
+
},
|
|
220
|
+
]
|
|
221
|
+
: []),
|
|
222
|
+
].filter((repo) => repo.url && repo.path && repo.addons.length);
|
|
223
|
+
return {
|
|
224
|
+
product,
|
|
225
|
+
org,
|
|
226
|
+
odooVersion,
|
|
227
|
+
engine,
|
|
228
|
+
composeTemplateUrl: stringValue(values, 'composeTemplateUrl') ?? defaultComposeTemplateUrl,
|
|
229
|
+
composeTemplateRef: stringValue(values, 'composeTemplateRef'),
|
|
230
|
+
agentSkillsTemplateUrl: installAgentSkillsTemplate
|
|
231
|
+
? stringValue(values, 'agentSkillsTemplateUrl') ?? defaultAgentSkillsTemplateUrl
|
|
232
|
+
: undefined,
|
|
233
|
+
agentSkillsTemplateRef: stringValue(values, 'agentSkillsTemplateRef'),
|
|
234
|
+
postgresVersion: stringValue(values, 'postgresVersion'),
|
|
235
|
+
httpPort: stringValue(values, 'httpPort'),
|
|
236
|
+
geventPort: stringValue(values, 'geventPort'),
|
|
237
|
+
devRepo,
|
|
238
|
+
devRepoUrl,
|
|
239
|
+
communityRepo,
|
|
240
|
+
proRepo,
|
|
241
|
+
communityRepoUrl,
|
|
242
|
+
proRepoUrl,
|
|
243
|
+
communityAddons,
|
|
244
|
+
proAddons,
|
|
245
|
+
sourceRepos,
|
|
246
|
+
target,
|
|
247
|
+
dryRun: booleanValue(values, 'dryRun', false),
|
|
248
|
+
initEmptyRepos: booleanValue(values, 'initEmptyRepos', false),
|
|
249
|
+
stage: booleanValue(values, 'stage', true),
|
|
250
|
+
createMissingRepos: booleanValue(values, 'createMissingRepos', false),
|
|
251
|
+
repoVisibility: visibilityValue(values),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export function isHelpRequested(argv) {
|
|
255
|
+
return argv.includes('--help') || argv.includes('-h');
|
|
256
|
+
}
|
|
257
|
+
export function isVersionRequested(argv) {
|
|
258
|
+
return argv.includes('--version') || argv.includes('-v');
|
|
259
|
+
}
|