@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.
Files changed (88) hide show
  1. package/EXAMPLES.md +22 -5
  2. package/PLUGIN-HELP.md +26 -0
  3. package/README.md +37 -4
  4. package/dist/command-dispatch.d.ts.map +1 -1
  5. package/dist/command-dispatch.js +2 -1
  6. package/dist/command-dispatch.js.map +1 -1
  7. package/dist/command-registry.d.ts +30 -0
  8. package/dist/command-registry.d.ts.map +1 -1
  9. package/dist/command-registry.js +34 -1
  10. package/dist/command-registry.js.map +1 -1
  11. package/dist/commands/plugins.d.ts +21 -0
  12. package/dist/commands/plugins.d.ts.map +1 -1
  13. package/dist/commands/plugins.js +74 -1
  14. package/dist/commands/plugins.js.map +1 -1
  15. package/dist/commands/resource-extensions.d.ts +12 -0
  16. package/dist/commands/resource-extensions.d.ts.map +1 -1
  17. package/dist/commands/resource-extensions.js +13 -1
  18. package/dist/commands/resource-extensions.js.map +1 -1
  19. package/dist/commands/run.d.ts.map +1 -1
  20. package/dist/commands/run.js +5 -3
  21. package/dist/commands/run.js.map +1 -1
  22. package/dist/commands/watch.d.ts.map +1 -1
  23. package/dist/commands/watch.js +21 -12
  24. package/dist/commands/watch.js.map +1 -1
  25. package/dist/completion.d.ts.map +1 -1
  26. package/dist/completion.js +140 -13
  27. package/dist/completion.js.map +1 -1
  28. package/dist/default-plugins/bpmn/README.md +69 -0
  29. package/dist/default-plugins/bpmn/c8ctl-plugin.js +125 -0
  30. package/dist/default-plugins/bpmn/c8ctl-plugin.js.map +1 -0
  31. package/dist/default-plugins/bpmn/lint.js +259 -0
  32. package/dist/default-plugins/bpmn/lint.js.map +1 -0
  33. package/dist/default-plugins/bpmn/package.json +8 -0
  34. package/dist/default-plugins/cluster/package.json +1 -1
  35. package/dist/default-plugins/element-template/AGENTS.md +65 -0
  36. package/dist/default-plugins/element-template/README.md +331 -0
  37. package/dist/default-plugins/element-template/c8ctl-plugin.js +247 -0
  38. package/dist/default-plugins/element-template/c8ctl-plugin.js.map +1 -0
  39. package/dist/default-plugins/element-template/commands/apply.js +298 -0
  40. package/dist/default-plugins/element-template/commands/apply.js.map +1 -0
  41. package/dist/default-plugins/element-template/commands/get-properties.js +344 -0
  42. package/dist/default-plugins/element-template/commands/get-properties.js.map +1 -0
  43. package/dist/default-plugins/element-template/commands/get.js +107 -0
  44. package/dist/default-plugins/element-template/commands/get.js.map +1 -0
  45. package/dist/default-plugins/element-template/commands/info.js +189 -0
  46. package/dist/default-plugins/element-template/commands/info.js.map +1 -0
  47. package/dist/default-plugins/element-template/commands/search.js +101 -0
  48. package/dist/default-plugins/element-template/commands/search.js.map +1 -0
  49. package/dist/default-plugins/element-template/commands/sync.js +36 -0
  50. package/dist/default-plugins/element-template/commands/sync.js.map +1 -0
  51. package/dist/default-plugins/element-template/docs/design.md +145 -0
  52. package/dist/default-plugins/element-template/helpers.js +404 -0
  53. package/dist/default-plugins/element-template/helpers.js.map +1 -0
  54. package/dist/default-plugins/element-template/marketplace.js +396 -0
  55. package/dist/default-plugins/element-template/marketplace.js.map +1 -0
  56. package/dist/default-plugins/element-template/package.json +8 -0
  57. package/dist/default-plugins/element-template/template-ref.js +232 -0
  58. package/dist/default-plugins/element-template/template-ref.js.map +1 -0
  59. package/dist/default-plugins/element-template/vendor-src/bundle-entry.js +44 -0
  60. package/dist/default-plugins/feel/README.md +163 -0
  61. package/dist/default-plugins/feel/c8ctl-plugin.js +616 -0
  62. package/dist/default-plugins/feel/c8ctl-plugin.js.map +1 -0
  63. package/dist/default-plugins/feel/package.json +8 -0
  64. package/dist/deployments.d.ts +1 -0
  65. package/dist/deployments.d.ts.map +1 -1
  66. package/dist/deployments.js +89 -20
  67. package/dist/deployments.js.map +1 -1
  68. package/dist/help.d.ts.map +1 -1
  69. package/dist/help.js +156 -5
  70. package/dist/help.js.map +1 -1
  71. package/dist/ignore.d.ts +16 -1
  72. package/dist/ignore.d.ts.map +1 -1
  73. package/dist/ignore.js +55 -3
  74. package/dist/ignore.js.map +1 -1
  75. package/dist/index.d.ts +23 -0
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +294 -25
  78. package/dist/index.js.map +1 -1
  79. package/dist/plugin-loader.d.ts +168 -7
  80. package/dist/plugin-loader.d.ts.map +1 -1
  81. package/dist/plugin-loader.js +284 -21
  82. package/dist/plugin-loader.js.map +1 -1
  83. package/dist/runtime.d.ts +10 -0
  84. package/dist/runtime.d.ts.map +1 -1
  85. package/dist/runtime.js +6 -0
  86. package/dist/runtime.js.map +1 -1
  87. package/dist/vendor/bpmn-element-templates.cjs +59639 -0
  88. 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