zinkee 0.1.44 → 0.1.45
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/README.md +4 -0
- package/dist/chunk-MHEVRMDX.js +1230 -0
- package/dist/chunk-MHEVRMDX.js.map +1 -0
- package/{src/utils/examples.ts → dist/examples-E6FFEVBE.js} +856 -1141
- package/dist/examples-E6FFEVBE.js.map +1 -0
- package/dist/index.js +1145 -8456
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/.github/workflows/npm-publish.yml +0 -77
- package/.github/workflows/pr-checks.yml +0 -36
- package/AGENTS.md +0 -110
- package/docs/cli-contract.md +0 -41
- package/docs/npm-release.md +0 -64
- package/docs/superpowers/plans/2026-03-24-zinkee-cli-implementation.md +0 -837
- package/docs/superpowers/plans/2026-03-25-cli-backend-error-contract.md +0 -503
- package/docs/superpowers/plans/2026-03-26-display-freeform-create.md +0 -389
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-backend-blockers.md +0 -172
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-design.md +0 -1576
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-checklist.md +0 -215
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-design.md +0 -492
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-status.md +0 -307
- package/docs/superpowers/specs/2026-07-30-cli-pr-checks-design.md +0 -37
- package/src/api/automations.ts +0 -404
- package/src/api/comments.ts +0 -51
- package/src/api/displays.ts +0 -337
- package/src/api/document-templates.ts +0 -110
- package/src/api/files.ts +0 -50
- package/src/api/formulas.test.ts +0 -65
- package/src/api/formulas.ts +0 -67
- package/src/api/logs.ts +0 -34
- package/src/api/navigation.ts +0 -70
- package/src/api/records.ts +0 -184
- package/src/api/schemas.ts +0 -148
- package/src/api/teamspace.ts +0 -110
- package/src/cli-examples.ts +0 -130
- package/src/cli-runner.ts +0 -95
- package/src/client.test.ts +0 -189
- package/src/client.ts +0 -269
- package/src/command-registry.ts +0 -882
- package/src/commands/automations.test.ts +0 -1030
- package/src/commands/automations.ts +0 -2102
- package/src/commands/comments.test.ts +0 -214
- package/src/commands/comments.ts +0 -303
- package/src/commands/config.test.ts +0 -81
- package/src/commands/config.ts +0 -150
- package/src/commands/displays.test.ts +0 -1105
- package/src/commands/displays.ts +0 -1442
- package/src/commands/document-templates.test.ts +0 -569
- package/src/commands/document-templates.ts +0 -563
- package/src/commands/files.test.ts +0 -284
- package/src/commands/files.ts +0 -280
- package/src/commands/formulas.test.ts +0 -194
- package/src/commands/formulas.ts +0 -243
- package/src/commands/logs.test.ts +0 -123
- package/src/commands/logs.ts +0 -159
- package/src/commands/navigation.test.ts +0 -211
- package/src/commands/navigation.ts +0 -348
- package/src/commands/profiles.test.ts +0 -191
- package/src/commands/profiles.ts +0 -303
- package/src/commands/records.test.ts +0 -860
- package/src/commands/records.ts +0 -883
- package/src/commands/schemas.test.ts +0 -1252
- package/src/commands/schemas.ts +0 -890
- package/src/commands/teamspace.test.ts +0 -229
- package/src/commands/teamspace.ts +0 -546
- package/src/completion/engine.test.ts +0 -138
- package/src/completion/engine.ts +0 -168
- package/src/completion/install.test.ts +0 -179
- package/src/completion/install.ts +0 -260
- package/src/completion/runtime.ts +0 -150
- package/src/completion/scripts.ts +0 -91
- package/src/config.test.ts +0 -362
- package/src/config.ts +0 -294
- package/src/index.test.ts +0 -217
- package/src/index.ts +0 -8
- package/src/parsers/expressions.test.ts +0 -95
- package/src/parsers/expressions.ts +0 -128
- package/src/parsers/kv.test.ts +0 -35
- package/src/parsers/kv.ts +0 -49
- package/src/parsers/selectors.test.ts +0 -23
- package/src/parsers/selectors.ts +0 -29
- package/src/program.ts +0 -64
- package/src/runtime-context.ts +0 -103
- package/src/types.ts +0 -76
- package/src/utils/argv-rewrite.test.ts +0 -149
- package/src/utils/argv-rewrite.ts +0 -269
- package/src/utils/errors.test.ts +0 -85
- package/src/utils/errors.ts +0 -191
- package/src/utils/examples.test.ts +0 -374
- package/src/utils/output.test.ts +0 -65
- package/src/utils/output.ts +0 -154
- package/src/utils/schema-fields.ts +0 -1016
- package/tsconfig.json +0 -20
- package/tsup.config.ts +0 -13
package/src/commands/displays.ts
DELETED
|
@@ -1,1442 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import type { Writable } from "node:stream";
|
|
3
|
-
|
|
4
|
-
import { Command, InvalidArgumentError } from "commander";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
DisplaysApi,
|
|
8
|
-
type ApiDisplayFolder,
|
|
9
|
-
type DisplayResource,
|
|
10
|
-
type DisplaySummary,
|
|
11
|
-
} from "../api/displays.js";
|
|
12
|
-
import { ApiClient } from "../client.js";
|
|
13
|
-
import { defaultConfigPath, readRuntimeConfig } from "../config.js";
|
|
14
|
-
import {
|
|
15
|
-
parseExpression,
|
|
16
|
-
type ExpressionComparator,
|
|
17
|
-
type ParsedExpression,
|
|
18
|
-
} from "../parsers/expressions.js";
|
|
19
|
-
import { normalizeUuidOrSlug, type NormalizedUuidOrSlug } from "../parsers/selectors.js";
|
|
20
|
-
import { resolveRuntimeContext } from "../runtime-context.js";
|
|
21
|
-
import type { RuntimeOverrides } from "../types.js";
|
|
22
|
-
import { CliError } from "../utils/errors.js";
|
|
23
|
-
import { buildJsonSuccess, folderColumns, FOLDER_TABLE_KEYS, printTable } from "../utils/output.js";
|
|
24
|
-
|
|
25
|
-
const DATE_DYNAMIC_OPTIONS =
|
|
26
|
-
"today, thisWeek, thisMonth, thisQuarter, thisSemester, thisYear, next7Days, next30Days, next6Months, next12Months, last12Months";
|
|
27
|
-
|
|
28
|
-
interface CommandIo {
|
|
29
|
-
write(chunk: string): unknown;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface RegisterDisplaysCommandsOptions {
|
|
33
|
-
configPath?: string;
|
|
34
|
-
stdout?: CommandIo;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface GlobalOptions extends RuntimeOverrides {}
|
|
38
|
-
|
|
39
|
-
interface DisplayCreateOptions {
|
|
40
|
-
name: string;
|
|
41
|
-
description?: string;
|
|
42
|
-
template: string;
|
|
43
|
-
freeformLayouts?: string;
|
|
44
|
-
freeformLayoutsFile?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface DisplayUpdateOptions {
|
|
48
|
-
name?: string;
|
|
49
|
-
description?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface LayoutUpdateOptions {
|
|
53
|
-
template: string;
|
|
54
|
-
freeformLayouts?: string;
|
|
55
|
-
freeformLayoutsFile?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface TabMutationOptions {
|
|
59
|
-
name?: string;
|
|
60
|
-
orientation?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface VariableMutationOptions {
|
|
64
|
-
type?: string;
|
|
65
|
-
name?: string;
|
|
66
|
-
allowMultiple?: boolean;
|
|
67
|
-
static?: boolean;
|
|
68
|
-
default?: string;
|
|
69
|
-
defaultValue?: string[];
|
|
70
|
-
sourceSchema?: string;
|
|
71
|
-
sourceField?: string;
|
|
72
|
-
filter?: ParsedExpression[];
|
|
73
|
-
dateMode?: string;
|
|
74
|
-
dateDynamicOption?: string;
|
|
75
|
-
dateSpecific?: string;
|
|
76
|
-
dateRangeStart?: string;
|
|
77
|
-
dateRangeEnd?: string;
|
|
78
|
-
raw?: string;
|
|
79
|
-
rawFile?: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface WidgetMutationOptions {
|
|
83
|
-
type?: string;
|
|
84
|
-
name?: string;
|
|
85
|
-
description?: string;
|
|
86
|
-
icon?: string;
|
|
87
|
-
color?: string;
|
|
88
|
-
originResource?: string;
|
|
89
|
-
originSubschema?: boolean;
|
|
90
|
-
tab?: string;
|
|
91
|
-
slot?: string;
|
|
92
|
-
layout?: string;
|
|
93
|
-
layoutFile?: string;
|
|
94
|
-
field?: string[];
|
|
95
|
-
sort?: string[];
|
|
96
|
-
filter?: ParsedExpression[];
|
|
97
|
-
widgetReadOnly?: boolean;
|
|
98
|
-
hideAddButton?: boolean;
|
|
99
|
-
hideDeleteButton?: boolean;
|
|
100
|
-
hideSortButton?: boolean;
|
|
101
|
-
hideFilterButton?: boolean;
|
|
102
|
-
hideSeeButton?: boolean;
|
|
103
|
-
lockField?: string[];
|
|
104
|
-
lockCondition?: LockConditionOption[];
|
|
105
|
-
lockMessageBefore?: LockMessageOption[];
|
|
106
|
-
lockMessageAfter?: LockMessageOption[];
|
|
107
|
-
clearLockedFields?: boolean;
|
|
108
|
-
calc?: string;
|
|
109
|
-
calcField?: string;
|
|
110
|
-
kpiConfig?: string;
|
|
111
|
-
chartType?: string;
|
|
112
|
-
xAxisField?: string;
|
|
113
|
-
xAxisDateFormat?: string;
|
|
114
|
-
xAxisTimeSeries?: boolean;
|
|
115
|
-
yAxisSerie?: string[];
|
|
116
|
-
legend?: string;
|
|
117
|
-
// KANBAN / TIMELINE viewState.fieldMapping (UUIDs only; slugs are not resolved here)
|
|
118
|
-
columnField?: string;
|
|
119
|
-
swimlaneField?: string;
|
|
120
|
-
startDateField?: string;
|
|
121
|
-
endDateField?: string;
|
|
122
|
-
labelField?: string;
|
|
123
|
-
groupField?: string;
|
|
124
|
-
subgroupField?: string;
|
|
125
|
-
progressField?: string;
|
|
126
|
-
cardTitleField?: string;
|
|
127
|
-
cardCategory1Field?: string;
|
|
128
|
-
cardCategory2Field?: string;
|
|
129
|
-
cardCategory3Field?: string;
|
|
130
|
-
cardCategory4Field?: string;
|
|
131
|
-
cardDescriptionField?: string;
|
|
132
|
-
aggregationMethod?: string;
|
|
133
|
-
aggregationField?: string;
|
|
134
|
-
viewState?: string;
|
|
135
|
-
viewStateFile?: string;
|
|
136
|
-
// HIERARCHY view.hierarchyConfig (UUIDs only; slugs are not resolved here)
|
|
137
|
-
hierarchyLabel?: string;
|
|
138
|
-
hierarchyInfo?: string[];
|
|
139
|
-
hierarchyNumber?: string[];
|
|
140
|
-
hierarchyRootName?: string;
|
|
141
|
-
hierarchyRootLabelField?: string;
|
|
142
|
-
hierarchyRootInfo?: string[];
|
|
143
|
-
hierarchyRootNumber?: string[];
|
|
144
|
-
hierarchyChild?: string[];
|
|
145
|
-
hierarchyConfig?: string;
|
|
146
|
-
hierarchyConfigFile?: string;
|
|
147
|
-
raw?: string;
|
|
148
|
-
rawFile?: string;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
interface LockConditionOption {
|
|
152
|
-
lockedField: string;
|
|
153
|
-
expression: ParsedExpression;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
interface LockMessageOption {
|
|
157
|
-
lockedField: string;
|
|
158
|
-
message: string;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
interface NavigationTargetMutationOptions {
|
|
162
|
-
sourceWidget?: string;
|
|
163
|
-
targetDisplay?: string;
|
|
164
|
-
targetVariable?: string;
|
|
165
|
-
label?: string;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
interface CrossWidgetFilterSetOptions {
|
|
169
|
-
sourceWidget?: string;
|
|
170
|
-
sourceField?: string;
|
|
171
|
-
sourceSchema?: string;
|
|
172
|
-
target?: string[];
|
|
173
|
-
raw?: string;
|
|
174
|
-
rawFile?: string;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function getCommandIo(stdout: CommandIo | undefined): CommandIo {
|
|
178
|
-
return stdout ?? (process.stdout as Writable);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function writeLine(stdout: CommandIo, value: string): void {
|
|
182
|
-
stdout.write(`${value}\n`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function getGlobalOptions(command: Command): GlobalOptions {
|
|
186
|
-
return command.optsWithGlobals<GlobalOptions>();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function emitJson(stdout: CommandIo, commandName: string, data: unknown): void {
|
|
190
|
-
writeLine(stdout, JSON.stringify(buildJsonSuccess(data, { command: commandName })));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function toConfigurationError(filePath: string, error: unknown): never {
|
|
194
|
-
if (error instanceof CliError) {
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
199
|
-
throw new CliError("configuration_error", `Config file not found at ${filePath}.`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (error instanceof Error) {
|
|
203
|
-
throw new CliError("configuration_error", error.message);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function createDisplaysApi(
|
|
210
|
-
command: Command,
|
|
211
|
-
filePath: string,
|
|
212
|
-
): { api: DisplaysApi; json: boolean; readOnly: boolean } {
|
|
213
|
-
try {
|
|
214
|
-
const config = readRuntimeConfig(filePath);
|
|
215
|
-
const runtime = resolveRuntimeContext(config, getGlobalOptions(command));
|
|
216
|
-
|
|
217
|
-
if (!runtime.baseUrl) {
|
|
218
|
-
throw new CliError("configuration_error", `Resolved profile "${runtime.profile}" is missing a base URL.`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
api: new DisplaysApi(
|
|
223
|
-
new ApiClient({
|
|
224
|
-
baseUrl: runtime.baseUrl,
|
|
225
|
-
apiKey: runtime.apiKey,
|
|
226
|
-
}),
|
|
227
|
-
),
|
|
228
|
-
json: runtime.json,
|
|
229
|
-
readOnly: runtime.readOnly,
|
|
230
|
-
};
|
|
231
|
-
} catch (error) {
|
|
232
|
-
toConfigurationError(filePath, error);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function ensureWritable(commandName: string, readOnly: boolean, mode: "write" | "destructive"): void {
|
|
237
|
-
if (readOnly) {
|
|
238
|
-
throw new CliError(
|
|
239
|
-
"command_not_allowed_in_read_only_mode",
|
|
240
|
-
`Command "${commandName}" is not allowed in read-only mode.`,
|
|
241
|
-
{ commandMode: mode, executionMode: "read-only" },
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function parseUuidOrSlugSelector(input: string): NormalizedUuidOrSlug {
|
|
247
|
-
try {
|
|
248
|
-
return normalizeUuidOrSlug(input);
|
|
249
|
-
} catch (error) {
|
|
250
|
-
const message = error instanceof Error ? error.message : "Invalid selector.";
|
|
251
|
-
throw new InvalidArgumentError(message);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const COMPARATOR_TO_DISPLAY_OPERATOR: Record<ExpressionComparator, string> = {
|
|
256
|
-
eq: "EQUAL",
|
|
257
|
-
neq: "NOT_EQUAL",
|
|
258
|
-
gt: "GREATER",
|
|
259
|
-
gte: "GREATER_OR_EQUAL",
|
|
260
|
-
lt: "LESS",
|
|
261
|
-
lte: "LESS_OR_EQUAL",
|
|
262
|
-
contains: "CONTAINS",
|
|
263
|
-
in: "ANY_OF",
|
|
264
|
-
is_empty: "IS_EMPTY",
|
|
265
|
-
is_not_empty: "IS_NOT_EMPTY",
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
function toDisplayFilter(parsed: ParsedExpression): Record<string, unknown> {
|
|
269
|
-
const values =
|
|
270
|
-
parsed.value === null
|
|
271
|
-
? []
|
|
272
|
-
: Array.isArray(parsed.value)
|
|
273
|
-
? [...parsed.value]
|
|
274
|
-
: [parsed.value];
|
|
275
|
-
return {
|
|
276
|
-
origin: { fieldId: parsed.field },
|
|
277
|
-
comparisonOperator: COMPARATOR_TO_DISPLAY_OPERATOR[parsed.comparator],
|
|
278
|
-
values,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function parseFilterExpression(input: string): ParsedExpression {
|
|
283
|
-
try {
|
|
284
|
-
return parseExpression(input);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
const message = error instanceof Error ? error.message : "Invalid filter expression.";
|
|
287
|
-
throw new InvalidArgumentError(message);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function collectFilters(value: string, previous: ParsedExpression[] = []): ParsedExpression[] {
|
|
292
|
-
return [...previous, parseFilterExpression(value)];
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function parseLockConditionOption(input: string): LockConditionOption {
|
|
296
|
-
const trimmed = input.trim();
|
|
297
|
-
let lockedField: string;
|
|
298
|
-
let expressionText: string;
|
|
299
|
-
|
|
300
|
-
if (trimmed.includes(":") && !trimmed.includes(" ")) {
|
|
301
|
-
const separatorIndex = trimmed.indexOf(":");
|
|
302
|
-
lockedField = trimmed.slice(0, separatorIndex).trim();
|
|
303
|
-
expressionText = trimmed.slice(separatorIndex + 1).trim();
|
|
304
|
-
} else {
|
|
305
|
-
const parts = trimmed.split(/\s+/);
|
|
306
|
-
lockedField = parts[0]?.trim() ?? "";
|
|
307
|
-
expressionText = parts.slice(1).join(" ").trim();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (!lockedField || !expressionText) {
|
|
311
|
-
throw new InvalidArgumentError(
|
|
312
|
-
'Expected "<lockedField>:<field>:<comparator>[:<value>]" (or space-separated), e.g. "status:invoice-number:is_empty".',
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return { lockedField, expression: parseFilterExpression(expressionText) };
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function collectLockConditions(value: string, previous: LockConditionOption[] = []): LockConditionOption[] {
|
|
320
|
-
return [...previous, parseLockConditionOption(value)];
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function parseLockMessageOption(input: string): LockMessageOption {
|
|
324
|
-
const separatorIndex = input.indexOf(":");
|
|
325
|
-
const lockedField = separatorIndex > 0 ? input.slice(0, separatorIndex).trim() : "";
|
|
326
|
-
const message = separatorIndex > 0 ? input.slice(separatorIndex + 1).trim() : "";
|
|
327
|
-
|
|
328
|
-
if (!lockedField || !message) {
|
|
329
|
-
throw new InvalidArgumentError('Expected "<lockedField>:<message>", e.g. "status:Locked once approved.".');
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return { lockedField, message };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function collectLockMessages(value: string, previous: LockMessageOption[] = []): LockMessageOption[] {
|
|
336
|
-
return [...previous, parseLockMessageOption(value)];
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Builds behavior.lockedFields from the --lock-* flags. Every flag names its
|
|
341
|
-
* locked field explicitly, so ordering between flags never matters. A field
|
|
342
|
-
* with no conditions is locked permanently; with conditions it locks when all
|
|
343
|
-
* of them match (AND). Returns [] for --clear-locked-fields and undefined when
|
|
344
|
-
* no lock flag was used.
|
|
345
|
-
*/
|
|
346
|
-
function buildLockedFields(options: WidgetMutationOptions): Array<Record<string, unknown>> | undefined {
|
|
347
|
-
const hasLockFlags =
|
|
348
|
-
(options.lockField?.length ?? 0) > 0 ||
|
|
349
|
-
(options.lockCondition?.length ?? 0) > 0 ||
|
|
350
|
-
(options.lockMessageBefore?.length ?? 0) > 0 ||
|
|
351
|
-
(options.lockMessageAfter?.length ?? 0) > 0;
|
|
352
|
-
|
|
353
|
-
if (options.clearLockedFields) {
|
|
354
|
-
if (hasLockFlags) {
|
|
355
|
-
throw new CliError(
|
|
356
|
-
"invalid_cli_usage",
|
|
357
|
-
'Use either "--clear-locked-fields" or the "--lock-*" flags, not both.',
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
return [];
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (!hasLockFlags) {
|
|
364
|
-
return undefined;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
interface LockRule {
|
|
368
|
-
conditions: Array<Record<string, unknown>>;
|
|
369
|
-
messageBeforeLock?: string;
|
|
370
|
-
messageAfterLock?: string;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const rules = new Map<string, LockRule>();
|
|
374
|
-
const ensureRule = (lockedField: string): LockRule => {
|
|
375
|
-
let rule = rules.get(lockedField);
|
|
376
|
-
if (!rule) {
|
|
377
|
-
rule = { conditions: [] };
|
|
378
|
-
rules.set(lockedField, rule);
|
|
379
|
-
}
|
|
380
|
-
return rule;
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
for (const lockedField of options.lockField ?? []) {
|
|
384
|
-
ensureRule(lockedField);
|
|
385
|
-
}
|
|
386
|
-
for (const { lockedField, expression } of options.lockCondition ?? []) {
|
|
387
|
-
ensureRule(lockedField).conditions.push(toDisplayFilter(expression));
|
|
388
|
-
}
|
|
389
|
-
for (const { lockedField, message } of options.lockMessageBefore ?? []) {
|
|
390
|
-
ensureRule(lockedField).messageBeforeLock = message;
|
|
391
|
-
}
|
|
392
|
-
for (const { lockedField, message } of options.lockMessageAfter ?? []) {
|
|
393
|
-
ensureRule(lockedField).messageAfterLock = message;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return [...rules.entries()].map(([lockedField, rule]) => ({
|
|
397
|
-
field: { fieldId: lockedField },
|
|
398
|
-
conditions: rule.conditions,
|
|
399
|
-
...(rule.messageBeforeLock !== undefined ? { messageBeforeLock: rule.messageBeforeLock } : {}),
|
|
400
|
-
...(rule.messageAfterLock !== undefined ? { messageAfterLock: rule.messageAfterLock } : {}),
|
|
401
|
-
}));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function parseBooleanFlag(input: string): boolean {
|
|
405
|
-
const normalized = input.trim().toLowerCase();
|
|
406
|
-
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
|
|
407
|
-
if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
|
|
408
|
-
throw new InvalidArgumentError(`Expected a boolean value, received "${input}".`);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function parseNonNegativeInteger(input: string): number {
|
|
412
|
-
if (!/^\d+$/.test(input.trim())) {
|
|
413
|
-
throw new InvalidArgumentError(`Expected a non-negative integer, received "${input}".`);
|
|
414
|
-
}
|
|
415
|
-
return Number(input);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function readRawObject(raw?: string, rawFile?: string): Record<string, unknown> {
|
|
419
|
-
if (raw && rawFile) {
|
|
420
|
-
throw new CliError("invalid_cli_usage", 'Use either "--raw" or "--raw-file", not both.');
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const source = rawFile ? readFileSync(rawFile, "utf8") : raw;
|
|
424
|
-
if (!source) {
|
|
425
|
-
return {};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
try {
|
|
429
|
-
const parsed = JSON.parse(source) as unknown;
|
|
430
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
431
|
-
throw new CliError("invalid_cli_usage", "Raw JSON payload must be a JSON object.");
|
|
432
|
-
}
|
|
433
|
-
return parsed as Record<string, unknown>;
|
|
434
|
-
} catch (error) {
|
|
435
|
-
if (error instanceof CliError) throw error;
|
|
436
|
-
throw new CliError("invalid_cli_usage", "Raw JSON payload is invalid.");
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function readNamedJsonOption(raw?: string, rawFile?: string, inlineFlag = "--option", fileFlag = "--option-file"): unknown {
|
|
441
|
-
if (raw && rawFile) {
|
|
442
|
-
throw new CliError("invalid_cli_usage", `Use either "${inlineFlag}" or "${fileFlag}", not both.`);
|
|
443
|
-
}
|
|
444
|
-
const source = rawFile ? readFileSync(rawFile, "utf8") : raw;
|
|
445
|
-
if (!source) return undefined;
|
|
446
|
-
try {
|
|
447
|
-
return JSON.parse(source) as unknown;
|
|
448
|
-
} catch {
|
|
449
|
-
throw new CliError("invalid_cli_usage", `${inlineFlag} payload is invalid JSON.`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function isEmptyObject(value: object): boolean {
|
|
454
|
-
return Object.keys(value).length === 0;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
458
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function deepMerge(
|
|
462
|
-
target: Record<string, unknown>,
|
|
463
|
-
source: Record<string, unknown>,
|
|
464
|
-
): Record<string, unknown> {
|
|
465
|
-
const out: Record<string, unknown> = { ...target };
|
|
466
|
-
for (const [key, value] of Object.entries(source)) {
|
|
467
|
-
const existing = out[key];
|
|
468
|
-
out[key] = isPlainObject(existing) && isPlainObject(value) ? deepMerge(existing, value) : value;
|
|
469
|
-
}
|
|
470
|
-
return out;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function assignDefined(target: Record<string, unknown>, key: string, value: unknown): void {
|
|
474
|
-
if (value !== undefined) target[key] = value;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function readNamedJsonObject(
|
|
478
|
-
raw: string | undefined,
|
|
479
|
-
rawFile: string | undefined,
|
|
480
|
-
inlineFlag: string,
|
|
481
|
-
fileFlag: string,
|
|
482
|
-
): Record<string, unknown> | undefined {
|
|
483
|
-
const parsed = readNamedJsonOption(raw, rawFile, inlineFlag, fileFlag);
|
|
484
|
-
if (parsed === undefined) return undefined;
|
|
485
|
-
if (!isPlainObject(parsed)) {
|
|
486
|
-
throw new CliError("invalid_cli_usage", `${inlineFlag} payload must be a JSON object.`);
|
|
487
|
-
}
|
|
488
|
-
return parsed;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const HIERARCHY_AGGREGATIONS = new Set([
|
|
492
|
-
"SUM",
|
|
493
|
-
"AVERAGE",
|
|
494
|
-
"MINIMUM",
|
|
495
|
-
"MAXIMUM",
|
|
496
|
-
"COUNT",
|
|
497
|
-
"COUNT_DISTINCT",
|
|
498
|
-
]);
|
|
499
|
-
|
|
500
|
-
function parseHierarchyInfo(value: string): Record<string, unknown> {
|
|
501
|
-
const idx = value.indexOf(":");
|
|
502
|
-
if (idx <= 0 || idx === value.length - 1) {
|
|
503
|
-
throw new CliError("invalid_cli_usage", `--hierarchy-info "${value}" must use the format <id:name>.`);
|
|
504
|
-
}
|
|
505
|
-
return { id: value.slice(0, idx).trim(), name: value.slice(idx + 1).trim() };
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function parseHierarchyNumber(value: string): Record<string, unknown> {
|
|
509
|
-
const parts = value.split(":");
|
|
510
|
-
if (parts.length < 3) {
|
|
511
|
-
throw new CliError(
|
|
512
|
-
"invalid_cli_usage",
|
|
513
|
-
`--hierarchy-number "${value}" must use the format <id:name:aggregation>.`,
|
|
514
|
-
);
|
|
515
|
-
}
|
|
516
|
-
const id = parts[0]?.trim() ?? "";
|
|
517
|
-
const aggregation = (parts[parts.length - 1]?.trim() ?? "").toUpperCase();
|
|
518
|
-
const name = parts.slice(1, -1).join(":").trim();
|
|
519
|
-
if (!id || !name) {
|
|
520
|
-
throw new CliError(
|
|
521
|
-
"invalid_cli_usage",
|
|
522
|
-
`--hierarchy-number "${value}" must use the format <id:name:aggregation>.`,
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
if (!HIERARCHY_AGGREGATIONS.has(aggregation)) {
|
|
526
|
-
throw new CliError(
|
|
527
|
-
"invalid_cli_usage",
|
|
528
|
-
`--hierarchy-number aggregation "${aggregation}" must be one of SUM, AVERAGE, MINIMUM, MAXIMUM, COUNT, COUNT_DISTINCT.`,
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
return { id, name, aggregation };
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function parseHierarchyMapping(value: string, flag: string): Record<string, unknown> {
|
|
535
|
-
const idx = value.indexOf(":");
|
|
536
|
-
if (idx <= 0 || idx === value.length - 1) {
|
|
537
|
-
throw new CliError("invalid_cli_usage", `${flag} "${value}" must use the format <columnId:fieldId>.`);
|
|
538
|
-
}
|
|
539
|
-
return { columnId: value.slice(0, idx).trim(), fieldId: value.slice(idx + 1).trim() };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function buildWidgetFieldMapping(options: WidgetMutationOptions): Record<string, unknown> | undefined {
|
|
543
|
-
const mapping: Record<string, unknown> = {};
|
|
544
|
-
assignDefined(mapping, "columnField", options.columnField);
|
|
545
|
-
assignDefined(mapping, "swimlaneField", options.swimlaneField);
|
|
546
|
-
assignDefined(mapping, "startDateField", options.startDateField);
|
|
547
|
-
assignDefined(mapping, "endDateField", options.endDateField);
|
|
548
|
-
assignDefined(mapping, "labelField", options.labelField);
|
|
549
|
-
assignDefined(mapping, "groupField", options.groupField);
|
|
550
|
-
assignDefined(mapping, "subgroupField", options.subgroupField);
|
|
551
|
-
assignDefined(mapping, "progressField", options.progressField);
|
|
552
|
-
|
|
553
|
-
const cardConfig: Record<string, unknown> = {};
|
|
554
|
-
assignDefined(cardConfig, "titleField", options.cardTitleField);
|
|
555
|
-
assignDefined(cardConfig, "category1Field", options.cardCategory1Field);
|
|
556
|
-
assignDefined(cardConfig, "category2Field", options.cardCategory2Field);
|
|
557
|
-
assignDefined(cardConfig, "category3Field", options.cardCategory3Field);
|
|
558
|
-
assignDefined(cardConfig, "category4Field", options.cardCategory4Field);
|
|
559
|
-
assignDefined(cardConfig, "descriptionField", options.cardDescriptionField);
|
|
560
|
-
if (Object.keys(cardConfig).length > 0) mapping.cardConfig = cardConfig;
|
|
561
|
-
|
|
562
|
-
const aggregationConfig: Record<string, unknown> = {};
|
|
563
|
-
assignDefined(aggregationConfig, "method", options.aggregationMethod);
|
|
564
|
-
assignDefined(aggregationConfig, "fieldId", options.aggregationField);
|
|
565
|
-
if (Object.keys(aggregationConfig).length > 0) mapping.aggregationConfig = aggregationConfig;
|
|
566
|
-
|
|
567
|
-
return Object.keys(mapping).length > 0 ? mapping : undefined;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
function buildHierarchyConfig(options: WidgetMutationOptions): Record<string, unknown> | undefined {
|
|
571
|
-
const base = readNamedJsonObject(
|
|
572
|
-
options.hierarchyConfig,
|
|
573
|
-
options.hierarchyConfigFile,
|
|
574
|
-
"--hierarchy-config",
|
|
575
|
-
"--hierarchy-config-file",
|
|
576
|
-
);
|
|
577
|
-
const patch: Record<string, unknown> = {};
|
|
578
|
-
|
|
579
|
-
const columns: Record<string, unknown> = {};
|
|
580
|
-
assignDefined(columns, "label", options.hierarchyLabel);
|
|
581
|
-
if (options.hierarchyInfo && options.hierarchyInfo.length > 0) {
|
|
582
|
-
columns.infos = options.hierarchyInfo.map(parseHierarchyInfo);
|
|
583
|
-
}
|
|
584
|
-
if (options.hierarchyNumber && options.hierarchyNumber.length > 0) {
|
|
585
|
-
columns.numbers = options.hierarchyNumber.map(parseHierarchyNumber);
|
|
586
|
-
}
|
|
587
|
-
if (Object.keys(columns).length > 0) patch.columns = columns;
|
|
588
|
-
|
|
589
|
-
const rootLevel: Record<string, unknown> = {};
|
|
590
|
-
assignDefined(rootLevel, "name", options.hierarchyRootName);
|
|
591
|
-
assignDefined(rootLevel, "labelFieldId", options.hierarchyRootLabelField);
|
|
592
|
-
if (options.hierarchyRootInfo && options.hierarchyRootInfo.length > 0) {
|
|
593
|
-
rootLevel.infoMappings = options.hierarchyRootInfo.map((entry) =>
|
|
594
|
-
parseHierarchyMapping(entry, "--hierarchy-root-info"),
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
if (options.hierarchyRootNumber && options.hierarchyRootNumber.length > 0) {
|
|
598
|
-
rootLevel.numberMappings = options.hierarchyRootNumber.map((entry) =>
|
|
599
|
-
parseHierarchyMapping(entry, "--hierarchy-root-number"),
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
if (Object.keys(rootLevel).length > 0) patch.rootLevel = rootLevel;
|
|
603
|
-
|
|
604
|
-
if (options.hierarchyChild && options.hierarchyChild.length > 0) {
|
|
605
|
-
patch.childLevels = options.hierarchyChild.map((entry, index) => {
|
|
606
|
-
let parsed: unknown;
|
|
607
|
-
try {
|
|
608
|
-
parsed = JSON.parse(entry);
|
|
609
|
-
} catch {
|
|
610
|
-
throw new CliError("invalid_cli_usage", `--hierarchy-child payload #${index + 1} is invalid JSON.`);
|
|
611
|
-
}
|
|
612
|
-
if (!isPlainObject(parsed)) {
|
|
613
|
-
throw new CliError(
|
|
614
|
-
"invalid_cli_usage",
|
|
615
|
-
`--hierarchy-child payload #${index + 1} must be a JSON object.`,
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
return parsed;
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
if (base === undefined && Object.keys(patch).length === 0) return undefined;
|
|
623
|
-
return deepMerge(base ?? {}, patch);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function displayRows(displays: readonly DisplaySummary[]): Record<string, unknown>[] {
|
|
627
|
-
return displays.map((display) => ({
|
|
628
|
-
id: display.id,
|
|
629
|
-
name: display.name ?? "",
|
|
630
|
-
layoutTemplate: display.layoutTemplate ?? "",
|
|
631
|
-
variableCount: display.variableCount ?? "",
|
|
632
|
-
widgetCount: display.widgetCount ?? "",
|
|
633
|
-
...folderColumns(display.folder),
|
|
634
|
-
}));
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function getDisplaySubresource(value: unknown): Record<string, unknown>[] {
|
|
638
|
-
if (!Array.isArray(value)) {
|
|
639
|
-
return [];
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
return value.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === "object");
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function displayFolderRows(folders: readonly ApiDisplayFolder[]): Record<string, unknown>[] {
|
|
646
|
-
return folders.map((folder) => ({
|
|
647
|
-
id: folder.id ?? "",
|
|
648
|
-
name: folder.name ?? "",
|
|
649
|
-
isRoot: folder.isRoot ?? "",
|
|
650
|
-
displayCount: folder.displayCount ?? "",
|
|
651
|
-
}));
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
async function resolveDisplayFolderId(
|
|
655
|
-
api: DisplaysApi,
|
|
656
|
-
selector: NormalizedUuidOrSlug,
|
|
657
|
-
): Promise<string> {
|
|
658
|
-
if (selector.kind === "uuid") {
|
|
659
|
-
return selector.value;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const folders = await api.listFolders();
|
|
663
|
-
const matches = folders.filter((folder) => folder.name === selector.value);
|
|
664
|
-
|
|
665
|
-
if (matches.length === 1 && typeof matches[0]?.id === "string" && matches[0].id.length > 0) {
|
|
666
|
-
return matches[0].id;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if (matches.length > 1) {
|
|
670
|
-
throw new CliError(
|
|
671
|
-
"invalid_cli_usage",
|
|
672
|
-
`Folder "${selector.value}" is ambiguous. Found ${matches.length} matches.`,
|
|
673
|
-
);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
throw new CliError("invalid_cli_usage", `Folder "${selector.value}" was not found.`);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
function buildVariableBody(options: VariableMutationOptions): Record<string, unknown> {
|
|
680
|
-
const body = {
|
|
681
|
-
...readRawObject(options.raw, options.rawFile),
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
if (options.type !== undefined) body.type = options.type;
|
|
685
|
-
if (options.name !== undefined) body.name = options.name;
|
|
686
|
-
if (options.allowMultiple !== undefined) body.allowMultiple = options.allowMultiple;
|
|
687
|
-
if (options.static !== undefined) body.staticVariable = options.static;
|
|
688
|
-
if (options.default !== undefined) body.defaultValue = options.default;
|
|
689
|
-
if (options.defaultValue && options.defaultValue.length > 0) body.defaultValues = options.defaultValue;
|
|
690
|
-
if (options.sourceSchema || options.sourceField) {
|
|
691
|
-
body.source = {
|
|
692
|
-
schemaId: options.sourceSchema,
|
|
693
|
-
fieldId: options.sourceField,
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
if (options.filter && options.filter.length > 0) {
|
|
697
|
-
body.filters = options.filter.map(toDisplayFilter);
|
|
698
|
-
}
|
|
699
|
-
if (
|
|
700
|
-
options.dateMode ||
|
|
701
|
-
options.dateDynamicOption ||
|
|
702
|
-
options.dateSpecific ||
|
|
703
|
-
options.dateRangeStart ||
|
|
704
|
-
options.dateRangeEnd
|
|
705
|
-
) {
|
|
706
|
-
body.date = {
|
|
707
|
-
mode: options.dateMode,
|
|
708
|
-
dynamicOption: options.dateDynamicOption,
|
|
709
|
-
specificDate: options.dateSpecific,
|
|
710
|
-
rangeStart: options.dateRangeStart,
|
|
711
|
-
rangeEnd: options.dateRangeEnd,
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
if (isEmptyObject(body)) {
|
|
716
|
-
throw new CliError("invalid_cli_usage", "Nothing to send for variable mutation.");
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return body;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function buildWidgetBody(options: WidgetMutationOptions): Record<string, unknown> {
|
|
723
|
-
const body = {
|
|
724
|
-
...readRawObject(options.raw, options.rawFile),
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
if (options.type !== undefined) {
|
|
728
|
-
const normalizedType = options.type.toLowerCase() === "table"
|
|
729
|
-
? "TABLE_OR_SUBTABLE"
|
|
730
|
-
: options.type.toUpperCase();
|
|
731
|
-
body.type = normalizedType;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (
|
|
735
|
-
options.name !== undefined ||
|
|
736
|
-
options.description !== undefined ||
|
|
737
|
-
options.icon !== undefined ||
|
|
738
|
-
options.color !== undefined
|
|
739
|
-
) {
|
|
740
|
-
body.presentation = {
|
|
741
|
-
...(typeof body.presentation === "object" && body.presentation ? body.presentation as Record<string, unknown> : {}),
|
|
742
|
-
name: options.name,
|
|
743
|
-
description: options.description,
|
|
744
|
-
icon: options.icon,
|
|
745
|
-
color: options.color,
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
if (options.originResource !== undefined || options.originSubschema !== undefined) {
|
|
750
|
-
body.source = {
|
|
751
|
-
...(typeof body.source === "object" && body.source ? body.source as Record<string, unknown> : {}),
|
|
752
|
-
originResourceId: options.originResource,
|
|
753
|
-
isOriginSubSchema: options.originSubschema,
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
const layout = readNamedJsonOption(options.layout, options.layoutFile, "--layout", "--layout-file");
|
|
758
|
-
if (options.tab || options.slot || layout !== undefined) {
|
|
759
|
-
body.placement = {
|
|
760
|
-
...(typeof body.placement === "object" && body.placement ? body.placement as Record<string, unknown> : {}),
|
|
761
|
-
tabId: options.tab,
|
|
762
|
-
slotId: options.slot,
|
|
763
|
-
layout,
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
const viewPatch: Record<string, unknown> = {};
|
|
768
|
-
if (options.field && options.field.length > 0) {
|
|
769
|
-
viewPatch.fields = options.field;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const fieldMapping = buildWidgetFieldMapping(options);
|
|
773
|
-
const viewStateBase = readNamedJsonObject(
|
|
774
|
-
options.viewState,
|
|
775
|
-
options.viewStateFile,
|
|
776
|
-
"--view-state",
|
|
777
|
-
"--view-state-file",
|
|
778
|
-
);
|
|
779
|
-
let viewState: Record<string, unknown> | undefined = viewStateBase ? { ...viewStateBase } : undefined;
|
|
780
|
-
if (fieldMapping) {
|
|
781
|
-
viewState = deepMerge(viewState ?? {}, { fieldMapping });
|
|
782
|
-
}
|
|
783
|
-
if (viewState !== undefined) {
|
|
784
|
-
viewPatch.viewAdditionalProperties = { viewState };
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const hierarchyConfig = buildHierarchyConfig(options);
|
|
788
|
-
if (hierarchyConfig !== undefined) {
|
|
789
|
-
viewPatch.hierarchyConfig = hierarchyConfig;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
if (Object.keys(viewPatch).length > 0) {
|
|
793
|
-
const existingView =
|
|
794
|
-
typeof body.view === "object" && body.view ? (body.view as Record<string, unknown>) : {};
|
|
795
|
-
body.view = deepMerge(existingView, viewPatch);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
const queryMerge: Record<string, unknown> = {};
|
|
799
|
-
if (options.sort && options.sort.length > 0) queryMerge.sorts = options.sort;
|
|
800
|
-
if (options.filter && options.filter.length > 0) queryMerge.filters = options.filter.map(toDisplayFilter);
|
|
801
|
-
if (Object.keys(queryMerge).length > 0) {
|
|
802
|
-
body.query = {
|
|
803
|
-
...(typeof body.query === "object" && body.query ? body.query as Record<string, unknown> : {}),
|
|
804
|
-
...queryMerge,
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if (
|
|
809
|
-
options.widgetReadOnly !== undefined ||
|
|
810
|
-
options.hideAddButton !== undefined ||
|
|
811
|
-
options.hideDeleteButton !== undefined ||
|
|
812
|
-
options.hideSortButton !== undefined ||
|
|
813
|
-
options.hideFilterButton !== undefined ||
|
|
814
|
-
options.hideSeeButton !== undefined
|
|
815
|
-
) {
|
|
816
|
-
body.behavior = {
|
|
817
|
-
...(typeof body.behavior === "object" && body.behavior ? body.behavior as Record<string, unknown> : {}),
|
|
818
|
-
readOnly: options.widgetReadOnly,
|
|
819
|
-
hideAddButton: options.hideAddButton,
|
|
820
|
-
hideDeleteButton: options.hideDeleteButton,
|
|
821
|
-
hideSortButton: options.hideSortButton,
|
|
822
|
-
hideFilterButton: options.hideFilterButton,
|
|
823
|
-
hideSeeButton: options.hideSeeButton,
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
const lockedFields = buildLockedFields(options);
|
|
828
|
-
if (lockedFields !== undefined) {
|
|
829
|
-
body.behavior = {
|
|
830
|
-
...(typeof body.behavior === "object" && body.behavior ? body.behavior as Record<string, unknown> : {}),
|
|
831
|
-
lockedFields,
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
if (options.calc || options.calcField || options.kpiConfig) {
|
|
836
|
-
body.kpi = {
|
|
837
|
-
...(typeof body.kpi === "object" && body.kpi ? body.kpi as Record<string, unknown> : {}),
|
|
838
|
-
typeCalc: options.calc,
|
|
839
|
-
fieldCalc: options.calcField,
|
|
840
|
-
config: readNamedJsonOption(options.kpiConfig, undefined, "--kpi-config"),
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
const hasChartFlags =
|
|
845
|
-
options.chartType !== undefined ||
|
|
846
|
-
options.xAxisField !== undefined ||
|
|
847
|
-
options.xAxisDateFormat !== undefined ||
|
|
848
|
-
options.xAxisTimeSeries !== undefined ||
|
|
849
|
-
(options.yAxisSerie?.length ?? 0) > 0 ||
|
|
850
|
-
options.legend !== undefined;
|
|
851
|
-
if (hasChartFlags) {
|
|
852
|
-
const legend = readNamedJsonOption(options.legend, undefined, "--legend");
|
|
853
|
-
body.chart = {
|
|
854
|
-
...(typeof body.chart === "object" && body.chart ? body.chart as Record<string, unknown> : {}),
|
|
855
|
-
...(options.chartType !== undefined ? { chartType: options.chartType } : {}),
|
|
856
|
-
...(options.xAxisField !== undefined ? { xAxisFieldId: options.xAxisField } : {}),
|
|
857
|
-
...(options.xAxisDateFormat !== undefined ? { xAxisDateFormat: options.xAxisDateFormat } : {}),
|
|
858
|
-
...(options.xAxisTimeSeries !== undefined ? { xAxisAsTimeSerie: options.xAxisTimeSeries } : {}),
|
|
859
|
-
...(options.yAxisSerie && options.yAxisSerie.length > 0
|
|
860
|
-
? { yAxisSeries: options.yAxisSerie.map((entry) => JSON.parse(entry)) }
|
|
861
|
-
: {}),
|
|
862
|
-
...(legend !== undefined ? { legend } : {}),
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
if (isEmptyObject(body)) {
|
|
867
|
-
throw new CliError("invalid_cli_usage", "Nothing to send for widget mutation.");
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
return body;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
function buildNavigationTargetBody(options: NavigationTargetMutationOptions): Record<string, unknown> {
|
|
874
|
-
const body: Record<string, unknown> = {};
|
|
875
|
-
if (options.sourceWidget !== undefined) body.sourceWidgetId = options.sourceWidget;
|
|
876
|
-
if (options.targetDisplay !== undefined) body.targetDisplayId = options.targetDisplay;
|
|
877
|
-
if (options.targetVariable !== undefined) body.targetVariableId = options.targetVariable;
|
|
878
|
-
if (options.label !== undefined) body.label = options.label;
|
|
879
|
-
if (isEmptyObject(body)) {
|
|
880
|
-
throw new CliError("invalid_cli_usage", "Nothing to send for navigation target mutation.");
|
|
881
|
-
}
|
|
882
|
-
return body;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
function readRawArray(raw?: string, rawFile?: string): unknown[] {
|
|
886
|
-
if (raw && rawFile) {
|
|
887
|
-
throw new CliError("invalid_cli_usage", 'Use either "--raw" or "--raw-file", not both.');
|
|
888
|
-
}
|
|
889
|
-
const source = rawFile ? readFileSync(rawFile, "utf8") : raw;
|
|
890
|
-
if (!source) {
|
|
891
|
-
return [];
|
|
892
|
-
}
|
|
893
|
-
try {
|
|
894
|
-
const parsed = JSON.parse(source) as unknown;
|
|
895
|
-
if (!Array.isArray(parsed)) {
|
|
896
|
-
throw new CliError("invalid_cli_usage", "Raw cross widget filter payload must be a JSON array of configurations.");
|
|
897
|
-
}
|
|
898
|
-
return parsed;
|
|
899
|
-
} catch (error) {
|
|
900
|
-
if (error instanceof CliError) throw error;
|
|
901
|
-
throw new CliError("invalid_cli_usage", "Raw JSON payload is invalid.");
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
// The endpoint replaces the whole array of configurations. With flags a single configuration is
|
|
906
|
-
// built and wrapped in an array; use --raw/--raw-file to send several configurations at once.
|
|
907
|
-
function buildCrossWidgetFilterBody(options: CrossWidgetFilterSetOptions): unknown[] {
|
|
908
|
-
if (options.raw !== undefined || options.rawFile !== undefined) {
|
|
909
|
-
return readRawArray(options.raw, options.rawFile);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
if (!options.sourceWidget || !options.sourceField) {
|
|
913
|
-
throw new CliError(
|
|
914
|
-
"invalid_cli_usage",
|
|
915
|
-
"Provide --source-widget and --source-field, or --raw/--raw-file with the full configuration array.",
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
const source: Record<string, unknown> = {
|
|
920
|
-
widgetId: options.sourceWidget,
|
|
921
|
-
fieldId: options.sourceField,
|
|
922
|
-
};
|
|
923
|
-
if (options.sourceSchema) {
|
|
924
|
-
source.schemaId = options.sourceSchema;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
return [
|
|
928
|
-
{
|
|
929
|
-
source,
|
|
930
|
-
targets: (options.target ?? []).map((entry) => {
|
|
931
|
-
const [widgetId, fieldId] = entry.split(":");
|
|
932
|
-
if (!widgetId || !fieldId) {
|
|
933
|
-
throw new CliError("invalid_cli_usage", `Invalid --target entry "${entry}". Use <widgetId:fieldId>.`);
|
|
934
|
-
}
|
|
935
|
-
return { widgetId, fieldId };
|
|
936
|
-
}),
|
|
937
|
-
},
|
|
938
|
-
];
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
export function registerDisplaysCommands(
|
|
942
|
-
program: Command,
|
|
943
|
-
options: RegisterDisplaysCommandsOptions = {},
|
|
944
|
-
): void {
|
|
945
|
-
const filePath = options.configPath ?? defaultConfigPath;
|
|
946
|
-
const stdout = getCommandIo(options.stdout);
|
|
947
|
-
const displays = program.command("displays").description("Manage displays");
|
|
948
|
-
|
|
949
|
-
displays
|
|
950
|
-
.command("list")
|
|
951
|
-
.description("List displays")
|
|
952
|
-
.action(async function action(this: Command) {
|
|
953
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
954
|
-
const data = await api.listDisplays();
|
|
955
|
-
if (json) {
|
|
956
|
-
emitJson(stdout, "displays list", data);
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
printTable(
|
|
960
|
-
displayRows(data),
|
|
961
|
-
["id", "name", "layoutTemplate", "variableCount", "widgetCount", ...FOLDER_TABLE_KEYS],
|
|
962
|
-
stdout,
|
|
963
|
-
);
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
displays
|
|
967
|
-
.command("get")
|
|
968
|
-
.description("Get a display")
|
|
969
|
-
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
970
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
971
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
972
|
-
const data = await api.getDisplay(display);
|
|
973
|
-
if (json) {
|
|
974
|
-
emitJson(stdout, "displays get", data);
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
printTable([{ id: data.id, name: data.name ?? "", layoutTemplate: data.layout?.template ?? "" }], ["id", "name", "layoutTemplate"], stdout);
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
displays
|
|
981
|
-
.command("create")
|
|
982
|
-
.description("Create a display")
|
|
983
|
-
.requiredOption("--name <name>", "display name")
|
|
984
|
-
.option("--description <text>", "display description")
|
|
985
|
-
.requiredOption("--template <template>", "layout template")
|
|
986
|
-
.option("--freeform-layouts <json>", "freeform layouts JSON")
|
|
987
|
-
.option("--freeform-layouts-file <path>", "freeform layouts JSON file")
|
|
988
|
-
.action(async function action(this: Command, commandOptions: DisplayCreateOptions) {
|
|
989
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
990
|
-
ensureWritable("displays create", readOnly, "write");
|
|
991
|
-
const layout: Record<string, unknown> = { template: commandOptions.template };
|
|
992
|
-
const freeformLayouts = readNamedJsonOption(
|
|
993
|
-
commandOptions.freeformLayouts,
|
|
994
|
-
commandOptions.freeformLayoutsFile,
|
|
995
|
-
"--freeform-layouts",
|
|
996
|
-
"--freeform-layouts-file",
|
|
997
|
-
);
|
|
998
|
-
if (freeformLayouts !== undefined) layout.freeformLayouts = freeformLayouts;
|
|
999
|
-
const data = await api.createDisplay({
|
|
1000
|
-
name: commandOptions.name,
|
|
1001
|
-
description: commandOptions.description,
|
|
1002
|
-
layout,
|
|
1003
|
-
});
|
|
1004
|
-
if (json) {
|
|
1005
|
-
emitJson(stdout, "displays create", data);
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
|
-
writeLine(stdout, `Created display "${commandOptions.name}".`);
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
displays
|
|
1012
|
-
.command("update")
|
|
1013
|
-
.description("Update a display")
|
|
1014
|
-
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1015
|
-
.option("--name <name>", "display name")
|
|
1016
|
-
.option("--description <text>", 'display description (use "" to clear)')
|
|
1017
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: DisplayUpdateOptions) {
|
|
1018
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1019
|
-
ensureWritable("displays update", readOnly, "write");
|
|
1020
|
-
if (commandOptions.name === undefined && commandOptions.description === undefined) {
|
|
1021
|
-
throw new CliError("invalid_cli_usage", "Nothing to update.");
|
|
1022
|
-
}
|
|
1023
|
-
if (commandOptions.name !== undefined && commandOptions.name.trim().length === 0) {
|
|
1024
|
-
throw new CliError("invalid_cli_usage", "Display name cannot be empty.");
|
|
1025
|
-
}
|
|
1026
|
-
const data = await api.updateDisplay(display, {
|
|
1027
|
-
name: commandOptions.name,
|
|
1028
|
-
description: commandOptions.description,
|
|
1029
|
-
});
|
|
1030
|
-
if (json) {
|
|
1031
|
-
emitJson(stdout, "displays update", data);
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
writeLine(stdout, `Updated display "${display.value}".`);
|
|
1035
|
-
});
|
|
1036
|
-
|
|
1037
|
-
displays
|
|
1038
|
-
.command("delete")
|
|
1039
|
-
.description("Delete a display")
|
|
1040
|
-
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1041
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1042
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1043
|
-
ensureWritable("displays delete", readOnly, "destructive");
|
|
1044
|
-
const data = await api.deleteDisplay(display);
|
|
1045
|
-
if (json) {
|
|
1046
|
-
emitJson(stdout, "displays delete", data);
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
writeLine(stdout, `Deleted display "${display.value}".`);
|
|
1050
|
-
});
|
|
1051
|
-
|
|
1052
|
-
const folders = displays
|
|
1053
|
-
.command("folders")
|
|
1054
|
-
.description("Manage display folders (flat: a folder only has a name, no order or properties)");
|
|
1055
|
-
|
|
1056
|
-
folders
|
|
1057
|
-
.command("list")
|
|
1058
|
-
.description("List display folders (non-root folders plus the root bucket, whose name is null)")
|
|
1059
|
-
.action(async function action(this: Command) {
|
|
1060
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
1061
|
-
const data = await api.listFolders();
|
|
1062
|
-
if (json) {
|
|
1063
|
-
emitJson(stdout, "displays folders list", data);
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
printTable(displayFolderRows(data), ["id", "name", "isRoot", "displayCount"], stdout);
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
folders
|
|
1070
|
-
.command("create")
|
|
1071
|
-
.description("Create a display folder")
|
|
1072
|
-
.requiredOption("--name <name>", "folder name")
|
|
1073
|
-
.action(async function action(this: Command, commandOptions: { name?: string }) {
|
|
1074
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1075
|
-
ensureWritable("displays folders create", readOnly, "write");
|
|
1076
|
-
if (commandOptions.name === undefined) {
|
|
1077
|
-
throw new CliError("invalid_cli_usage", 'Provide folder name via "--name".');
|
|
1078
|
-
}
|
|
1079
|
-
const data = await api.createFolder({ name: commandOptions.name });
|
|
1080
|
-
if (json) {
|
|
1081
|
-
emitJson(stdout, "displays folders create", data);
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
writeLine(stdout, `Created display folder "${String(data.name ?? commandOptions.name)}".`);
|
|
1085
|
-
});
|
|
1086
|
-
|
|
1087
|
-
folders
|
|
1088
|
-
.command("update")
|
|
1089
|
-
.description("Rename a display folder")
|
|
1090
|
-
.argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
|
|
1091
|
-
.requiredOption("--name <name>", "folder name")
|
|
1092
|
-
.action(async function action(
|
|
1093
|
-
this: Command,
|
|
1094
|
-
folder: NormalizedUuidOrSlug,
|
|
1095
|
-
commandOptions: { name?: string },
|
|
1096
|
-
) {
|
|
1097
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1098
|
-
ensureWritable("displays folders update", readOnly, "write");
|
|
1099
|
-
if (commandOptions.name === undefined) {
|
|
1100
|
-
throw new CliError("invalid_cli_usage", 'Provide folder name via "--name".');
|
|
1101
|
-
}
|
|
1102
|
-
const folderId = await resolveDisplayFolderId(api, folder);
|
|
1103
|
-
const data = await api.updateFolder({ kind: "uuid", value: folderId }, { name: commandOptions.name });
|
|
1104
|
-
if (json) {
|
|
1105
|
-
emitJson(stdout, "displays folders update", data);
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
writeLine(stdout, `Updated display folder "${String(data.name ?? folder.value)}".`);
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
folders
|
|
1112
|
-
.command("delete")
|
|
1113
|
-
.description("Delete a display folder (its displays move back to the root folder)")
|
|
1114
|
-
.argument("<folder>", "folder UUID or exact name", parseUuidOrSlugSelector)
|
|
1115
|
-
.action(async function action(this: Command, folder: NormalizedUuidOrSlug) {
|
|
1116
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1117
|
-
ensureWritable("displays folders delete", readOnly, "destructive");
|
|
1118
|
-
const folderId = await resolveDisplayFolderId(api, folder);
|
|
1119
|
-
const data = await api.deleteFolder({ kind: "uuid", value: folderId });
|
|
1120
|
-
if (json) {
|
|
1121
|
-
emitJson(stdout, "displays folders delete", data);
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
writeLine(stdout, `Deleted display folder "${folder.value}".`);
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
folders
|
|
1128
|
-
.command("move")
|
|
1129
|
-
.description("Move a display into a folder")
|
|
1130
|
-
.argument("<display>", "display UUID", parseUuidOrSlugSelector)
|
|
1131
|
-
.requiredOption(
|
|
1132
|
-
"--to <folder>",
|
|
1133
|
-
"target folder UUID or exact name (use the root folder to move it out of any folder)",
|
|
1134
|
-
parseUuidOrSlugSelector,
|
|
1135
|
-
)
|
|
1136
|
-
.action(async function action(
|
|
1137
|
-
this: Command,
|
|
1138
|
-
display: NormalizedUuidOrSlug,
|
|
1139
|
-
commandOptions: { to?: NormalizedUuidOrSlug },
|
|
1140
|
-
) {
|
|
1141
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1142
|
-
ensureWritable("displays folders move", readOnly, "write");
|
|
1143
|
-
if (!commandOptions.to) {
|
|
1144
|
-
throw new CliError("invalid_cli_usage", 'Provide target folder via "--to".');
|
|
1145
|
-
}
|
|
1146
|
-
const targetFolderId = await resolveDisplayFolderId(api, commandOptions.to);
|
|
1147
|
-
const data = await api.moveDisplay({ displayId: display.value, targetFolderId });
|
|
1148
|
-
if (json) {
|
|
1149
|
-
emitJson(stdout, "displays folders move", data);
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
writeLine(stdout, `Moved display "${display.value}" to folder "${targetFolderId}".`);
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
const layout = displays.command("layout").description("Manage display layout");
|
|
1156
|
-
layout
|
|
1157
|
-
.command("get")
|
|
1158
|
-
.description("Get display layout")
|
|
1159
|
-
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1160
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1161
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
1162
|
-
const data = await api.getDisplay(display);
|
|
1163
|
-
const layoutData = data.layout ?? null;
|
|
1164
|
-
if (json) {
|
|
1165
|
-
emitJson(stdout, "displays layout get", layoutData);
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
printTable(layoutData ? [{ template: layoutData.template ?? "", freeformLayouts: layoutData.freeformLayouts ?? "" }] : [], ["template", "freeformLayouts"], stdout);
|
|
1169
|
-
});
|
|
1170
|
-
layout
|
|
1171
|
-
.command("update")
|
|
1172
|
-
.description("Update display layout")
|
|
1173
|
-
.argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1174
|
-
.requiredOption("--template <template>", "layout template")
|
|
1175
|
-
.option("--freeform-layouts <json>", "freeform layouts JSON")
|
|
1176
|
-
.option("--freeform-layouts-file <path>", "freeform layouts JSON file")
|
|
1177
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: LayoutUpdateOptions) {
|
|
1178
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1179
|
-
ensureWritable("displays layout update", readOnly, "write");
|
|
1180
|
-
const body: Record<string, unknown> = { template: commandOptions.template };
|
|
1181
|
-
const freeformLayouts = readNamedJsonOption(commandOptions.freeformLayouts, commandOptions.freeformLayoutsFile, "--freeform-layouts", "--freeform-layouts-file");
|
|
1182
|
-
if (freeformLayouts !== undefined) body.freeformLayouts = freeformLayouts;
|
|
1183
|
-
const data = await api.patchLayout(display, body);
|
|
1184
|
-
if (json) {
|
|
1185
|
-
emitJson(stdout, "displays layout update", data);
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
writeLine(stdout, `Updated layout for display "${display.value}".`);
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
const tabs = displays.command("tabs").description("Manage display tabs");
|
|
1192
|
-
tabs.command("list").description("List tabs").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1193
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
1194
|
-
const data = await api.getDisplay(display);
|
|
1195
|
-
const tabsData = getDisplaySubresource(data.layout?.tabs);
|
|
1196
|
-
if (json) { emitJson(stdout, "displays tabs list", tabsData); return; }
|
|
1197
|
-
printTable(tabsData, undefined, stdout);
|
|
1198
|
-
});
|
|
1199
|
-
tabs.command("create").description("Create a tab").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).option("--name <name>").option("--orientation <orientation>").action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: TabMutationOptions) {
|
|
1200
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1201
|
-
ensureWritable("displays tabs create", readOnly, "write");
|
|
1202
|
-
const data = await api.createTab(display, { name: commandOptions.name, orientation: commandOptions.orientation });
|
|
1203
|
-
if (json) { emitJson(stdout, "displays tabs create", data); return; }
|
|
1204
|
-
writeLine(stdout, `Created tab on display "${display.value}".`);
|
|
1205
|
-
});
|
|
1206
|
-
tabs.command("update").description("Update a tab").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<tab>", "tab id").option("--name <name>").option("--orientation <orientation>").action(async function action(this: Command, display: NormalizedUuidOrSlug, tabId: string, commandOptions: TabMutationOptions) {
|
|
1207
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1208
|
-
ensureWritable("displays tabs update", readOnly, "write");
|
|
1209
|
-
const data = await api.updateTab(display, tabId, { name: commandOptions.name, orientation: commandOptions.orientation });
|
|
1210
|
-
if (json) { emitJson(stdout, "displays tabs update", data); return; }
|
|
1211
|
-
writeLine(stdout, `Updated tab "${tabId}".`);
|
|
1212
|
-
});
|
|
1213
|
-
tabs.command("delete").description("Delete a tab").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<tab>", "tab id").action(async function action(this: Command, display: NormalizedUuidOrSlug, tabId: string) {
|
|
1214
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath);
|
|
1215
|
-
ensureWritable("displays tabs delete", readOnly, "destructive");
|
|
1216
|
-
const data = await api.deleteTab(display, tabId);
|
|
1217
|
-
if (json) { emitJson(stdout, "displays tabs delete", data); return; }
|
|
1218
|
-
writeLine(stdout, `Deleted tab "${tabId}".`);
|
|
1219
|
-
});
|
|
1220
|
-
|
|
1221
|
-
const variables = displays.command("variables").description("Manage display variables");
|
|
1222
|
-
variables.command("list").description("List variables").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1223
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
1224
|
-
const data = await api.getDisplay(display);
|
|
1225
|
-
const items = getDisplaySubresource(data.variables);
|
|
1226
|
-
if (json) { emitJson(stdout, "displays variables list", items); return; }
|
|
1227
|
-
printTable(items, undefined, stdout);
|
|
1228
|
-
});
|
|
1229
|
-
variables.command("get").description("Get variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id").action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string) {
|
|
1230
|
-
const { api, json } = createDisplaysApi(this, filePath);
|
|
1231
|
-
const data = await api.getDisplay(display);
|
|
1232
|
-
const items = getDisplaySubresource(data.variables);
|
|
1233
|
-
const match = items.find((item) => item && typeof item === "object" && "id" in item && String(item.id) === variableId);
|
|
1234
|
-
if (!match) throw new CliError("invalid_cli_usage", `Variable "${variableId}" not found.`);
|
|
1235
|
-
if (json) { emitJson(stdout, "displays variables get", match); return; }
|
|
1236
|
-
printTable([match], undefined, stdout);
|
|
1237
|
-
});
|
|
1238
|
-
variables.command("create").description("Create variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1239
|
-
.option("--type <type>").option("--name <name>").option("--allow-multiple").option("--static")
|
|
1240
|
-
.option("--default <value>").option("--default-value <value>", "repeatable default value", (v, p: string[] = []) => [...p, v], [])
|
|
1241
|
-
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
1242
|
-
.option("--date-mode <mode>").option("--date-dynamic-option <option>", `dynamic date period (${DATE_DYNAMIC_OPTIONS})`).option("--date-specific <iso>").option("--date-range-start <iso>").option("--date-range-end <iso>")
|
|
1243
|
-
.option("--raw <json>").option("--raw-file <path>")
|
|
1244
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: VariableMutationOptions) {
|
|
1245
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays variables create", readOnly, "write");
|
|
1246
|
-
const data = await api.createVariable(display, buildVariableBody(commandOptions));
|
|
1247
|
-
if (json) { emitJson(stdout, "displays variables create", data); return; }
|
|
1248
|
-
writeLine(stdout, `Created variable on display "${display.value}".`);
|
|
1249
|
-
});
|
|
1250
|
-
variables.command("update").description("Update variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id")
|
|
1251
|
-
.option("--type <type>").option("--name <name>").option("--allow-multiple").option("--static")
|
|
1252
|
-
.option("--default <value>").option("--default-value <value>", "repeatable default value", (v, p: string[] = []) => [...p, v], [])
|
|
1253
|
-
.option("--source-schema <id>").option("--source-field <id>").option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
1254
|
-
.option("--date-mode <mode>").option("--date-dynamic-option <option>", `dynamic date period (${DATE_DYNAMIC_OPTIONS})`).option("--date-specific <iso>").option("--date-range-start <iso>").option("--date-range-end <iso>")
|
|
1255
|
-
.option("--raw <json>").option("--raw-file <path>")
|
|
1256
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string, commandOptions: VariableMutationOptions) {
|
|
1257
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays variables update", readOnly, "write");
|
|
1258
|
-
const data = await api.updateVariable(display, variableId, buildVariableBody(commandOptions));
|
|
1259
|
-
if (json) { emitJson(stdout, "displays variables update", data); return; }
|
|
1260
|
-
writeLine(stdout, `Updated variable "${variableId}".`);
|
|
1261
|
-
});
|
|
1262
|
-
variables.command("delete").description("Delete variable").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<variable>", "variable id").action(async function action(this: Command, display: NormalizedUuidOrSlug, variableId: string) {
|
|
1263
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays variables delete", readOnly, "destructive");
|
|
1264
|
-
const data = await api.deleteVariable(display, variableId);
|
|
1265
|
-
if (json) { emitJson(stdout, "displays variables delete", data); return; }
|
|
1266
|
-
writeLine(stdout, `Deleted variable "${variableId}".`);
|
|
1267
|
-
});
|
|
1268
|
-
|
|
1269
|
-
const widgets = displays.command("widgets").description("Manage display widgets");
|
|
1270
|
-
widgets.command("list").description("List widgets").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1271
|
-
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const items = getDisplaySubresource(data.widgets);
|
|
1272
|
-
if (json) { emitJson(stdout, "displays widgets list", items); return; } printTable(items, undefined, stdout);
|
|
1273
|
-
});
|
|
1274
|
-
widgets.command("get").description("Get widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<widget>", "widget id").action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string) {
|
|
1275
|
-
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getWidget(display, widgetId);
|
|
1276
|
-
if (json) { emitJson(stdout, "displays widgets get", data); return; } printTable([data], undefined, stdout);
|
|
1277
|
-
});
|
|
1278
|
-
widgets.command("create").description("Create widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1279
|
-
.requiredOption("--type <type>", "widget type")
|
|
1280
|
-
.option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>")
|
|
1281
|
-
.option("--origin-resource <uuid>").option("--origin-subschema")
|
|
1282
|
-
.option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>")
|
|
1283
|
-
.option("--field <field>", "repeatable field", (v, p: string[] = []) => [...p, v], [])
|
|
1284
|
-
.option("--sort <sort>", "repeatable sort", (v, p: string[] = []) => [...p, v], [])
|
|
1285
|
-
.option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
1286
|
-
.option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button")
|
|
1287
|
-
.option("--lock-field <field>", "lock a field permanently in this widget (repeatable; field UUID or slug)", (v, p: string[] = []) => [...p, v], [])
|
|
1288
|
-
.option("--lock-condition <expr>", 'conditional lock "<lockedField>:<field>:<comparator>[:<value>]"; locks <lockedField> when all its conditions match (AND, repeatable)', collectLockConditions, [])
|
|
1289
|
-
.option("--lock-message-before <field:text>", "message shown while a conditional lock is not met yet (repeatable)", collectLockMessages, [])
|
|
1290
|
-
.option("--lock-message-after <field:text>", "message shown once the field is locked (repeatable)", collectLockMessages, [])
|
|
1291
|
-
.option("--clear-locked-fields", "clear the widget locked-fields configuration")
|
|
1292
|
-
.option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>")
|
|
1293
|
-
.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")
|
|
1294
|
-
.option("--y-axis-serie <json>", "repeatable y axis serie", (v, p: string[] = []) => [...p, v], [])
|
|
1295
|
-
.option("--legend <json>")
|
|
1296
|
-
.option("--column-field <uuid>", "KANBAN column field (UUID, not slug)")
|
|
1297
|
-
.option("--swimlane-field <uuid>", "KANBAN swimlane field (UUID, not slug)")
|
|
1298
|
-
.option("--start-date-field <uuid>", "TIMELINE start date field (UUID, not slug)")
|
|
1299
|
-
.option("--end-date-field <uuid>", "TIMELINE end date field (UUID, not slug)")
|
|
1300
|
-
.option("--label-field <uuid>", "TIMELINE label field (UUID, not slug)")
|
|
1301
|
-
.option("--group-field <uuid>", "TIMELINE group field (UUID, not slug)")
|
|
1302
|
-
.option("--subgroup-field <uuid>", "TIMELINE subgroup field (UUID, not slug)")
|
|
1303
|
-
.option("--progress-field <uuid>", "TIMELINE progress field (UUID, not slug)")
|
|
1304
|
-
.option("--card-title-field <uuid>", "KANBAN/TIMELINE card title field (UUID, not slug)")
|
|
1305
|
-
.option("--card-category1-field <uuid>", "KANBAN/TIMELINE card category 1 field (UUID, not slug)")
|
|
1306
|
-
.option("--card-category2-field <uuid>", "KANBAN/TIMELINE card category 2 field (UUID, not slug)")
|
|
1307
|
-
.option("--card-category3-field <uuid>", "KANBAN card category 3 field (UUID, not slug)")
|
|
1308
|
-
.option("--card-category4-field <uuid>", "TIMELINE card category 4 field (UUID, not slug)")
|
|
1309
|
-
.option("--card-description-field <uuid>", "KANBAN/TIMELINE card description field (UUID, not slug)")
|
|
1310
|
-
.option("--aggregation-method <method>", "KANBAN card aggregation method (e.g. sum)")
|
|
1311
|
-
.option("--aggregation-field <uuid>", "KANBAN card aggregation field (UUID, not slug)")
|
|
1312
|
-
.option("--view-state <json>", "KANBAN/TIMELINE viewState UI scalars (columnOrder, granularity, sidebarWidth, show* ...)")
|
|
1313
|
-
.option("--view-state-file <path>", "read --view-state JSON from a file")
|
|
1314
|
-
.option("--hierarchy-label <name>", "HIERARCHY label column name")
|
|
1315
|
-
.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: string[] = []) => [...p, v], [])
|
|
1316
|
-
.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: string[] = []) => [...p, v], [])
|
|
1317
|
-
.option("--hierarchy-root-name <name>", "HIERARCHY root level name")
|
|
1318
|
-
.option("--hierarchy-root-label-field <uuid>", "HIERARCHY root level label field (UUID, not slug)")
|
|
1319
|
-
.option("--hierarchy-root-info <columnId:fieldId>", "HIERARCHY root info mapping; columnId must match a --hierarchy-info id (repeatable)", (v, p: string[] = []) => [...p, v], [])
|
|
1320
|
-
.option("--hierarchy-root-number <columnId:fieldId>", "HIERARCHY root number mapping; columnId must match a --hierarchy-number id (repeatable)", (v, p: string[] = []) => [...p, v], [])
|
|
1321
|
-
.option("--hierarchy-child <json>", "HIERARCHY child level JSON {name,schemaId,childFieldId,labelFieldId,infoMappings,numberMappings} (repeatable)", (v, p: string[] = []) => [...p, v], [])
|
|
1322
|
-
.option("--hierarchy-config <json>", "HIERARCHY full hierarchyConfig JSON (escape hatch)")
|
|
1323
|
-
.option("--hierarchy-config-file <path>", "read --hierarchy-config JSON from a file")
|
|
1324
|
-
.option("--raw <json>").option("--raw-file <path>")
|
|
1325
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: WidgetMutationOptions) {
|
|
1326
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays widgets create", readOnly, "write");
|
|
1327
|
-
const data = await api.createWidget(display, buildWidgetBody(commandOptions));
|
|
1328
|
-
if (json) { emitJson(stdout, "displays widgets create", data); return; }
|
|
1329
|
-
writeLine(stdout, `Created widget on display "${display.value}".`);
|
|
1330
|
-
});
|
|
1331
|
-
widgets.command("update").description("Update widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<widget>", "widget id")
|
|
1332
|
-
.option("--type <type>").option("--name <name>").option("--description <text>").option("--icon <icon>").option("--color <color>")
|
|
1333
|
-
.option("--origin-resource <uuid>").option("--origin-subschema")
|
|
1334
|
-
.option("--tab <tabId>").option("--slot <slotId>").option("--layout <json>").option("--layout-file <path>")
|
|
1335
|
-
.option("--field <field>", "repeatable field", (v, p: string[] = []) => [...p, v], [])
|
|
1336
|
-
.option("--sort <sort>", "repeatable sort", (v, p: string[] = []) => [...p, v], [])
|
|
1337
|
-
.option("--filter <expr>", "repeatable filter", collectFilters, [])
|
|
1338
|
-
.option("--widget-read-only").option("--hide-add-button").option("--hide-delete-button").option("--hide-sort-button").option("--hide-filter-button").option("--hide-see-button")
|
|
1339
|
-
.option("--lock-field <field>", "lock a field permanently in this widget (repeatable; field UUID or slug)", (v, p: string[] = []) => [...p, v], [])
|
|
1340
|
-
.option("--lock-condition <expr>", 'conditional lock "<lockedField>:<field>:<comparator>[:<value>]"; locks <lockedField> when all its conditions match (AND, repeatable)', collectLockConditions, [])
|
|
1341
|
-
.option("--lock-message-before <field:text>", "message shown while a conditional lock is not met yet (repeatable)", collectLockMessages, [])
|
|
1342
|
-
.option("--lock-message-after <field:text>", "message shown once the field is locked (repeatable)", collectLockMessages, [])
|
|
1343
|
-
.option("--clear-locked-fields", "clear the widget locked-fields configuration")
|
|
1344
|
-
.option("--calc <type>").option("--calc-field <uuid>").option("--kpi-config <json>")
|
|
1345
|
-
.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")
|
|
1346
|
-
.option("--y-axis-serie <json>", "repeatable y axis serie", (v, p: string[] = []) => [...p, v], [])
|
|
1347
|
-
.option("--legend <json>")
|
|
1348
|
-
.option("--column-field <uuid>", "KANBAN column field (UUID, not slug)")
|
|
1349
|
-
.option("--swimlane-field <uuid>", "KANBAN swimlane field (UUID, not slug)")
|
|
1350
|
-
.option("--start-date-field <uuid>", "TIMELINE start date field (UUID, not slug)")
|
|
1351
|
-
.option("--end-date-field <uuid>", "TIMELINE end date field (UUID, not slug)")
|
|
1352
|
-
.option("--label-field <uuid>", "TIMELINE label field (UUID, not slug)")
|
|
1353
|
-
.option("--group-field <uuid>", "TIMELINE group field (UUID, not slug)")
|
|
1354
|
-
.option("--subgroup-field <uuid>", "TIMELINE subgroup field (UUID, not slug)")
|
|
1355
|
-
.option("--progress-field <uuid>", "TIMELINE progress field (UUID, not slug)")
|
|
1356
|
-
.option("--card-title-field <uuid>", "KANBAN/TIMELINE card title field (UUID, not slug)")
|
|
1357
|
-
.option("--card-category1-field <uuid>", "KANBAN/TIMELINE card category 1 field (UUID, not slug)")
|
|
1358
|
-
.option("--card-category2-field <uuid>", "KANBAN/TIMELINE card category 2 field (UUID, not slug)")
|
|
1359
|
-
.option("--card-category3-field <uuid>", "KANBAN card category 3 field (UUID, not slug)")
|
|
1360
|
-
.option("--card-category4-field <uuid>", "TIMELINE card category 4 field (UUID, not slug)")
|
|
1361
|
-
.option("--card-description-field <uuid>", "KANBAN/TIMELINE card description field (UUID, not slug)")
|
|
1362
|
-
.option("--aggregation-method <method>", "KANBAN card aggregation method (e.g. sum)")
|
|
1363
|
-
.option("--aggregation-field <uuid>", "KANBAN card aggregation field (UUID, not slug)")
|
|
1364
|
-
.option("--view-state <json>", "KANBAN/TIMELINE viewState UI scalars (columnOrder, granularity, sidebarWidth, show* ...)")
|
|
1365
|
-
.option("--view-state-file <path>", "read --view-state JSON from a file")
|
|
1366
|
-
.option("--hierarchy-label <name>", "HIERARCHY label column name")
|
|
1367
|
-
.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: string[] = []) => [...p, v], [])
|
|
1368
|
-
.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: string[] = []) => [...p, v], [])
|
|
1369
|
-
.option("--hierarchy-root-name <name>", "HIERARCHY root level name")
|
|
1370
|
-
.option("--hierarchy-root-label-field <uuid>", "HIERARCHY root level label field (UUID, not slug)")
|
|
1371
|
-
.option("--hierarchy-root-info <columnId:fieldId>", "HIERARCHY root info mapping; columnId must match a --hierarchy-info id (repeatable)", (v, p: string[] = []) => [...p, v], [])
|
|
1372
|
-
.option("--hierarchy-root-number <columnId:fieldId>", "HIERARCHY root number mapping; columnId must match a --hierarchy-number id (repeatable)", (v, p: string[] = []) => [...p, v], [])
|
|
1373
|
-
.option("--hierarchy-child <json>", "HIERARCHY child level JSON {name,schemaId,childFieldId,labelFieldId,infoMappings,numberMappings} (repeatable)", (v, p: string[] = []) => [...p, v], [])
|
|
1374
|
-
.option("--hierarchy-config <json>", "HIERARCHY full hierarchyConfig JSON (escape hatch)")
|
|
1375
|
-
.option("--hierarchy-config-file <path>", "read --hierarchy-config JSON from a file")
|
|
1376
|
-
.option("--raw <json>").option("--raw-file <path>")
|
|
1377
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string, commandOptions: WidgetMutationOptions) {
|
|
1378
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays widgets update", readOnly, "write");
|
|
1379
|
-
const data = await api.updateWidget(display, widgetId, buildWidgetBody(commandOptions));
|
|
1380
|
-
if (json) { emitJson(stdout, "displays widgets update", data); return; }
|
|
1381
|
-
writeLine(stdout, `Updated widget "${widgetId}".`);
|
|
1382
|
-
});
|
|
1383
|
-
widgets.command("delete").description("Delete widget").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<widget>", "widget id").action(async function action(this: Command, display: NormalizedUuidOrSlug, widgetId: string) {
|
|
1384
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays widgets delete", readOnly, "destructive");
|
|
1385
|
-
const data = await api.deleteWidget(display, widgetId);
|
|
1386
|
-
if (json) { emitJson(stdout, "displays widgets delete", data); return; }
|
|
1387
|
-
writeLine(stdout, `Deleted widget "${widgetId}".`);
|
|
1388
|
-
});
|
|
1389
|
-
|
|
1390
|
-
const targets = displays.command("navigation-targets").description("Manage navigation targets");
|
|
1391
|
-
targets.command("list").description("List navigation targets").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1392
|
-
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const items = getDisplaySubresource(data.navigationTargets);
|
|
1393
|
-
if (json) { emitJson(stdout, "displays navigation-targets list", items); return; } printTable(items, undefined, stdout);
|
|
1394
|
-
});
|
|
1395
|
-
targets.command("create").description("Create navigation target").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1396
|
-
.requiredOption("--source-widget <id>").requiredOption("--target-display <id>").requiredOption("--target-variable <id>").option("--label <label>")
|
|
1397
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: NavigationTargetMutationOptions) {
|
|
1398
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays navigation-targets create", readOnly, "write");
|
|
1399
|
-
const data = await api.createNavigationTarget(display, buildNavigationTargetBody(commandOptions));
|
|
1400
|
-
if (json) { emitJson(stdout, "displays navigation-targets create", data); return; }
|
|
1401
|
-
writeLine(stdout, `Created navigation target on display "${display.value}".`);
|
|
1402
|
-
});
|
|
1403
|
-
targets.command("update").description("Update navigation target").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<target>", "target id")
|
|
1404
|
-
.option("--source-widget <id>").option("--target-display <id>").option("--target-variable <id>").option("--label <label>")
|
|
1405
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, targetId: string, commandOptions: NavigationTargetMutationOptions) {
|
|
1406
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays navigation-targets update", readOnly, "write");
|
|
1407
|
-
const data = await api.updateNavigationTarget(display, targetId, buildNavigationTargetBody(commandOptions));
|
|
1408
|
-
if (json) { emitJson(stdout, "displays navigation-targets update", data); return; }
|
|
1409
|
-
writeLine(stdout, `Updated navigation target "${targetId}".`);
|
|
1410
|
-
});
|
|
1411
|
-
targets.command("delete").description("Delete navigation target").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).argument("<target>", "target id").action(async function action(this: Command, display: NormalizedUuidOrSlug, targetId: string) {
|
|
1412
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays navigation-targets delete", readOnly, "destructive");
|
|
1413
|
-
const data = await api.deleteNavigationTarget(display, targetId);
|
|
1414
|
-
if (json) { emitJson(stdout, "displays navigation-targets delete", data); return; }
|
|
1415
|
-
writeLine(stdout, `Deleted navigation target "${targetId}".`);
|
|
1416
|
-
});
|
|
1417
|
-
|
|
1418
|
-
const cross = displays.command("cross-widget-filter").description("Manage cross widget filter");
|
|
1419
|
-
cross.command("get").description("Get cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1420
|
-
const { api, json } = createDisplaysApi(this, filePath); const data = await api.getDisplay(display); const item = data.crossWidgetFilter ?? null;
|
|
1421
|
-
if (json) { emitJson(stdout, "displays cross-widget-filter get", item); return; } printTable(item ?? [], undefined, stdout);
|
|
1422
|
-
});
|
|
1423
|
-
cross.command("set").description("Set cross widget filter (replaces the whole array of configurations)").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector)
|
|
1424
|
-
.option("--source-widget <id>", "source widget id (single-configuration mode)")
|
|
1425
|
-
.option("--source-field <id>", "source field id (single-configuration mode)")
|
|
1426
|
-
.option("--source-schema <id>", "source schema id, only for HIERARCHY source widgets")
|
|
1427
|
-
.option("--target <widget:field>", "repeatable target (single-configuration mode)", (v, p: string[] = []) => [...p, v], [])
|
|
1428
|
-
.option("--raw <json>", "full array of configurations as JSON (for several configurations)")
|
|
1429
|
-
.option("--raw-file <path>", "path to a JSON file with the full array of configurations")
|
|
1430
|
-
.action(async function action(this: Command, display: NormalizedUuidOrSlug, commandOptions: CrossWidgetFilterSetOptions) {
|
|
1431
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays cross-widget-filter set", readOnly, "write");
|
|
1432
|
-
const data = await api.setCrossWidgetFilter(display, buildCrossWidgetFilterBody(commandOptions));
|
|
1433
|
-
if (json) { emitJson(stdout, "displays cross-widget-filter set", data); return; }
|
|
1434
|
-
writeLine(stdout, `Updated cross widget filter for display "${display.value}".`);
|
|
1435
|
-
});
|
|
1436
|
-
cross.command("delete").description("Delete cross widget filter").argument("<display>", "display uuid or slug", parseUuidOrSlugSelector).action(async function action(this: Command, display: NormalizedUuidOrSlug) {
|
|
1437
|
-
const { api, json, readOnly } = createDisplaysApi(this, filePath); ensureWritable("displays cross-widget-filter delete", readOnly, "destructive");
|
|
1438
|
-
const data = await api.deleteCrossWidgetFilter(display);
|
|
1439
|
-
if (json) { emitJson(stdout, "displays cross-widget-filter delete", data); return; }
|
|
1440
|
-
writeLine(stdout, `Deleted cross widget filter for display "${display.value}".`);
|
|
1441
|
-
});
|
|
1442
|
-
}
|