@camunda8/cli 2.8.0-alpha.9 → 3.0.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/EXAMPLES.md +22 -5
- package/PLUGIN-HELP.md +26 -0
- package/README.md +37 -4
- package/dist/command-dispatch.d.ts.map +1 -1
- package/dist/command-dispatch.js +2 -1
- package/dist/command-dispatch.js.map +1 -1
- package/dist/command-registry.d.ts +30 -0
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +34 -1
- package/dist/command-registry.js.map +1 -1
- package/dist/commands/plugins.d.ts +21 -0
- package/dist/commands/plugins.d.ts.map +1 -1
- package/dist/commands/plugins.js +74 -1
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/resource-extensions.d.ts +12 -0
- package/dist/commands/resource-extensions.d.ts.map +1 -1
- package/dist/commands/resource-extensions.js +13 -1
- package/dist/commands/resource-extensions.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +5 -3
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +21 -12
- package/dist/commands/watch.js.map +1 -1
- package/dist/completion.d.ts.map +1 -1
- package/dist/completion.js +140 -13
- package/dist/completion.js.map +1 -1
- package/dist/default-plugins/bpmn/README.md +69 -0
- package/dist/default-plugins/bpmn/c8ctl-plugin.js +125 -0
- package/dist/default-plugins/bpmn/c8ctl-plugin.js.map +1 -0
- package/dist/default-plugins/bpmn/lint.js +259 -0
- package/dist/default-plugins/bpmn/lint.js.map +1 -0
- package/dist/default-plugins/bpmn/package.json +8 -0
- package/dist/default-plugins/cluster/package.json +1 -1
- package/dist/default-plugins/element-template/AGENTS.md +65 -0
- package/dist/default-plugins/element-template/README.md +331 -0
- package/dist/default-plugins/element-template/c8ctl-plugin.js +247 -0
- package/dist/default-plugins/element-template/c8ctl-plugin.js.map +1 -0
- package/dist/default-plugins/element-template/commands/apply.js +298 -0
- package/dist/default-plugins/element-template/commands/apply.js.map +1 -0
- package/dist/default-plugins/element-template/commands/get-properties.js +344 -0
- package/dist/default-plugins/element-template/commands/get-properties.js.map +1 -0
- package/dist/default-plugins/element-template/commands/get.js +107 -0
- package/dist/default-plugins/element-template/commands/get.js.map +1 -0
- package/dist/default-plugins/element-template/commands/info.js +189 -0
- package/dist/default-plugins/element-template/commands/info.js.map +1 -0
- package/dist/default-plugins/element-template/commands/search.js +101 -0
- package/dist/default-plugins/element-template/commands/search.js.map +1 -0
- package/dist/default-plugins/element-template/commands/sync.js +36 -0
- package/dist/default-plugins/element-template/commands/sync.js.map +1 -0
- package/dist/default-plugins/element-template/docs/design.md +145 -0
- package/dist/default-plugins/element-template/helpers.js +404 -0
- package/dist/default-plugins/element-template/helpers.js.map +1 -0
- package/dist/default-plugins/element-template/marketplace.js +396 -0
- package/dist/default-plugins/element-template/marketplace.js.map +1 -0
- package/dist/default-plugins/element-template/package.json +8 -0
- package/dist/default-plugins/element-template/template-ref.js +232 -0
- package/dist/default-plugins/element-template/template-ref.js.map +1 -0
- package/dist/default-plugins/element-template/vendor-src/bundle-entry.js +44 -0
- package/dist/default-plugins/feel/README.md +163 -0
- package/dist/default-plugins/feel/c8ctl-plugin.js +616 -0
- package/dist/default-plugins/feel/c8ctl-plugin.js.map +1 -0
- package/dist/default-plugins/feel/package.json +8 -0
- package/dist/deployments.d.ts +1 -0
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +89 -20
- package/dist/deployments.js.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +156 -5
- package/dist/help.js.map +1 -1
- package/dist/ignore.d.ts +16 -1
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +55 -3
- package/dist/ignore.js.map +1 -1
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +294 -25
- package/dist/index.js.map +1 -1
- package/dist/plugin-loader.d.ts +168 -7
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +284 -21
- package/dist/plugin-loader.js.map +1 -1
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +6 -0
- package/dist/runtime.js.map +1 -1
- package/dist/vendor/bpmn-element-templates.cjs +59639 -0
- package/package.json +20 -7
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `c8ctl element-template apply` — apply an element template to a BPMN
|
|
3
|
+
* element via the prebuilt bpmn-js + bpmn-js-element-templates vendor bundle.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { applySetOverrides, findPropertiesByBindingName, parseArgs, parseSetArg, warnUnmetConditions, } from "../helpers.js";
|
|
10
|
+
import { elementExistsInBpmn, getExecutionPlatformVersion, parseTemplateRef, readBpmnInput, readTemplateFromPathOrUrl, resolveOotbTemplate, } from "../template-ref.js";
|
|
11
|
+
if (!globalThis.c8ctl)
|
|
12
|
+
throw new Error("c8ctl runtime not initialised");
|
|
13
|
+
const c8ctl = globalThis.c8ctl;
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
/**
|
|
17
|
+
* Locate the prebuilt vendor bundle. Plugins live in two places:
|
|
18
|
+
* - dev: default-plugins/element-template/commands/apply.ts
|
|
19
|
+
* vendor: ../../../dist/vendor/bpmn-element-templates.cjs
|
|
20
|
+
* - production: dist/default-plugins/element-template/commands/apply.js
|
|
21
|
+
* vendor: ../../../vendor/bpmn-element-templates.cjs
|
|
22
|
+
*/
|
|
23
|
+
function resolveVendorBundle() {
|
|
24
|
+
const candidates = [
|
|
25
|
+
resolvePath(__dirname, "..", "..", "..", "dist", "vendor", "bpmn-element-templates.cjs"),
|
|
26
|
+
resolvePath(__dirname, "..", "..", "..", "vendor", "bpmn-element-templates.cjs"),
|
|
27
|
+
];
|
|
28
|
+
for (const path of candidates) {
|
|
29
|
+
if (existsSync(path)) {
|
|
30
|
+
return path;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new Error("Vendor bundle not found. Run `npm run build:vendor` to build it.\n" +
|
|
34
|
+
`Searched: ${candidates.join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Find the first child of `extensionElements` whose moddle `$type` matches.
|
|
38
|
+
* Avoids importing bpmn-js's `is()` helper just for one shape check.
|
|
39
|
+
*/
|
|
40
|
+
function findExtensionByType(extensionElements, type) {
|
|
41
|
+
if (!extensionElements) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
// biome-ignore lint/plugin: moddle API contract boundary — get() returns untyped collections
|
|
45
|
+
const values = extensionElements.get("values");
|
|
46
|
+
return values?.find((v) => v.$type === type);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Force the source/value of each `--set` into the corresponding moddle child.
|
|
50
|
+
*
|
|
51
|
+
* bpmn-js-element-templates' applyTemplate preserves existing input/output/
|
|
52
|
+
* header/property values on a re-apply (and unconditionally on Dropdown
|
|
53
|
+
* properties — see ChangeElementTemplateHandler#shouldKeepValue, ref
|
|
54
|
+
* bpmn-io/bpmn-js-properties-panel#767). For a CLI the user expectation is
|
|
55
|
+
* "--set wins". After the first applyTemplate we walk the --set args, locate
|
|
56
|
+
* the moddle entry that backs each binding, and update its value via
|
|
57
|
+
* `modeling.updateModdleProperties` so the change goes through the same
|
|
58
|
+
* command stack the library uses internally.
|
|
59
|
+
*
|
|
60
|
+
* Bindings whose moddle entry doesn't exist yet (because their condition was
|
|
61
|
+
* not met against the pre-apply element state) are skipped here and picked
|
|
62
|
+
* up by the second applyTemplate, which re-evaluates conditions against the
|
|
63
|
+
* now-updated element and creates the missing entries using our mutated
|
|
64
|
+
* `prop.value` as the default.
|
|
65
|
+
*/
|
|
66
|
+
function forceSetValues(modeler, element, template, setArgs) {
|
|
67
|
+
const modeling = modeler.get("modeling");
|
|
68
|
+
// biome-ignore lint/plugin: moddle API contract boundary — get() returns untyped ModdleElement
|
|
69
|
+
const extensionElements = element.businessObject.get("extensionElements");
|
|
70
|
+
if (!extensionElements) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const ioMapping = findExtensionByType(extensionElements, "zeebe:IoMapping");
|
|
74
|
+
const taskHeaders = findExtensionByType(extensionElements, "zeebe:TaskHeaders");
|
|
75
|
+
const taskDefinition = findExtensionByType(extensionElements, "zeebe:TaskDefinition");
|
|
76
|
+
const zeebeProperties = findExtensionByType(extensionElements, "zeebe:Properties");
|
|
77
|
+
for (const arg of setArgs) {
|
|
78
|
+
const { bindingTypeFilter, name, value } = parseSetArg(arg);
|
|
79
|
+
const matches = findPropertiesByBindingName(template.properties, name, bindingTypeFilter);
|
|
80
|
+
for (const prop of matches) {
|
|
81
|
+
updateModdleForProperty(modeling, element, prop, value, {
|
|
82
|
+
ioMapping,
|
|
83
|
+
taskHeaders,
|
|
84
|
+
taskDefinition,
|
|
85
|
+
zeebeProperties,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function updateModdleForProperty(modeling, element, prop, value, containers) {
|
|
91
|
+
const binding = prop.binding;
|
|
92
|
+
if (!binding) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
switch (binding.type) {
|
|
96
|
+
case "zeebe:input": {
|
|
97
|
+
// biome-ignore lint/plugin: moddle API contract boundary — get() returns untyped collections
|
|
98
|
+
const inputs = (containers.ioMapping?.get("inputParameters") ??
|
|
99
|
+
[]);
|
|
100
|
+
const child = inputs.find((p) => p.target === binding.name);
|
|
101
|
+
if (child) {
|
|
102
|
+
modeling.updateModdleProperties(element, child, { source: value });
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
case "zeebe:output": {
|
|
107
|
+
// biome-ignore lint/plugin: moddle API contract boundary — get() returns untyped collections
|
|
108
|
+
const outputs = (containers.ioMapping?.get("outputParameters") ??
|
|
109
|
+
[]);
|
|
110
|
+
const child = outputs.find((p) => p.source === binding.source);
|
|
111
|
+
if (child) {
|
|
112
|
+
modeling.updateModdleProperties(element, child, { target: value });
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
case "zeebe:taskHeader": {
|
|
117
|
+
// biome-ignore lint/plugin: moddle API contract boundary — get() returns untyped collections
|
|
118
|
+
const headers = (containers.taskHeaders?.get("values") ??
|
|
119
|
+
[]);
|
|
120
|
+
const child = headers.find((h) => h.key === binding.key);
|
|
121
|
+
if (child) {
|
|
122
|
+
modeling.updateModdleProperties(element, child, { value });
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
case "zeebe:property": {
|
|
127
|
+
// biome-ignore lint/plugin: moddle API contract boundary — get() returns untyped collections
|
|
128
|
+
const props = (containers.zeebeProperties?.get("properties") ??
|
|
129
|
+
[]);
|
|
130
|
+
const child = props.find((p) => p.name === binding.name);
|
|
131
|
+
if (child) {
|
|
132
|
+
modeling.updateModdleProperties(element, child, { value });
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "zeebe:taskDefinition": {
|
|
137
|
+
if (containers.taskDefinition && binding.property) {
|
|
138
|
+
modeling.updateModdleProperties(element, containers.taskDefinition, {
|
|
139
|
+
[binding.property]: value,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Apply an element template to a BPMN element using bpmn-js-headless and
|
|
148
|
+
* bpmn-js-element-templates (same libraries as Web/Desktop Modeler).
|
|
149
|
+
*
|
|
150
|
+
* Loaded from a prebuilt CJS vendor bundle since the upstream libraries
|
|
151
|
+
* use extensionless ESM imports that Node.js can't resolve without a bundler.
|
|
152
|
+
*
|
|
153
|
+
* Pass `setArgs` to honor `--set` overrides on re-apply. The first
|
|
154
|
+
* applyTemplate uses the (already-mutated) template defaults for fresh
|
|
155
|
+
* properties; `forceSetValues` then overrides preserved values; a second
|
|
156
|
+
* applyTemplate picks up dependents whose conditions just became met.
|
|
157
|
+
*/
|
|
158
|
+
async function applyElementTemplate(xml, template, elementId, setArgs) {
|
|
159
|
+
const vendorPath = resolveVendorBundle();
|
|
160
|
+
const vendor = require(vendorPath);
|
|
161
|
+
const { Modeler, CloudElementTemplatesCoreModule, ZeebeModdleExtension, HeadlessTextRendererModule, } = vendor;
|
|
162
|
+
// HeadlessTextRendererModule overrides bpmn-js's default textRenderer,
|
|
163
|
+
// which would otherwise call document.createElementNS during importXML
|
|
164
|
+
// (to measure external label bounds) and throw "document is not defined"
|
|
165
|
+
// in Node. The errors are non-fatal but produce noisy stack traces.
|
|
166
|
+
const modeler = new Modeler({
|
|
167
|
+
additionalModules: [
|
|
168
|
+
HeadlessTextRendererModule,
|
|
169
|
+
CloudElementTemplatesCoreModule,
|
|
170
|
+
],
|
|
171
|
+
moddleExtensions: { zeebe: ZeebeModdleExtension },
|
|
172
|
+
});
|
|
173
|
+
await modeler.importXML(xml);
|
|
174
|
+
const elementRegistry = modeler.get("elementRegistry");
|
|
175
|
+
const element = elementRegistry.get(elementId);
|
|
176
|
+
if (!element) {
|
|
177
|
+
throw new Error(`Element "${elementId}" not found in the BPMN diagram`);
|
|
178
|
+
}
|
|
179
|
+
const elementTemplates = modeler.get("elementTemplates");
|
|
180
|
+
elementTemplates.set([template]);
|
|
181
|
+
elementTemplates.applyTemplate(element, template);
|
|
182
|
+
if (setArgs.length > 0) {
|
|
183
|
+
forceSetValues(modeler, element, template, setArgs);
|
|
184
|
+
// Second pass so dependents whose conditions are newly met get
|
|
185
|
+
// created with our mutated defaults.
|
|
186
|
+
elementTemplates.applyTemplate(element, template);
|
|
187
|
+
}
|
|
188
|
+
const result = await modeler.saveXML({ format: true });
|
|
189
|
+
return result.xml;
|
|
190
|
+
}
|
|
191
|
+
export async function applySubcommand(args) {
|
|
192
|
+
const logger = c8ctl.getLogger();
|
|
193
|
+
const parsed = parseArgs(args);
|
|
194
|
+
if (parsed.error) {
|
|
195
|
+
throw new Error(parsed.error);
|
|
196
|
+
}
|
|
197
|
+
const [templateArg, elementId, bpmnFilePath] = parsed.positionals;
|
|
198
|
+
if (parsed.positionals.length > 3) {
|
|
199
|
+
throw new Error(`Unexpected argument: ${parsed.positionals[3]}. Usage: c8ctl element-template apply <template> <element-id> [<file.bpmn>]`);
|
|
200
|
+
}
|
|
201
|
+
if (!templateArg) {
|
|
202
|
+
throw new Error("Missing template argument. Usage: c8ctl element-template apply <template> <element-id> [<file.bpmn>]");
|
|
203
|
+
}
|
|
204
|
+
if (!elementId) {
|
|
205
|
+
throw new Error("Missing element-id argument. Usage: c8ctl element-template apply <template> <element-id> [<file.bpmn>]");
|
|
206
|
+
}
|
|
207
|
+
// Reject incompatible flag combinations before reading stdin —
|
|
208
|
+
// otherwise a piped BPMN stream is consumed (and a long-running
|
|
209
|
+
// producer can hang) just to surface a usage error.
|
|
210
|
+
if (parsed.inPlace && !bpmnFilePath) {
|
|
211
|
+
throw new Error("--in-place cannot be used with stdin input");
|
|
212
|
+
}
|
|
213
|
+
const input = await readBpmnInput(bpmnFilePath);
|
|
214
|
+
if (!input) {
|
|
215
|
+
throw new Error("No BPMN input provided. Pass a file path or pipe BPMN XML via stdin.");
|
|
216
|
+
}
|
|
217
|
+
const ref = parseTemplateRef(templateArg);
|
|
218
|
+
if (!ref) {
|
|
219
|
+
throw new Error("Missing template argument.");
|
|
220
|
+
}
|
|
221
|
+
let template;
|
|
222
|
+
if (ref.kind === "id") {
|
|
223
|
+
const executionPlatformVersion = await getExecutionPlatformVersion(input.xml);
|
|
224
|
+
template = await resolveOotbTemplate(ref, { executionPlatformVersion });
|
|
225
|
+
if (ref.version === undefined && !executionPlatformVersion) {
|
|
226
|
+
logger.warn("BPMN has no modeler:executionPlatformVersion — applying latest version " +
|
|
227
|
+
`(${template.version}) of ${ref.id}.`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
template = await readTemplateFromPathOrUrl(ref.value);
|
|
232
|
+
}
|
|
233
|
+
// Validate --set overrides against the template before dry-run or real apply,
|
|
234
|
+
// so `--dry-run --set notAProperty=x` fails just as the real apply would.
|
|
235
|
+
let setProperties = [];
|
|
236
|
+
if (parsed.setArgs.length > 0) {
|
|
237
|
+
setProperties = applySetOverrides(template.properties, parsed.setArgs);
|
|
238
|
+
}
|
|
239
|
+
// Dry-run: describe what would happen without mutating anything
|
|
240
|
+
if (c8ctl.dryRun) {
|
|
241
|
+
// Validate the target element exists so dry-run only reports a successful
|
|
242
|
+
// plan when the real apply would also succeed.
|
|
243
|
+
if (!(await elementExistsInBpmn(input.xml, elementId))) {
|
|
244
|
+
throw new Error(`Element "${elementId}" not found in the BPMN diagram`);
|
|
245
|
+
}
|
|
246
|
+
const fallbackId = ref.kind === "id" ? ref.id : ref.value;
|
|
247
|
+
const info = {
|
|
248
|
+
dryRun: true,
|
|
249
|
+
command: "element-template apply",
|
|
250
|
+
template: {
|
|
251
|
+
id: template.id ?? fallbackId,
|
|
252
|
+
name: template.name,
|
|
253
|
+
version: template.version,
|
|
254
|
+
},
|
|
255
|
+
elementId,
|
|
256
|
+
source: input.source,
|
|
257
|
+
inPlace: parsed.inPlace && !!bpmnFilePath,
|
|
258
|
+
setOverrides: parsed.setArgs,
|
|
259
|
+
};
|
|
260
|
+
if (c8ctl.outputMode === "json") {
|
|
261
|
+
logger.json(info);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
logger.output("Dry run — no changes applied.");
|
|
265
|
+
logger.output(` Template: ${info.template.name ?? info.template.id}${info.template.version != null ? ` v${info.template.version}` : ""}`);
|
|
266
|
+
logger.output(` Element: ${elementId}`);
|
|
267
|
+
logger.output(` Source: ${input.source}`);
|
|
268
|
+
if (info.inPlace) {
|
|
269
|
+
logger.output(` Mode: in-place (would overwrite ${bpmnFilePath})`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
logger.output(" Mode: stdout (would print transformed XML)");
|
|
273
|
+
}
|
|
274
|
+
if (parsed.setArgs.length > 0) {
|
|
275
|
+
logger.output(` --set: ${parsed.setArgs.join(", ")}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
let resultXml;
|
|
281
|
+
try {
|
|
282
|
+
resultXml = await applyElementTemplate(input.xml, template, elementId, parsed.setArgs);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
286
|
+
throw new Error(`Error applying template: ${message}`);
|
|
287
|
+
}
|
|
288
|
+
if (setProperties.length > 0) {
|
|
289
|
+
warnUnmetConditions(logger, resultXml, setProperties);
|
|
290
|
+
}
|
|
291
|
+
if (parsed.inPlace && bpmnFilePath) {
|
|
292
|
+
writeFileSync(resolvePath(bpmnFilePath), resultXml, "utf-8");
|
|
293
|
+
logger.info(`Updated ${bpmnFilePath}`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
process.stdout.write(resultXml);
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../../default-plugins/element-template/commands/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `c8ctl element-template get-properties` — list settable properties of a
|
|
3
|
+
* template. Default render is a condensed table of name + description
|
|
4
|
+
* rows grouped by section. `--detailed` swaps in per-property detail cards.
|
|
5
|
+
*/
|
|
6
|
+
import { styleText } from "node:util";
|
|
7
|
+
import { BINDING_TYPE_SHORTHANDS, globToRegex, } from "../helpers.js";
|
|
8
|
+
import { loadTemplate } from "../template-ref.js";
|
|
9
|
+
import { formatKeyedCard, parseInspectArgs } from "./info.js";
|
|
10
|
+
if (!globalThis.c8ctl)
|
|
11
|
+
throw new Error("c8ctl runtime not initialised");
|
|
12
|
+
const c8ctl = globalThis.c8ctl;
|
|
13
|
+
export async function getPropertiesSubcommand(args) {
|
|
14
|
+
const logger = c8ctl.getLogger();
|
|
15
|
+
const usage = "Usage: c8ctl element-template get-properties <template> [<name>...] [--group <id>...] [--detailed | -d]\n" +
|
|
16
|
+
" Default: condensed list (name + description). --detailed shows full cards.\n" +
|
|
17
|
+
" Names may include shell-style globs — quote to avoid shell expansion: 'auth*'";
|
|
18
|
+
const parsed = parseInspectArgs(args, usage, {
|
|
19
|
+
allowPropertyNames: true,
|
|
20
|
+
allowFilters: true,
|
|
21
|
+
});
|
|
22
|
+
const { template, allDetails, groupLabelMap, sourceByDetail, autoResolvedVersion, } = await loadTemplate(parsed.templateArg);
|
|
23
|
+
if (autoResolvedVersion) {
|
|
24
|
+
logger.warn(`Resolved latest version (${template.version}) of ${template.id}. ` +
|
|
25
|
+
`Use '${template.id}@${template.version}' to pin.`);
|
|
26
|
+
}
|
|
27
|
+
// Resolve positional names if any (literals or globs). Each arg must
|
|
28
|
+
// match at least one property — typos shouldn't silently produce no
|
|
29
|
+
// output. With no positionals, default to all properties.
|
|
30
|
+
let details = parsed.propertyArgs.length > 0
|
|
31
|
+
? parsed.propertyArgs.flatMap((arg) => filterByPropertyArg(allDetails, arg, parsed.templateArg))
|
|
32
|
+
: allDetails;
|
|
33
|
+
// --group intersects with positional matches. Validation of the
|
|
34
|
+
// requested group ids uses the full template (allDetails) so a
|
|
35
|
+
// valid group id on a property the name filter excluded still
|
|
36
|
+
// counts as known — the user should see "no match" for the
|
|
37
|
+
// combination, not "unknown group id".
|
|
38
|
+
details = applyGroupFilter(details, allDetails, parsed.groups, groupLabelMap);
|
|
39
|
+
// Dedupe by object reference — the same property detail object is
|
|
40
|
+
// returned when it matches multiple positionals (e.g. `url` and
|
|
41
|
+
// `u*` together). NOT by binding name+type: distinct properties
|
|
42
|
+
// sometimes share those (operation-conditional duplicates that
|
|
43
|
+
// bind to the same field with different `condition` clauses).
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
details = details.filter((d) => {
|
|
46
|
+
if (seen.has(d)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
seen.add(d);
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
// Each filter is individually validated (positional names via
|
|
53
|
+
// filterByPropertyArg, group ids via applyGroupFilter), but their
|
|
54
|
+
// intersection can still be empty when a property exists somewhere
|
|
55
|
+
// and a group exists, just not together. Surface that as an error
|
|
56
|
+
// to match the README contract that filters error on no-match.
|
|
57
|
+
if (details.length === 0 &&
|
|
58
|
+
(parsed.propertyArgs.length > 0 || parsed.groups.length > 0)) {
|
|
59
|
+
const nameClause = parsed.propertyArgs.length > 0
|
|
60
|
+
? `name${parsed.propertyArgs.length > 1 ? "s" : ""} ${parsed.propertyArgs.map((a) => `"${a}"`).join(", ")}`
|
|
61
|
+
: "";
|
|
62
|
+
const groupClause = parsed.groups.length > 0
|
|
63
|
+
? `group${parsed.groups.length > 1 ? "s" : ""} ${parsed.groups.map((g) => `"${g}"`).join(", ")}`
|
|
64
|
+
: "";
|
|
65
|
+
const what = [nameClause, groupClause].filter(Boolean).join(" and ");
|
|
66
|
+
throw new Error(`No properties match ${what}.`);
|
|
67
|
+
}
|
|
68
|
+
const total = allDetails.length;
|
|
69
|
+
if (c8ctl.outputMode === "json") {
|
|
70
|
+
const projector = parsed.detailed
|
|
71
|
+
? buildShowProperty
|
|
72
|
+
: buildCondensedProperty;
|
|
73
|
+
logger.json(buildJsonPayload(template, details, projector, total, sourceByDetail));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Summary line at the top — gives an at-a-glance answer to "how
|
|
77
|
+
// many am I seeing, out of how many available". Especially useful
|
|
78
|
+
// after a filter to confirm scope.
|
|
79
|
+
logger.output(styleText("dim", `Showing ${details.length} of ${total} properties.`));
|
|
80
|
+
logger.output("");
|
|
81
|
+
if (parsed.detailed) {
|
|
82
|
+
// Detail-card view — full per-property fields.
|
|
83
|
+
for (let i = 0; i < details.length; i++) {
|
|
84
|
+
for (const line of formatPropertyCard(details[i])) {
|
|
85
|
+
logger.output(line);
|
|
86
|
+
}
|
|
87
|
+
if (i < details.length - 1) {
|
|
88
|
+
logger.output("");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Default: condensed list — group heading + name + description.
|
|
94
|
+
renderCondensedTable(details, groupLabelMap, logger);
|
|
95
|
+
// Trailing hint — point users at name filtering and --detailed.
|
|
96
|
+
logger.output("");
|
|
97
|
+
logger.output(styleText("dim", "Filter by name (supports globs):\n" +
|
|
98
|
+
` c8ctl element-template get-properties ${parsed.templateArg} 'auth*' url\n` +
|
|
99
|
+
"For full details on each property:\n" +
|
|
100
|
+
` c8ctl element-template get-properties ${parsed.templateArg} --detailed`));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Project a property into the JSON shape that mirrors the **condensed
|
|
104
|
+
* get-properties text output** — just identification (binding) and the
|
|
105
|
+
* descriptive text the row surfaces. Group is included so consumers
|
|
106
|
+
* can resolve the section heading via the top-level `groups` table.
|
|
107
|
+
*/
|
|
108
|
+
function buildCondensedProperty(prop) {
|
|
109
|
+
return {
|
|
110
|
+
id: prop.id,
|
|
111
|
+
binding: prop.binding,
|
|
112
|
+
label: prop.label,
|
|
113
|
+
description: prop.description,
|
|
114
|
+
group: prop.group,
|
|
115
|
+
choices: prop.choices,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Project a property into the JSON shape that mirrors the
|
|
120
|
+
* **--detailed get-properties card** — every field the card surfaces.
|
|
121
|
+
* Schema field names verbatim.
|
|
122
|
+
*/
|
|
123
|
+
function buildShowProperty(prop) {
|
|
124
|
+
return {
|
|
125
|
+
id: prop.id,
|
|
126
|
+
binding: prop.binding,
|
|
127
|
+
type: prop.type,
|
|
128
|
+
optional: prop.optional,
|
|
129
|
+
value: prop.value,
|
|
130
|
+
feel: prop.feel,
|
|
131
|
+
group: prop.group,
|
|
132
|
+
condition: prop.condition,
|
|
133
|
+
label: prop.label,
|
|
134
|
+
description: prop.description,
|
|
135
|
+
constraints: prop.constraints,
|
|
136
|
+
choices: prop.choices,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* JSON payload for `get-properties`. `projectProp` decides which
|
|
141
|
+
* per-property shape to use (condensed vs detailed) so JSON mirrors
|
|
142
|
+
* text density per mode.
|
|
143
|
+
*/
|
|
144
|
+
function buildJsonPayload(template, details, projectProp, totalCount, sourceByDetail) {
|
|
145
|
+
const properties = details
|
|
146
|
+
.map((d) => sourceByDetail.get(d))
|
|
147
|
+
.filter((p) => Boolean(p))
|
|
148
|
+
.map(projectProp);
|
|
149
|
+
return {
|
|
150
|
+
count: properties.length,
|
|
151
|
+
total: totalCount,
|
|
152
|
+
groups: template.groups ?? [],
|
|
153
|
+
properties,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Filter `details` to those whose `groupId` is in the requested list.
|
|
158
|
+
* Empty list means no filter. Unknown group ids throw with the list of
|
|
159
|
+
* valid ids — group ids are short and bounded so we always show them.
|
|
160
|
+
*
|
|
161
|
+
* `allDetails` is the unfiltered property set from the template; we
|
|
162
|
+
* use it to compute the valid-group set so that a group id which only
|
|
163
|
+
* appears on a property excluded by an earlier name filter still counts
|
|
164
|
+
* as known. Without this, combining a valid name filter and a valid
|
|
165
|
+
* group id from a template with no `groups` table would surface
|
|
166
|
+
* "Unknown group id" rather than the intended empty-intersection error.
|
|
167
|
+
*/
|
|
168
|
+
function applyGroupFilter(details, allDetails, groups, groupLabelMap) {
|
|
169
|
+
if (groups.length === 0) {
|
|
170
|
+
return details;
|
|
171
|
+
}
|
|
172
|
+
// Valid group IDs: the template's declared groups table PLUS any group id
|
|
173
|
+
// referenced directly on any property in the template (templates that use
|
|
174
|
+
// property.group without a matching groups entry still render a heading —
|
|
175
|
+
// so the filter must accept those ids too).
|
|
176
|
+
const validFromTemplate = new Set(groupLabelMap.keys());
|
|
177
|
+
const validFromDetails = new Set(allDetails
|
|
178
|
+
.map((d) => d.groupId)
|
|
179
|
+
.filter((id) => id !== undefined));
|
|
180
|
+
const valid = new Set([...validFromTemplate, ...validFromDetails]);
|
|
181
|
+
for (const g of groups) {
|
|
182
|
+
if (!valid.has(g)) {
|
|
183
|
+
const known = [...valid].join(", ") || "(none defined on this template)";
|
|
184
|
+
throw new Error(`Unknown group id "${g}". Use the template's group id (not its label).\n` +
|
|
185
|
+
`Available group ids: ${known}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const set = new Set(groups);
|
|
189
|
+
return details.filter((d) => d.groupId !== undefined && set.has(d.groupId));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Render the condensed one-line-per-property listing grouped by
|
|
193
|
+
* section.
|
|
194
|
+
*/
|
|
195
|
+
function renderCondensedTable(details, groupLabelMap, logger) {
|
|
196
|
+
const grouped = new Map();
|
|
197
|
+
const ungrouped = [];
|
|
198
|
+
for (const detail of details) {
|
|
199
|
+
if (detail.groupId) {
|
|
200
|
+
const list = grouped.get(detail.groupId) ?? [];
|
|
201
|
+
list.push(detail);
|
|
202
|
+
grouped.set(detail.groupId, list);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
ungrouped.push(detail);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Global name-column width so the description column anchors at the
|
|
209
|
+
// same x-position across all groups.
|
|
210
|
+
const nameWidth = Math.max(0, ...details.map((d) => (d.name ?? "?").length));
|
|
211
|
+
const groupEntries = [...grouped.entries()].map(([groupId, group]) => ({ groupId, group }));
|
|
212
|
+
if (ungrouped.length > 0) {
|
|
213
|
+
groupEntries.push({ groupId: null, group: ungrouped });
|
|
214
|
+
}
|
|
215
|
+
for (let i = 0; i < groupEntries.length; i++) {
|
|
216
|
+
const { groupId, group } = groupEntries[i];
|
|
217
|
+
const label = groupId ? (groupLabelMap.get(groupId) ?? groupId) : "Other";
|
|
218
|
+
// Show "Label (id)" so the --group filter token is self-documenting.
|
|
219
|
+
// Skip the parenthetical when label and id are identical.
|
|
220
|
+
const heading = groupId && label !== groupId
|
|
221
|
+
? `${styleText("bold", label)} ${styleText("dim", `(${groupId})`)}`
|
|
222
|
+
: styleText("bold", label);
|
|
223
|
+
logger.output(heading);
|
|
224
|
+
for (const detail of group) {
|
|
225
|
+
logger.output(formatCondensedRow(detail, { nameWidth }));
|
|
226
|
+
}
|
|
227
|
+
if (i < groupEntries.length - 1) {
|
|
228
|
+
logger.output("");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Build one condensed row: indent · padded name · description-or-label.
|
|
234
|
+
*/
|
|
235
|
+
function formatCondensedRow(detail, { nameWidth }) {
|
|
236
|
+
const NAME_INDENT = " ";
|
|
237
|
+
const COLUMN_GAP = " ";
|
|
238
|
+
const CONT_INDENT = " ".repeat(NAME_INDENT.length + 4);
|
|
239
|
+
const name = (detail.name ?? "?").padEnd(nameWidth);
|
|
240
|
+
const text = detail.description ?? detail.label ?? "";
|
|
241
|
+
const trailing = text ? `${COLUMN_GAP}${styleText("dim", text)}` : "";
|
|
242
|
+
const lines = [`${NAME_INDENT}${styleText("bold", name)}${trailing}`];
|
|
243
|
+
// Surface `id` only when it adds information — i.e. it exists and
|
|
244
|
+
// differs from the binding name. For most properties (id == name or
|
|
245
|
+
// id missing) this stays a single-line row.
|
|
246
|
+
if (detail.id && detail.id !== detail.name) {
|
|
247
|
+
lines.push(styleText("dim", `${CONT_INDENT}[id: ${detail.id}]`));
|
|
248
|
+
}
|
|
249
|
+
if (detail.choices?.length) {
|
|
250
|
+
const values = detail.choices.map((c) => c.value).join(", ");
|
|
251
|
+
lines.push(styleText("dim", `${CONT_INDENT}[choices: ${values}]`));
|
|
252
|
+
}
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Detail card for a single property — keyed two-column layout.
|
|
257
|
+
*/
|
|
258
|
+
function formatPropertyCard(detail) {
|
|
259
|
+
const fields = [];
|
|
260
|
+
if (detail.id) {
|
|
261
|
+
fields.push(["Id", detail.id]);
|
|
262
|
+
}
|
|
263
|
+
fields.push(["Type", detail.type ?? "String"]);
|
|
264
|
+
fields.push(["Required", detail.required ? "yes" : "no"]);
|
|
265
|
+
if (detail.feel) {
|
|
266
|
+
fields.push(["FEEL", detail.feel]);
|
|
267
|
+
}
|
|
268
|
+
if (detail.default !== undefined && detail.default !== "") {
|
|
269
|
+
fields.push(["Default", formatBadgeValue(detail.default)]);
|
|
270
|
+
}
|
|
271
|
+
if (detail.bindingType) {
|
|
272
|
+
fields.push(["Binding", detail.bindingType]);
|
|
273
|
+
}
|
|
274
|
+
if (detail.label || detail.description) {
|
|
275
|
+
const text = [detail.label, detail.description].filter(Boolean).join(" — ");
|
|
276
|
+
fields.push(["Description", text]);
|
|
277
|
+
}
|
|
278
|
+
if (detail.conditionText) {
|
|
279
|
+
fields.push(["Active when", detail.conditionText]);
|
|
280
|
+
}
|
|
281
|
+
else if (detail.condition) {
|
|
282
|
+
fields.push(["Conditional", "(see template definition)"]);
|
|
283
|
+
}
|
|
284
|
+
if (detail.pattern) {
|
|
285
|
+
fields.push(["Pattern", detail.pattern.value]);
|
|
286
|
+
if (detail.pattern.message) {
|
|
287
|
+
fields.push(["", detail.pattern.message]);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (detail.choices?.length) {
|
|
291
|
+
fields.push(["Choices", detail.choices.map((c) => c.value).join(", ")]);
|
|
292
|
+
}
|
|
293
|
+
return formatKeyedCard({
|
|
294
|
+
title: detail.name ?? "?",
|
|
295
|
+
subtitle: detail.group ? `(${detail.group})` : undefined,
|
|
296
|
+
fields,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Filter properties by a `<name>` or `<binding-type>:<name>` argument.
|
|
301
|
+
*/
|
|
302
|
+
function filterByPropertyArg(details, propertyArg, templateArg) {
|
|
303
|
+
const colon = propertyArg.indexOf(":");
|
|
304
|
+
let nameFilter = propertyArg;
|
|
305
|
+
let typeFilter;
|
|
306
|
+
if (colon !== -1) {
|
|
307
|
+
const prefix = propertyArg.slice(0, colon);
|
|
308
|
+
typeFilter = BINDING_TYPE_SHORTHANDS[prefix];
|
|
309
|
+
if (!typeFilter) {
|
|
310
|
+
const valid = Object.keys(BINDING_TYPE_SHORTHANDS).join(", ");
|
|
311
|
+
throw new Error(`Unknown binding type prefix "${prefix}". Valid prefixes: ${valid}`);
|
|
312
|
+
}
|
|
313
|
+
nameFilter = propertyArg.slice(colon + 1);
|
|
314
|
+
}
|
|
315
|
+
const isGlob = nameFilter.includes("*");
|
|
316
|
+
const matcher = isGlob ? globToRegex(nameFilter) : null;
|
|
317
|
+
const matches = details.filter((d) => {
|
|
318
|
+
if (typeFilter && d.bindingType !== typeFilter) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
if (!d.name) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return matcher ? matcher.test(d.name) : d.name === nameFilter;
|
|
325
|
+
});
|
|
326
|
+
if (matches.length === 0) {
|
|
327
|
+
const hint = templateArg
|
|
328
|
+
? `\nRun 'c8ctl element-template get-properties ${templateArg}' to list all available properties.`
|
|
329
|
+
: "";
|
|
330
|
+
throw new Error(`Property "${propertyArg}" not found.${hint}`);
|
|
331
|
+
}
|
|
332
|
+
return matches;
|
|
333
|
+
}
|
|
334
|
+
function formatBadgeValue(value) {
|
|
335
|
+
if (typeof value === "string") {
|
|
336
|
+
// Multi-line defaults (FEEL expression bodies, JSON snippets) would
|
|
337
|
+
// otherwise wrap and break the badge layout — collapse to a single
|
|
338
|
+
// line and truncate.
|
|
339
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
340
|
+
return oneLine.length > 60 ? `${oneLine.slice(0, 57)}...` : oneLine;
|
|
341
|
+
}
|
|
342
|
+
return JSON.stringify(value);
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=get-properties.js.map
|