zinkee 0.1.37 → 0.1.39

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/dist/index.js CHANGED
@@ -461,7 +461,7 @@ var commandRegistry = {
461
461
  command: "displays cross-widget-filter set",
462
462
  accessLevel: "write",
463
463
  supportsExample: true,
464
- supportsRaw: false,
464
+ supportsRaw: true,
465
465
  output: writeOutput
466
466
  },
467
467
  "displays cross-widget-filter delete": {
@@ -3554,6 +3554,48 @@ var specificExamples = {
3554
3554
  response: `{
3555
3555
  "displayId": "0f6f062f-1478-4df2-96a1-c495d037d260",
3556
3556
  "widgetId": "11111111-1111-4111-a111-111111111111"
3557
+ }`
3558
+ },
3559
+ {
3560
+ description: "Create a TABLE widget with locked fields: contact-name locked permanently (no conditions) and status locked while invoice-number is empty, with before/after lock messages.",
3561
+ command: `zinkee --json displays widgets create 0f6f062f-1478-4df2-96a1-c495d037d260 --type table --name Invoices --origin-resource 2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e --layout '{"x":0,"y":0,"w":12,"h":8}' --field contact-name --field invoice-number --field status --field amount --lock-field contact-name --lock-condition "status:invoice-number:is_empty" --lock-message-before "status:Status locks until the invoice number is set." --lock-message-after "status:Status is locked because the invoice number is missing."`,
3562
+ raw: `{
3563
+ "type": "TABLE_OR_SUBTABLE",
3564
+ "source": {
3565
+ "originResourceId": "2eb8bdcf-6d9c-4fbb-9018-7fa8180f4c5e",
3566
+ "isOriginSubSchema": false
3567
+ },
3568
+ "placement": {
3569
+ "layout": { "x": 0, "y": 0, "w": 12, "h": 8 }
3570
+ },
3571
+ "presentation": { "name": "Invoices" },
3572
+ "view": {
3573
+ "fields": ["contact-name", "invoice-number", "status", "amount"]
3574
+ },
3575
+ "behavior": {
3576
+ "lockedFields": [
3577
+ {
3578
+ "field": { "fieldId": "contact-name" },
3579
+ "conditions": []
3580
+ },
3581
+ {
3582
+ "field": { "fieldId": "status" },
3583
+ "conditions": [
3584
+ {
3585
+ "origin": { "fieldId": "invoice-number" },
3586
+ "comparisonOperator": "IS_EMPTY",
3587
+ "values": []
3588
+ }
3589
+ ],
3590
+ "messageBeforeLock": "Status locks until the invoice number is set.",
3591
+ "messageAfterLock": "Status is locked because the invoice number is missing."
3592
+ }
3593
+ ]
3594
+ }
3595
+ }`,
3596
+ response: `{
3597
+ "displayId": "0f6f062f-1478-4df2-96a1-c495d037d260",
3598
+ "widgetId": "11111111-1111-4111-a111-111111111111"
3557
3599
  }`
3558
3600
  },
3559
3601
  {
@@ -3935,6 +3977,43 @@ var specificExamples = {
3935
3977
  "hideDeleteButton": true,
3936
3978
  "hideFilterButton": true
3937
3979
  }
3980
+ }`
3981
+ },
3982
+ {
3983
+ description: "Configure locked fields (replaces the stored configuration): contact-name locked permanently, status locked while invoice-number is empty. Empty conditions = always locked; conditions combine with AND. Enforcement happens in display widgets only, never in the database view.",
3984
+ command: `zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --lock-field contact-name --lock-condition "status:invoice-number:is_empty" --lock-message-before "status:Status locks until the invoice number is set." --lock-message-after "status:Status is locked because the invoice number is missing."`,
3985
+ raw: `{
3986
+ "behavior": {
3987
+ "lockedFields": [
3988
+ {
3989
+ "field": { "fieldId": "contact-name" },
3990
+ "conditions": []
3991
+ },
3992
+ {
3993
+ "field": { "fieldId": "status" },
3994
+ "conditions": [
3995
+ {
3996
+ "origin": { "fieldId": "invoice-number" },
3997
+ "comparisonOperator": "IS_EMPTY",
3998
+ "values": []
3999
+ }
4000
+ ],
4001
+ "messageBeforeLock": "Status locks until the invoice number is set.",
4002
+ "messageAfterLock": "Status is locked because the invoice number is missing."
4003
+ }
4004
+ ]
4005
+ }
4006
+ }`,
4007
+ response: `{
4008
+ "displayId": "0f6f062f-1478-4df2-96a1-c495d037d260",
4009
+ "widgetId": "11111111-1111-4111-a111-111111111111"
4010
+ }`
4011
+ },
4012
+ {
4013
+ description: "Clear the locked-fields configuration.",
4014
+ command: "zinkee --json displays widgets update 0f6f062f-1478-4df2-96a1-c495d037d260 11111111-1111-4111-a111-111111111111 --clear-locked-fields",
4015
+ raw: `{
4016
+ "behavior": { "lockedFields": [] }
3938
4017
  }`
3939
4018
  },
3940
4019
  {
@@ -4036,40 +4115,86 @@ var specificExamples = {
4036
4115
  ]),
4037
4116
  "displays cross-widget-filter set": buildCommandExamples([
4038
4117
  {
4039
- description: "Link one source widget/field to several target widgets through the same field.",
4118
+ description: "Single configuration with flags: link one source widget/field to several targets. The whole array is replaced.",
4040
4119
  command: "zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --source-widget 11111111-1111-4111-a111-111111111111 --source-field c60b8526-5835-4c9f-aecd-9a16c7704a34 --target 22222222-2222-4222-a222-222222222222:c60b8526-5835-4c9f-aecd-9a16c7704a34 --target 33333333-3333-4333-a333-333333333333:c60b8526-5835-4c9f-aecd-9a16c7704a34",
4041
- raw: `{
4042
- "source": {
4043
- "widgetId": "11111111-1111-4111-a111-111111111111",
4044
- "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4045
- },
4046
- "targets": [
4047
- {
4048
- "widgetId": "22222222-2222-4222-a222-222222222222",
4120
+ raw: `[
4121
+ {
4122
+ "source": {
4123
+ "widgetId": "11111111-1111-4111-a111-111111111111",
4049
4124
  "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4050
4125
  },
4126
+ "targets": [
4127
+ {
4128
+ "widgetId": "22222222-2222-4222-a222-222222222222",
4129
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4130
+ },
4131
+ {
4132
+ "widgetId": "33333333-3333-4333-a333-333333333333",
4133
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4134
+ }
4135
+ ]
4136
+ }
4137
+ ]`
4138
+ },
4051
4139
  {
4052
- "widgetId": "33333333-3333-4333-a333-333333333333",
4140
+ description: "Several independent configurations with --raw (each source filters its own targets). A widget cannot be a target of two configurations.",
4141
+ command: `zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --raw '[{"source":{"widgetId":"11111111-1111-4111-a111-111111111111","fieldId":"c60b8526-5835-4c9f-aecd-9a16c7704a34"},"targets":[{"widgetId":"22222222-2222-4222-a222-222222222222","fieldId":"c60b8526-5835-4c9f-aecd-9a16c7704a34"}]},{"source":{"widgetId":"44444444-4444-4444-a444-444444444444","fieldId":"a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"},"targets":[{"widgetId":"55555555-5555-4555-a555-555555555555","fieldId":"a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"}]}]'`,
4142
+ raw: `[
4143
+ {
4144
+ "source": {
4145
+ "widgetId": "11111111-1111-4111-a111-111111111111",
4053
4146
  "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4054
- }
4055
- ]
4056
- }`
4057
4147
  },
4058
- {
4059
- description: "Filter one target widget by the status field of the source.",
4060
- command: "zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --source-widget 11111111-1111-4111-a111-111111111111 --source-field a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890 --target 66666666-6666-4666-a666-666666666666:a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890",
4061
- raw: `{
4062
- "source": {
4063
- "widgetId": "11111111-1111-4111-a111-111111111111",
4064
- "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
4148
+ "targets": [
4149
+ {
4150
+ "widgetId": "22222222-2222-4222-a222-222222222222",
4151
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4152
+ }
4153
+ ]
4065
4154
  },
4066
- "targets": [
4155
+ {
4156
+ "source": {
4157
+ "widgetId": "44444444-4444-4444-a444-444444444444",
4158
+ "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
4159
+ },
4160
+ "targets": [
4161
+ {
4162
+ "widgetId": "55555555-5555-4555-a555-555555555555",
4163
+ "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
4164
+ }
4165
+ ]
4166
+ }
4167
+ ]`
4168
+ },
4067
4169
  {
4068
- "widgetId": "66666666-6666-4666-a666-666666666666",
4170
+ description: "Hierarchy chain with --raw-file: a target of one configuration is the source of the next (click Parent \u2192 filters Child, click Child \u2192 filters Grandchild).",
4171
+ command: "zinkee --json displays cross-widget-filter set 0f6f062f-1478-4df2-96a1-c495d037d260 --raw-file cross-widget-filter.json",
4172
+ raw: `[
4173
+ {
4174
+ "source": {
4175
+ "widgetId": "11111111-1111-4111-a111-111111111111",
4176
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4177
+ },
4178
+ "targets": [
4179
+ {
4180
+ "widgetId": "22222222-2222-4222-a222-222222222222",
4181
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4182
+ }
4183
+ ]
4184
+ },
4185
+ {
4186
+ "source": {
4187
+ "widgetId": "22222222-2222-4222-a222-222222222222",
4069
4188
  "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
4070
- }
4071
- ]
4072
- }`
4189
+ },
4190
+ "targets": [
4191
+ {
4192
+ "widgetId": "33333333-3333-4333-a333-333333333333",
4193
+ "fieldId": "a8f1d7e3-2b5c-4d9e-8a1b-3c4d5e6f7890"
4194
+ }
4195
+ ]
4196
+ }
4197
+ ]`
4073
4198
  }
4074
4199
  ]),
4075
4200
  "schemas list": buildCommandExamples([
@@ -4962,18 +5087,20 @@ var specificExamples = {
4962
5087
  }
4963
5088
  }
4964
5089
  ],
4965
- "crossWidgetFilter": {
4966
- "source": {
4967
- "widgetId": "11111111-1111-4111-a111-111111111111",
4968
- "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4969
- },
4970
- "targets": [
4971
- {
4972
- "widgetId": "22222222-2222-4222-a222-222222222222",
5090
+ "crossWidgetFilter": [
5091
+ {
5092
+ "sourceWidget": {
5093
+ "widgetId": "11111111-1111-4111-a111-111111111111",
4973
5094
  "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
4974
- }
4975
- ]
4976
- },
5095
+ },
5096
+ "targetWidgets": [
5097
+ {
5098
+ "widgetId": "22222222-2222-4222-a222-222222222222",
5099
+ "fieldId": "c60b8526-5835-4c9f-aecd-9a16c7704a34"
5100
+ }
5101
+ ]
5102
+ }
5103
+ ],
4977
5104
  "navigationTargets": [
4978
5105
  {
4979
5106
  "id": "nav-target-customer",
@@ -8859,6 +8986,83 @@ function parseFilterExpression(input) {
8859
8986
  function collectFilters(value, previous = []) {
8860
8987
  return [...previous, parseFilterExpression(value)];
8861
8988
  }
8989
+ function parseLockConditionOption(input) {
8990
+ const trimmed = input.trim();
8991
+ let lockedField;
8992
+ let expressionText;
8993
+ if (trimmed.includes(":") && !trimmed.includes(" ")) {
8994
+ const separatorIndex = trimmed.indexOf(":");
8995
+ lockedField = trimmed.slice(0, separatorIndex).trim();
8996
+ expressionText = trimmed.slice(separatorIndex + 1).trim();
8997
+ } else {
8998
+ const parts = trimmed.split(/\s+/);
8999
+ lockedField = parts[0]?.trim() ?? "";
9000
+ expressionText = parts.slice(1).join(" ").trim();
9001
+ }
9002
+ if (!lockedField || !expressionText) {
9003
+ throw new InvalidArgumentError3(
9004
+ 'Expected "<lockedField>:<field>:<comparator>[:<value>]" (or space-separated), e.g. "status:invoice-number:is_empty".'
9005
+ );
9006
+ }
9007
+ return { lockedField, expression: parseFilterExpression(expressionText) };
9008
+ }
9009
+ function collectLockConditions(value, previous = []) {
9010
+ return [...previous, parseLockConditionOption(value)];
9011
+ }
9012
+ function parseLockMessageOption(input) {
9013
+ const separatorIndex = input.indexOf(":");
9014
+ const lockedField = separatorIndex > 0 ? input.slice(0, separatorIndex).trim() : "";
9015
+ const message = separatorIndex > 0 ? input.slice(separatorIndex + 1).trim() : "";
9016
+ if (!lockedField || !message) {
9017
+ throw new InvalidArgumentError3('Expected "<lockedField>:<message>", e.g. "status:Locked once approved.".');
9018
+ }
9019
+ return { lockedField, message };
9020
+ }
9021
+ function collectLockMessages(value, previous = []) {
9022
+ return [...previous, parseLockMessageOption(value)];
9023
+ }
9024
+ function buildLockedFields(options) {
9025
+ const hasLockFlags = (options.lockField?.length ?? 0) > 0 || (options.lockCondition?.length ?? 0) > 0 || (options.lockMessageBefore?.length ?? 0) > 0 || (options.lockMessageAfter?.length ?? 0) > 0;
9026
+ if (options.clearLockedFields) {
9027
+ if (hasLockFlags) {
9028
+ throw new CliError(
9029
+ "invalid_cli_usage",
9030
+ 'Use either "--clear-locked-fields" or the "--lock-*" flags, not both.'
9031
+ );
9032
+ }
9033
+ return [];
9034
+ }
9035
+ if (!hasLockFlags) {
9036
+ return void 0;
9037
+ }
9038
+ const rules = /* @__PURE__ */ new Map();
9039
+ const ensureRule = (lockedField) => {
9040
+ let rule = rules.get(lockedField);
9041
+ if (!rule) {
9042
+ rule = { conditions: [] };
9043
+ rules.set(lockedField, rule);
9044
+ }
9045
+ return rule;
9046
+ };
9047
+ for (const lockedField of options.lockField ?? []) {
9048
+ ensureRule(lockedField);
9049
+ }
9050
+ for (const { lockedField, expression } of options.lockCondition ?? []) {
9051
+ ensureRule(lockedField).conditions.push(toDisplayFilter(expression));
9052
+ }
9053
+ for (const { lockedField, message } of options.lockMessageBefore ?? []) {
9054
+ ensureRule(lockedField).messageBeforeLock = message;
9055
+ }
9056
+ for (const { lockedField, message } of options.lockMessageAfter ?? []) {
9057
+ ensureRule(lockedField).messageAfterLock = message;
9058
+ }
9059
+ return [...rules.entries()].map(([lockedField, rule]) => ({
9060
+ field: { fieldId: lockedField },
9061
+ conditions: rule.conditions,
9062
+ ...rule.messageBeforeLock !== void 0 ? { messageBeforeLock: rule.messageBeforeLock } : {},
9063
+ ...rule.messageAfterLock !== void 0 ? { messageAfterLock: rule.messageAfterLock } : {}
9064
+ }));
9065
+ }
8862
9066
  function readRawObject2(raw, rawFile) {
8863
9067
  if (raw && rawFile) {
8864
9068
  throw new CliError("invalid_cli_usage", 'Use either "--raw" or "--raw-file", not both.');
@@ -9189,6 +9393,13 @@ function buildWidgetBody(options) {
9189
9393
  hideSeeButton: options.hideSeeButton
9190
9394
  };
9191
9395
  }
9396
+ const lockedFields = buildLockedFields(options);
9397
+ if (lockedFields !== void 0) {
9398
+ body.behavior = {
9399
+ ...typeof body.behavior === "object" && body.behavior ? body.behavior : {},
9400
+ lockedFields
9401
+ };
9402
+ }
9192
9403
  if (options.calc || options.calcField || options.kpiConfig) {
9193
9404
  body.kpi = {
9194
9405
  ...typeof body.kpi === "object" && body.kpi ? body.kpi : {},
@@ -9226,20 +9437,54 @@ function buildNavigationTargetBody(options) {
9226
9437
  }
9227
9438
  return body;
9228
9439
  }
9440
+ function readRawArray(raw, rawFile) {
9441
+ if (raw && rawFile) {
9442
+ throw new CliError("invalid_cli_usage", 'Use either "--raw" or "--raw-file", not both.');
9443
+ }
9444
+ const source = rawFile ? readFileSync4(rawFile, "utf8") : raw;
9445
+ if (!source) {
9446
+ return [];
9447
+ }
9448
+ try {
9449
+ const parsed = JSON.parse(source);
9450
+ if (!Array.isArray(parsed)) {
9451
+ throw new CliError("invalid_cli_usage", "Raw cross widget filter payload must be a JSON array of configurations.");
9452
+ }
9453
+ return parsed;
9454
+ } catch (error) {
9455
+ if (error instanceof CliError) throw error;
9456
+ throw new CliError("invalid_cli_usage", "Raw JSON payload is invalid.");
9457
+ }
9458
+ }
9229
9459
  function buildCrossWidgetFilterBody(options) {
9230
- return {
9231
- source: {
9232
- widgetId: options.sourceWidget,
9233
- fieldId: options.sourceField
9234
- },
9235
- targets: options.target.map((entry) => {
9236
- const [widgetId, fieldId] = entry.split(":");
9237
- if (!widgetId || !fieldId) {
9238
- throw new CliError("invalid_cli_usage", `Invalid --target entry "${entry}". Use <widgetId:fieldId>.`);
9239
- }
9240
- return { widgetId, fieldId };
9241
- })
9460
+ if (options.raw !== void 0 || options.rawFile !== void 0) {
9461
+ return readRawArray(options.raw, options.rawFile);
9462
+ }
9463
+ if (!options.sourceWidget || !options.sourceField) {
9464
+ throw new CliError(
9465
+ "invalid_cli_usage",
9466
+ "Provide --source-widget and --source-field, or --raw/--raw-file with the full configuration array."
9467
+ );
9468
+ }
9469
+ const source = {
9470
+ widgetId: options.sourceWidget,
9471
+ fieldId: options.sourceField
9242
9472
  };
9473
+ if (options.sourceSchema) {
9474
+ source.schemaId = options.sourceSchema;
9475
+ }
9476
+ return [
9477
+ {
9478
+ source,
9479
+ targets: (options.target ?? []).map((entry) => {
9480
+ const [widgetId, fieldId] = entry.split(":");
9481
+ if (!widgetId || !fieldId) {
9482
+ throw new CliError("invalid_cli_usage", `Invalid --target entry "${entry}". Use <widgetId:fieldId>.`);
9483
+ }
9484
+ return { widgetId, fieldId };
9485
+ })
9486
+ }
9487
+ ];
9243
9488
  }
9244
9489
  function registerDisplaysCommands(program, options = {}) {
9245
9490
  const filePath = options.configPath ?? defaultConfigPath;
@@ -9506,7 +9751,7 @@ function registerDisplaysCommands(program, options = {}) {
9506
9751
  }
9507
9752
  printTable([data], void 0, stdout);
9508
9753
  });
9509
- widgets.command("create").description("Create widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector3).requiredOption("--type <type>", "widget type").option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>").option("--origin-resource <uuid>").option("--origin-subschema").option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>").option("--field <field>", "repeatable field", (v, p = []) => [...p, v], []).option("--sort <sort>", "repeatable sort", (v, p = []) => [...p, v], []).option("--filter <expr>", "repeatable filter", collectFilters, []).option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button").option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>").option("--chart-type <type>", "chart type (lines, bars, stackedBars, combined)").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>", "time granularity (DATE, MONTH_YEAR, QUARTER_YEAR, YEAR)").option("--x-axis-time-series").option("--y-axis-serie <json>", "repeatable y axis serie", (v, p = []) => [...p, v], []).option("--legend <json>").option("--column-field <uuid>", "KANBAN column field (UUID, not slug)").option("--swimlane-field <uuid>", "KANBAN swimlane field (UUID, not slug)").option("--start-date-field <uuid>", "TIMELINE start date field (UUID, not slug)").option("--end-date-field <uuid>", "TIMELINE end date field (UUID, not slug)").option("--label-field <uuid>", "TIMELINE label field (UUID, not slug)").option("--group-field <uuid>", "TIMELINE group field (UUID, not slug)").option("--subgroup-field <uuid>", "TIMELINE subgroup field (UUID, not slug)").option("--progress-field <uuid>", "TIMELINE progress field (UUID, not slug)").option("--card-title-field <uuid>", "KANBAN/TIMELINE card title field (UUID, not slug)").option("--card-category1-field <uuid>", "KANBAN/TIMELINE card category 1 field (UUID, not slug)").option("--card-category2-field <uuid>", "KANBAN/TIMELINE card category 2 field (UUID, not slug)").option("--card-category3-field <uuid>", "KANBAN card category 3 field (UUID, not slug)").option("--card-category4-field <uuid>", "TIMELINE card category 4 field (UUID, not slug)").option("--card-description-field <uuid>", "KANBAN/TIMELINE card description field (UUID, not slug)").option("--aggregation-method <method>", "KANBAN card aggregation method (e.g. sum)").option("--aggregation-field <uuid>", "KANBAN card aggregation field (UUID, not slug)").option("--view-state <json>", "KANBAN/TIMELINE viewState UI scalars (columnOrder, granularity, sidebarWidth, show* ...)").option("--view-state-file <path>", "read --view-state JSON from a file").option("--hierarchy-label <name>", "HIERARCHY label column name").option("--hierarchy-info <id:name>", "HIERARCHY info column; id is a unique opaque column id you choose (e.g. col_1779975207355_zfndknslw), not the name, and is reused as columnId in mappings (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-number <id:name:aggregation>", "HIERARCHY number column; id is a unique opaque column id (e.g. col_1779975207355_zfndknslw), not the name; aggregation SUM|AVERAGE|MINIMUM|MAXIMUM|COUNT|COUNT_DISTINCT (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-name <name>", "HIERARCHY root level name").option("--hierarchy-root-label-field <uuid>", "HIERARCHY root level label field (UUID, not slug)").option("--hierarchy-root-info <columnId:fieldId>", "HIERARCHY root info mapping; columnId must match a --hierarchy-info id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-number <columnId:fieldId>", "HIERARCHY root number mapping; columnId must match a --hierarchy-number id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-child <json>", "HIERARCHY child level JSON {name,schemaId,childFieldId,labelFieldId,infoMappings,numberMappings} (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-config <json>", "HIERARCHY full hierarchyConfig JSON (escape hatch)").option("--hierarchy-config-file <path>", "read --hierarchy-config JSON from a file").option("--raw <json>").option("--raw-file <path>").action(async function action(display, commandOptions) {
9754
+ widgets.command("create").description("Create widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector3).requiredOption("--type <type>", "widget type").option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>").option("--origin-resource <uuid>").option("--origin-subschema").option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>").option("--field <field>", "repeatable field", (v, p = []) => [...p, v], []).option("--sort <sort>", "repeatable sort", (v, p = []) => [...p, v], []).option("--filter <expr>", "repeatable filter", collectFilters, []).option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button").option("--lock-field <field>", "lock a field permanently in this widget (repeatable; field UUID or slug)", (v, p = []) => [...p, v], []).option("--lock-condition <expr>", 'conditional lock "<lockedField>:<field>:<comparator>[:<value>]"; locks <lockedField> when all its conditions match (AND, repeatable)', collectLockConditions, []).option("--lock-message-before <field:text>", "message shown while a conditional lock is not met yet (repeatable)", collectLockMessages, []).option("--lock-message-after <field:text>", "message shown once the field is locked (repeatable)", collectLockMessages, []).option("--clear-locked-fields", "clear the widget locked-fields configuration").option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>").option("--chart-type <type>", "chart type (lines, bars, stackedBars, combined)").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>", "time granularity (DATE, MONTH_YEAR, QUARTER_YEAR, YEAR)").option("--x-axis-time-series").option("--y-axis-serie <json>", "repeatable y axis serie", (v, p = []) => [...p, v], []).option("--legend <json>").option("--column-field <uuid>", "KANBAN column field (UUID, not slug)").option("--swimlane-field <uuid>", "KANBAN swimlane field (UUID, not slug)").option("--start-date-field <uuid>", "TIMELINE start date field (UUID, not slug)").option("--end-date-field <uuid>", "TIMELINE end date field (UUID, not slug)").option("--label-field <uuid>", "TIMELINE label field (UUID, not slug)").option("--group-field <uuid>", "TIMELINE group field (UUID, not slug)").option("--subgroup-field <uuid>", "TIMELINE subgroup field (UUID, not slug)").option("--progress-field <uuid>", "TIMELINE progress field (UUID, not slug)").option("--card-title-field <uuid>", "KANBAN/TIMELINE card title field (UUID, not slug)").option("--card-category1-field <uuid>", "KANBAN/TIMELINE card category 1 field (UUID, not slug)").option("--card-category2-field <uuid>", "KANBAN/TIMELINE card category 2 field (UUID, not slug)").option("--card-category3-field <uuid>", "KANBAN card category 3 field (UUID, not slug)").option("--card-category4-field <uuid>", "TIMELINE card category 4 field (UUID, not slug)").option("--card-description-field <uuid>", "KANBAN/TIMELINE card description field (UUID, not slug)").option("--aggregation-method <method>", "KANBAN card aggregation method (e.g. sum)").option("--aggregation-field <uuid>", "KANBAN card aggregation field (UUID, not slug)").option("--view-state <json>", "KANBAN/TIMELINE viewState UI scalars (columnOrder, granularity, sidebarWidth, show* ...)").option("--view-state-file <path>", "read --view-state JSON from a file").option("--hierarchy-label <name>", "HIERARCHY label column name").option("--hierarchy-info <id:name>", "HIERARCHY info column; id is a unique opaque column id you choose (e.g. col_1779975207355_zfndknslw), not the name, and is reused as columnId in mappings (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-number <id:name:aggregation>", "HIERARCHY number column; id is a unique opaque column id (e.g. col_1779975207355_zfndknslw), not the name; aggregation SUM|AVERAGE|MINIMUM|MAXIMUM|COUNT|COUNT_DISTINCT (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-name <name>", "HIERARCHY root level name").option("--hierarchy-root-label-field <uuid>", "HIERARCHY root level label field (UUID, not slug)").option("--hierarchy-root-info <columnId:fieldId>", "HIERARCHY root info mapping; columnId must match a --hierarchy-info id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-number <columnId:fieldId>", "HIERARCHY root number mapping; columnId must match a --hierarchy-number id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-child <json>", "HIERARCHY child level JSON {name,schemaId,childFieldId,labelFieldId,infoMappings,numberMappings} (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-config <json>", "HIERARCHY full hierarchyConfig JSON (escape hatch)").option("--hierarchy-config-file <path>", "read --hierarchy-config JSON from a file").option("--raw <json>").option("--raw-file <path>").action(async function action(display, commandOptions) {
9510
9755
  const { api, json, readOnly } = createDisplaysApi(this, filePath);
9511
9756
  ensureWritable3("displays widgets create", readOnly, "write");
9512
9757
  const data = await api.createWidget(display, buildWidgetBody(commandOptions));
@@ -9516,7 +9761,7 @@ function registerDisplaysCommands(program, options = {}) {
9516
9761
  }
9517
9762
  writeLine4(stdout, `Created widget on display "${display.value}".`);
9518
9763
  });
9519
- widgets.command("update").description("Update widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector3).argument("<widget>", "widget id").option("--type <type>").option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>").option("--origin-resource <uuid>").option("--origin-subschema").option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>").option("--field <field>", "repeatable field", (v, p = []) => [...p, v], []).option("--sort <sort>", "repeatable sort", (v, p = []) => [...p, v], []).option("--filter <expr>", "repeatable filter", collectFilters, []).option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button").option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>").option("--chart-type <type>", "chart type (lines, bars, stackedBars, combined)").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>", "time granularity (DATE, MONTH_YEAR, QUARTER_YEAR, YEAR)").option("--x-axis-time-series").option("--y-axis-serie <json>", "repeatable y axis serie", (v, p = []) => [...p, v], []).option("--legend <json>").option("--column-field <uuid>", "KANBAN column field (UUID, not slug)").option("--swimlane-field <uuid>", "KANBAN swimlane field (UUID, not slug)").option("--start-date-field <uuid>", "TIMELINE start date field (UUID, not slug)").option("--end-date-field <uuid>", "TIMELINE end date field (UUID, not slug)").option("--label-field <uuid>", "TIMELINE label field (UUID, not slug)").option("--group-field <uuid>", "TIMELINE group field (UUID, not slug)").option("--subgroup-field <uuid>", "TIMELINE subgroup field (UUID, not slug)").option("--progress-field <uuid>", "TIMELINE progress field (UUID, not slug)").option("--card-title-field <uuid>", "KANBAN/TIMELINE card title field (UUID, not slug)").option("--card-category1-field <uuid>", "KANBAN/TIMELINE card category 1 field (UUID, not slug)").option("--card-category2-field <uuid>", "KANBAN/TIMELINE card category 2 field (UUID, not slug)").option("--card-category3-field <uuid>", "KANBAN card category 3 field (UUID, not slug)").option("--card-category4-field <uuid>", "TIMELINE card category 4 field (UUID, not slug)").option("--card-description-field <uuid>", "KANBAN/TIMELINE card description field (UUID, not slug)").option("--aggregation-method <method>", "KANBAN card aggregation method (e.g. sum)").option("--aggregation-field <uuid>", "KANBAN card aggregation field (UUID, not slug)").option("--view-state <json>", "KANBAN/TIMELINE viewState UI scalars (columnOrder, granularity, sidebarWidth, show* ...)").option("--view-state-file <path>", "read --view-state JSON from a file").option("--hierarchy-label <name>", "HIERARCHY label column name").option("--hierarchy-info <id:name>", "HIERARCHY info column; id is a unique opaque column id you choose (e.g. col_1779975207355_zfndknslw), not the name, and is reused as columnId in mappings (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-number <id:name:aggregation>", "HIERARCHY number column; id is a unique opaque column id (e.g. col_1779975207355_zfndknslw), not the name; aggregation SUM|AVERAGE|MINIMUM|MAXIMUM|COUNT|COUNT_DISTINCT (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-name <name>", "HIERARCHY root level name").option("--hierarchy-root-label-field <uuid>", "HIERARCHY root level label field (UUID, not slug)").option("--hierarchy-root-info <columnId:fieldId>", "HIERARCHY root info mapping; columnId must match a --hierarchy-info id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-number <columnId:fieldId>", "HIERARCHY root number mapping; columnId must match a --hierarchy-number id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-child <json>", "HIERARCHY child level JSON {name,schemaId,childFieldId,labelFieldId,infoMappings,numberMappings} (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-config <json>", "HIERARCHY full hierarchyConfig JSON (escape hatch)").option("--hierarchy-config-file <path>", "read --hierarchy-config JSON from a file").option("--raw <json>").option("--raw-file <path>").action(async function action(display, widgetId, commandOptions) {
9764
+ widgets.command("update").description("Update widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector3).argument("<widget>", "widget id").option("--type <type>").option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>").option("--origin-resource <uuid>").option("--origin-subschema").option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>").option("--field <field>", "repeatable field", (v, p = []) => [...p, v], []).option("--sort <sort>", "repeatable sort", (v, p = []) => [...p, v], []).option("--filter <expr>", "repeatable filter", collectFilters, []).option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button").option("--lock-field <field>", "lock a field permanently in this widget (repeatable; field UUID or slug)", (v, p = []) => [...p, v], []).option("--lock-condition <expr>", 'conditional lock "<lockedField>:<field>:<comparator>[:<value>]"; locks <lockedField> when all its conditions match (AND, repeatable)', collectLockConditions, []).option("--lock-message-before <field:text>", "message shown while a conditional lock is not met yet (repeatable)", collectLockMessages, []).option("--lock-message-after <field:text>", "message shown once the field is locked (repeatable)", collectLockMessages, []).option("--clear-locked-fields", "clear the widget locked-fields configuration").option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>").option("--chart-type <type>", "chart type (lines, bars, stackedBars, combined)").option("--x-axis-field <uuid>").option("--x-axis-date-format <granularity>", "time granularity (DATE, MONTH_YEAR, QUARTER_YEAR, YEAR)").option("--x-axis-time-series").option("--y-axis-serie <json>", "repeatable y axis serie", (v, p = []) => [...p, v], []).option("--legend <json>").option("--column-field <uuid>", "KANBAN column field (UUID, not slug)").option("--swimlane-field <uuid>", "KANBAN swimlane field (UUID, not slug)").option("--start-date-field <uuid>", "TIMELINE start date field (UUID, not slug)").option("--end-date-field <uuid>", "TIMELINE end date field (UUID, not slug)").option("--label-field <uuid>", "TIMELINE label field (UUID, not slug)").option("--group-field <uuid>", "TIMELINE group field (UUID, not slug)").option("--subgroup-field <uuid>", "TIMELINE subgroup field (UUID, not slug)").option("--progress-field <uuid>", "TIMELINE progress field (UUID, not slug)").option("--card-title-field <uuid>", "KANBAN/TIMELINE card title field (UUID, not slug)").option("--card-category1-field <uuid>", "KANBAN/TIMELINE card category 1 field (UUID, not slug)").option("--card-category2-field <uuid>", "KANBAN/TIMELINE card category 2 field (UUID, not slug)").option("--card-category3-field <uuid>", "KANBAN card category 3 field (UUID, not slug)").option("--card-category4-field <uuid>", "TIMELINE card category 4 field (UUID, not slug)").option("--card-description-field <uuid>", "KANBAN/TIMELINE card description field (UUID, not slug)").option("--aggregation-method <method>", "KANBAN card aggregation method (e.g. sum)").option("--aggregation-field <uuid>", "KANBAN card aggregation field (UUID, not slug)").option("--view-state <json>", "KANBAN/TIMELINE viewState UI scalars (columnOrder, granularity, sidebarWidth, show* ...)").option("--view-state-file <path>", "read --view-state JSON from a file").option("--hierarchy-label <name>", "HIERARCHY label column name").option("--hierarchy-info <id:name>", "HIERARCHY info column; id is a unique opaque column id you choose (e.g. col_1779975207355_zfndknslw), not the name, and is reused as columnId in mappings (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-number <id:name:aggregation>", "HIERARCHY number column; id is a unique opaque column id (e.g. col_1779975207355_zfndknslw), not the name; aggregation SUM|AVERAGE|MINIMUM|MAXIMUM|COUNT|COUNT_DISTINCT (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-name <name>", "HIERARCHY root level name").option("--hierarchy-root-label-field <uuid>", "HIERARCHY root level label field (UUID, not slug)").option("--hierarchy-root-info <columnId:fieldId>", "HIERARCHY root info mapping; columnId must match a --hierarchy-info id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-root-number <columnId:fieldId>", "HIERARCHY root number mapping; columnId must match a --hierarchy-number id (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-child <json>", "HIERARCHY child level JSON {name,schemaId,childFieldId,labelFieldId,infoMappings,numberMappings} (repeatable)", (v, p = []) => [...p, v], []).option("--hierarchy-config <json>", "HIERARCHY full hierarchyConfig JSON (escape hatch)").option("--hierarchy-config-file <path>", "read --hierarchy-config JSON from a file").option("--raw <json>").option("--raw-file <path>").action(async function action(display, widgetId, commandOptions) {
9520
9765
  const { api, json, readOnly } = createDisplaysApi(this, filePath);
9521
9766
  ensureWritable3("displays widgets update", readOnly, "write");
9522
9767
  const data = await api.updateWidget(display, widgetId, buildWidgetBody(commandOptions));
@@ -9586,9 +9831,9 @@ function registerDisplaysCommands(program, options = {}) {
9586
9831
  emitJson4(stdout, "displays cross-widget-filter get", item);
9587
9832
  return;
9588
9833
  }
9589
- printTable(item ? [item] : [], void 0, stdout);
9834
+ printTable(item ?? [], void 0, stdout);
9590
9835
  });
9591
- cross.command("set").description("Set cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector3).requiredOption("--source-widget <id>").requiredOption("--source-field <id>").requiredOption("--target <widget:field>", "repeatable target", (v, p = []) => [...p, v], []).action(async function action(display, commandOptions) {
9836
+ cross.command("set").description("Set cross widget filter (replaces the whole array of configurations)").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector3).option("--source-widget <id>", "source widget id (single-configuration mode)").option("--source-field <id>", "source field id (single-configuration mode)").option("--source-schema <id>", "source schema id, only for HIERARCHY source widgets").option("--target <widget:field>", "repeatable target (single-configuration mode)", (v, p = []) => [...p, v], []).option("--raw <json>", "full array of configurations as JSON (for several configurations)").option("--raw-file <path>", "path to a JSON file with the full array of configurations").action(async function action(display, commandOptions) {
9592
9837
  const { api, json, readOnly } = createDisplaysApi(this, filePath);
9593
9838
  ensureWritable3("displays cross-widget-filter set", readOnly, "write");
9594
9839
  const data = await api.setCrossWidgetFilter(display, buildCrossWidgetFilterBody(commandOptions));