dataiku-sdk 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/types/src/index.d.ts +15 -2
- package/dist/packages/types/src/index.js +9 -1
- package/dist/src/cli/coerce.d.ts +36 -0
- package/dist/src/cli/coerce.js +239 -0
- package/dist/src/cli/commands/api-deployer.d.ts +2 -0
- package/dist/src/cli/commands/api-deployer.js +170 -0
- package/dist/src/cli/commands/api-service.d.ts +2 -0
- package/dist/src/cli/commands/api-service.js +102 -0
- package/dist/src/cli/commands/app.d.ts +2 -0
- package/dist/src/cli/commands/app.js +78 -0
- package/dist/src/cli/commands/bundle.d.ts +2 -0
- package/dist/src/cli/commands/bundle.js +107 -0
- package/dist/src/cli/commands/business-app.d.ts +2 -0
- package/dist/src/cli/commands/business-app.js +76 -0
- package/dist/src/cli/commands/code-env.d.ts +2 -0
- package/dist/src/cli/commands/code-env.js +255 -0
- package/dist/src/cli/commands/code.d.ts +2 -0
- package/dist/src/cli/commands/code.js +63 -0
- package/dist/src/cli/commands/connection.d.ts +2 -0
- package/dist/src/cli/commands/connection.js +54 -0
- package/dist/src/cli/commands/continuous-activity.d.ts +2 -0
- package/dist/src/cli/commands/continuous-activity.js +38 -0
- package/dist/src/cli/commands/dashboard.d.ts +2 -0
- package/dist/src/cli/commands/dashboard.js +99 -0
- package/dist/src/cli/commands/data-quality.d.ts +2 -0
- package/dist/src/cli/commands/data-quality.js +235 -0
- package/dist/src/cli/commands/dataset.d.ts +2 -0
- package/dist/src/cli/commands/dataset.js +288 -0
- package/dist/src/cli/commands/discussion.d.ts +2 -0
- package/dist/src/cli/commands/discussion.js +47 -0
- package/dist/src/cli/commands/doctor.d.ts +2 -0
- package/dist/src/cli/commands/doctor.js +9 -0
- package/dist/src/cli/commands/flow-zone.d.ts +2 -0
- package/dist/src/cli/commands/flow-zone.js +282 -0
- package/dist/src/cli/commands/folder.d.ts +2 -0
- package/dist/src/cli/commands/folder.js +202 -0
- package/dist/src/cli/commands/future.d.ts +2 -0
- package/dist/src/cli/commands/future.js +57 -0
- package/dist/src/cli/commands/index.d.ts +2 -0
- package/dist/src/cli/commands/index.js +70 -0
- package/dist/src/cli/commands/insight.d.ts +2 -0
- package/dist/src/cli/commands/insight.js +156 -0
- package/dist/src/cli/commands/job.d.ts +2 -0
- package/dist/src/cli/commands/job.js +406 -0
- package/dist/src/cli/commands/meaning.d.ts +2 -0
- package/dist/src/cli/commands/meaning.js +115 -0
- package/dist/src/cli/commands/metrics.d.ts +2 -0
- package/dist/src/cli/commands/metrics.js +39 -0
- package/dist/src/cli/commands/notebook.d.ts +2 -0
- package/dist/src/cli/commands/notebook.js +242 -0
- package/dist/src/cli/commands/project-deployer.d.ts +2 -0
- package/dist/src/cli/commands/project-deployer.js +118 -0
- package/dist/src/cli/commands/project-library.d.ts +2 -0
- package/dist/src/cli/commands/project-library.js +197 -0
- package/dist/src/cli/commands/project.d.ts +2 -0
- package/dist/src/cli/commands/project.js +132 -0
- package/dist/src/cli/commands/recipe.d.ts +2 -0
- package/dist/src/cli/commands/recipe.js +632 -0
- package/dist/src/cli/commands/scenario.d.ts +2 -0
- package/dist/src/cli/commands/scenario.js +190 -0
- package/dist/src/cli/commands/sql.d.ts +2 -0
- package/dist/src/cli/commands/sql.js +109 -0
- package/dist/src/cli/commands/statistics.d.ts +2 -0
- package/dist/src/cli/commands/statistics.js +165 -0
- package/dist/src/cli/commands/streaming-endpoint.d.ts +2 -0
- package/dist/src/cli/commands/streaming-endpoint.js +50 -0
- package/dist/src/cli/commands/variable.d.ts +2 -0
- package/dist/src/cli/commands/variable.js +49 -0
- package/dist/src/cli/commands/webapp.d.ts +2 -0
- package/dist/src/cli/commands/webapp.js +67 -0
- package/dist/src/cli/commands/wiki.d.ts +2 -0
- package/dist/src/cli/commands/wiki.js +123 -0
- package/dist/src/cli/commands/workspace.d.ts +2 -0
- package/dist/src/cli/commands/workspace.js +68 -0
- package/dist/src/cli/contract.d.ts +104 -0
- package/dist/src/cli/contract.js +2031 -0
- package/dist/src/cli/doctor.d.ts +104 -0
- package/dist/src/cli/doctor.js +356 -0
- package/dist/src/cli/env.d.ts +2 -0
- package/dist/src/cli/env.js +37 -0
- package/dist/src/cli/flags.d.ts +16 -0
- package/dist/src/cli/flags.js +255 -0
- package/dist/src/cli/helpers/cleanup.d.ts +3 -0
- package/dist/src/cli/helpers/cleanup.js +158 -0
- package/dist/src/cli/helpers/dataset.d.ts +2 -0
- package/dist/src/cli/helpers/dataset.js +17 -0
- package/dist/src/cli/helpers/flow-zone.d.ts +69 -0
- package/dist/src/cli/helpers/flow-zone.js +420 -0
- package/dist/src/cli/helpers/recipe.d.ts +11 -0
- package/dist/src/cli/helpers/recipe.js +83 -0
- package/dist/src/cli/output.d.ts +38 -0
- package/dist/src/cli/output.js +159 -0
- package/dist/src/cli/runtime.d.ts +15 -0
- package/dist/src/cli/runtime.js +59 -0
- package/dist/src/cli/types.d.ts +25 -0
- package/dist/src/cli/types.js +1 -0
- package/dist/src/cli/usage.d.ts +16 -0
- package/dist/src/cli/usage.js +80 -0
- package/dist/src/cli/version.d.ts +14 -0
- package/dist/src/cli/version.js +63 -0
- package/dist/src/cli.js +671 -8254
- package/dist/src/client.d.ts +1 -1
- package/dist/src/client.js +14 -13
- package/dist/src/errors.d.ts +6 -0
- package/dist/src/errors.js +127 -4
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.js +2 -2
- package/dist/src/resources/applications.d.ts +1 -1
- package/dist/src/resources/applications.js +27 -1
- package/dist/src/resources/bundles.js +2 -2
- package/dist/src/resources/continuous-activities.js +1 -0
- package/dist/src/resources/data-quality.js +5 -3
- package/dist/src/resources/datasets.d.ts +3 -0
- package/dist/src/resources/datasets.js +3 -0
- package/dist/src/resources/flow-zones.d.ts +2 -2
- package/dist/src/resources/flow-zones.js +4 -1
- package/dist/src/resources/meanings.d.ts +2 -0
- package/dist/src/resources/meanings.js +4 -0
- package/dist/src/resources/notebooks.d.ts +5 -7
- package/dist/src/resources/notebooks.js +10 -7
- package/dist/src/resources/projects.d.ts +1 -1
- package/dist/src/resources/projects.js +2 -2
- package/dist/src/resources/recipes.d.ts +3 -3
- package/dist/src/resources/recipes.js +50 -7
- package/dist/src/resources/scenarios.js +67 -26
- package/dist/src/resources/statistics.js +9 -0
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.js +2 -2
- package/dist/src/utils/sanitize.js +2 -1
- package/package.json +1 -1
- package/packages/types/dist/index.d.ts +15 -2
- package/packages/types/dist/index.js +9 -1
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { readFileSync, } from "node:fs";
|
|
2
|
+
import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, plainRecord, requiredStringArray, splitCsvFlag, } from "../coerce.js";
|
|
3
|
+
import { UsageError, } from "../usage.js";
|
|
4
|
+
export function flowZoneId(value) {
|
|
5
|
+
const trimmed = value.trim();
|
|
6
|
+
if (!trimmed)
|
|
7
|
+
throw new UsageError("Flow zone id must not be empty.");
|
|
8
|
+
return trimmed;
|
|
9
|
+
}
|
|
10
|
+
export function flowZoneName(value) {
|
|
11
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
12
|
+
throw new UsageError("--name is required. Usage: dss flow-zone create --name NAME [--color #RRGGBB]");
|
|
13
|
+
}
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
|
16
|
+
export function flowZoneColor(value) {
|
|
17
|
+
if (value === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (typeof value !== "string" || !/^#[0-9a-fA-F]{6}$/.test(value.trim())) {
|
|
20
|
+
throw new UsageError("--color must be a hex color like #2ab1ac.");
|
|
21
|
+
}
|
|
22
|
+
return value.trim();
|
|
23
|
+
}
|
|
24
|
+
export function flowZoneObjectType(value) {
|
|
25
|
+
const normalized = value.trim().toUpperCase().replace(/-/g, "_");
|
|
26
|
+
if (normalized === "FOLDER")
|
|
27
|
+
return "MANAGED_FOLDER";
|
|
28
|
+
if (normalized === "MODEL_EVAL_STORE")
|
|
29
|
+
return "MODEL_EVALUATION_STORE";
|
|
30
|
+
if (normalized === "KNOWLEDGE_BANK")
|
|
31
|
+
return "RETRIEVABLE_KNOWLEDGE";
|
|
32
|
+
if (normalized === "DATASET"
|
|
33
|
+
|| normalized === "MANAGED_FOLDER"
|
|
34
|
+
|| normalized === "SAVED_MODEL"
|
|
35
|
+
|| normalized === "RECIPE"
|
|
36
|
+
|| normalized === "MODEL_EVALUATION_STORE"
|
|
37
|
+
|| normalized === "STREAMING_ENDPOINT"
|
|
38
|
+
|| normalized === "LABELING_TASK"
|
|
39
|
+
|| normalized === "RETRIEVABLE_KNOWLEDGE") {
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
throw new UsageError(`Invalid flow zone object type: ${value}. Use DATASET, RECIPE, MANAGED_FOLDER, SAVED_MODEL, MODEL_EVALUATION_STORE, STREAMING_ENDPOINT, LABELING_TASK, or RETRIEVABLE_KNOWLEDGE.`, "invalid_enum");
|
|
43
|
+
}
|
|
44
|
+
export function addFlowZoneFlagItems(items, flags, flagName, objectType) {
|
|
45
|
+
for (const objectId of splitCsvFlag(flags[flagName])) {
|
|
46
|
+
items.push({ objectId, objectType, });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function parseFlowZoneObject(value) {
|
|
50
|
+
const parts = value.split(":").map((part) => part.trim()).filter((part) => part.length > 0);
|
|
51
|
+
if (parts.length === 2) {
|
|
52
|
+
return { objectType: flowZoneObjectType(parts[0]), objectId: parts[1], };
|
|
53
|
+
}
|
|
54
|
+
if (parts.length === 3) {
|
|
55
|
+
return { projectKey: parts[0], objectType: flowZoneObjectType(parts[1]), objectId: parts[2], };
|
|
56
|
+
}
|
|
57
|
+
throw new UsageError(`Invalid --object value: ${value}. Use TYPE:ID or PROJECT_KEY:TYPE:ID.`);
|
|
58
|
+
}
|
|
59
|
+
export function flowZoneMoveItems(flags) {
|
|
60
|
+
const items = [];
|
|
61
|
+
addFlowZoneFlagItems(items, flags, "dataset", "DATASET");
|
|
62
|
+
addFlowZoneFlagItems(items, flags, "recipe", "RECIPE");
|
|
63
|
+
addFlowZoneFlagItems(items, flags, "folder", "MANAGED_FOLDER");
|
|
64
|
+
addFlowZoneFlagItems(items, flags, "saved-model", "SAVED_MODEL");
|
|
65
|
+
addFlowZoneFlagItems(items, flags, "model-evaluation-store", "MODEL_EVALUATION_STORE");
|
|
66
|
+
addFlowZoneFlagItems(items, flags, "streaming-endpoint", "STREAMING_ENDPOINT");
|
|
67
|
+
addFlowZoneFlagItems(items, flags, "labeling-task", "LABELING_TASK");
|
|
68
|
+
addFlowZoneFlagItems(items, flags, "knowledge-bank", "RETRIEVABLE_KNOWLEDGE");
|
|
69
|
+
for (const value of splitCsvFlag(flags["object"])) {
|
|
70
|
+
items.push(parseFlowZoneObject(value));
|
|
71
|
+
}
|
|
72
|
+
return items;
|
|
73
|
+
}
|
|
74
|
+
export function flowZoneItems(zone) {
|
|
75
|
+
return [...(zone.items ?? []), ...(zone.shared ?? []),];
|
|
76
|
+
}
|
|
77
|
+
export function flowZoneContains(zone, object) {
|
|
78
|
+
return flowZoneItems(zone).some((item) => item.objectId === object.objectId
|
|
79
|
+
&& item.objectType === object.objectType
|
|
80
|
+
&& (object.projectKey === undefined || item.projectKey === object.projectKey));
|
|
81
|
+
}
|
|
82
|
+
export function flowZoneSummary(zone, object) {
|
|
83
|
+
const items = flowZoneItems(zone);
|
|
84
|
+
return {
|
|
85
|
+
id: zone.id,
|
|
86
|
+
name: zone.name,
|
|
87
|
+
itemCount: items.length,
|
|
88
|
+
...(object ? { containsMatchingObject: flowZoneContains(zone, object), } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function flowZoneDetailSummary(zone) {
|
|
92
|
+
return {
|
|
93
|
+
...flowZoneSummary(zone),
|
|
94
|
+
items: flowZoneItems(zone),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function flowZonePlanColor(value, source) {
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (typeof value !== "string" || !/^#[0-9a-fA-F]{6}$/.test(value.trim())) {
|
|
101
|
+
throw new UsageError(`${source} must be a hex color like #2ab1ac.`, "validation_failed");
|
|
102
|
+
}
|
|
103
|
+
return value.trim();
|
|
104
|
+
}
|
|
105
|
+
export function flowZonePlanPosition(value, source) {
|
|
106
|
+
if (value === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
const record = plainRecord(value);
|
|
109
|
+
if (!record) {
|
|
110
|
+
throw new UsageError(`${source} must be an object with x and y.`, "validation_failed");
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
x: finiteNumberField(record, "x", source),
|
|
114
|
+
y: finiteNumberField(record, "y", source),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function flowZoneCurrentPosition(zone) {
|
|
118
|
+
const record = zone;
|
|
119
|
+
const position = plainRecord(record.position);
|
|
120
|
+
if (!position)
|
|
121
|
+
return undefined;
|
|
122
|
+
const x = position.x;
|
|
123
|
+
const y = position.y;
|
|
124
|
+
return typeof x === "number" && Number.isFinite(x) && typeof y === "number" && Number.isFinite(y)
|
|
125
|
+
? { x, y, }
|
|
126
|
+
: undefined;
|
|
127
|
+
}
|
|
128
|
+
export function flowZoneSamePosition(a, b) {
|
|
129
|
+
if (a === undefined || b === undefined)
|
|
130
|
+
return a === b;
|
|
131
|
+
return a.x === b.x && a.y === b.y;
|
|
132
|
+
}
|
|
133
|
+
export function parseFlowZonePlanItem(value, source) {
|
|
134
|
+
if (typeof value === "string")
|
|
135
|
+
return parseFlowZoneObject(value);
|
|
136
|
+
const record = plainRecord(value);
|
|
137
|
+
if (!record) {
|
|
138
|
+
throw new UsageError(`${source} must be TYPE:ID or an object.`, "validation_failed");
|
|
139
|
+
}
|
|
140
|
+
const object = optionalStringField(record, ["object",]);
|
|
141
|
+
if (object)
|
|
142
|
+
return parseFlowZoneObject(object);
|
|
143
|
+
const objectType = optionalStringField(record, ["objectType", "type",]);
|
|
144
|
+
const objectId = optionalStringField(record, ["objectId", "id", "name",]);
|
|
145
|
+
if (!objectType || !objectId) {
|
|
146
|
+
throw new UsageError(`${source} must include objectType/type and objectId/id, or object as TYPE:ID.`, "validation_failed");
|
|
147
|
+
}
|
|
148
|
+
const projectKey = optionalStringField(record, ["projectKey", "project",]);
|
|
149
|
+
return {
|
|
150
|
+
objectType: flowZoneObjectType(objectType),
|
|
151
|
+
objectId,
|
|
152
|
+
...(projectKey ? { projectKey, } : {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function addFlowZonePlanTypedItems(items, record, key, objectType, source) {
|
|
156
|
+
if (record[key] === undefined)
|
|
157
|
+
return;
|
|
158
|
+
for (const objectId of requiredStringArray(record[key], `${source}.${key}`)) {
|
|
159
|
+
items.push({ objectType, objectId, });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function flowZoneItemKey(item) {
|
|
163
|
+
return `${item.projectKey ?? ""}\0${item.objectType}\0${item.objectId}`;
|
|
164
|
+
}
|
|
165
|
+
export function flowZonePlanLabel(plan) {
|
|
166
|
+
return plan.id ?? plan.name ?? "<unknown>";
|
|
167
|
+
}
|
|
168
|
+
export function dedupeFlowZonePlanItems(items) {
|
|
169
|
+
const seen = new Set();
|
|
170
|
+
const result = [];
|
|
171
|
+
for (const item of items) {
|
|
172
|
+
const key = flowZoneItemKey(item);
|
|
173
|
+
if (seen.has(key))
|
|
174
|
+
continue;
|
|
175
|
+
seen.add(key);
|
|
176
|
+
result.push(item);
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
export function flowZonePlanItemKeys(plan) {
|
|
181
|
+
const keys = new Set();
|
|
182
|
+
for (const zone of plan.zones) {
|
|
183
|
+
for (const item of zone.items)
|
|
184
|
+
keys.add(flowZoneItemKey(item));
|
|
185
|
+
}
|
|
186
|
+
return keys;
|
|
187
|
+
}
|
|
188
|
+
export function validateUniqueFlowZoneAssignments(plan) {
|
|
189
|
+
const seen = new Map();
|
|
190
|
+
for (const zone of plan.zones) {
|
|
191
|
+
const label = flowZonePlanLabel(zone);
|
|
192
|
+
for (const item of zone.items) {
|
|
193
|
+
const key = flowZoneItemKey(item);
|
|
194
|
+
const previous = seen.get(key);
|
|
195
|
+
if (previous) {
|
|
196
|
+
throw new UsageError(`Flow object ${item.objectType}:${item.objectId} is assigned to both "${previous}" and "${label}".`, "validation_failed");
|
|
197
|
+
}
|
|
198
|
+
seen.set(key, label);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
export function parseFlowZoneOrganizePlan(input) {
|
|
203
|
+
const zones = input.zones;
|
|
204
|
+
if (!Array.isArray(zones) || zones.length === 0) {
|
|
205
|
+
throw new UsageError("Flow zone organize plan must include a non-empty zones array.", "validation_failed");
|
|
206
|
+
}
|
|
207
|
+
const plan = {
|
|
208
|
+
zones: zones.map((value, index) => {
|
|
209
|
+
const source = `zones[${index}]`;
|
|
210
|
+
const record = plainRecord(value);
|
|
211
|
+
if (!record)
|
|
212
|
+
throw new UsageError(`${source} must be an object.`, "validation_failed");
|
|
213
|
+
const id = optionalStringField(record, ["id", "zoneId",]);
|
|
214
|
+
const name = optionalStringField(record, ["name",]);
|
|
215
|
+
if (!id && !name) {
|
|
216
|
+
throw new UsageError(`${source} must include name or id.`, "validation_failed");
|
|
217
|
+
}
|
|
218
|
+
const items = [];
|
|
219
|
+
const rawItems = record.items ?? record.objects;
|
|
220
|
+
if (rawItems !== undefined) {
|
|
221
|
+
if (!Array.isArray(rawItems)) {
|
|
222
|
+
throw new UsageError(`${source}.items must be an array.`, "validation_failed");
|
|
223
|
+
}
|
|
224
|
+
rawItems.forEach((item, itemIndex) => {
|
|
225
|
+
items.push(parseFlowZonePlanItem(item, `${source}.items[${itemIndex}]`));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
addFlowZonePlanTypedItems(items, record, "datasets", "DATASET", source);
|
|
229
|
+
addFlowZonePlanTypedItems(items, record, "recipes", "RECIPE", source);
|
|
230
|
+
addFlowZonePlanTypedItems(items, record, "folders", "MANAGED_FOLDER", source);
|
|
231
|
+
addFlowZonePlanTypedItems(items, record, "savedModels", "SAVED_MODEL", source);
|
|
232
|
+
addFlowZonePlanTypedItems(items, record, "modelEvaluationStores", "MODEL_EVALUATION_STORE", source);
|
|
233
|
+
addFlowZonePlanTypedItems(items, record, "streamingEndpoints", "STREAMING_ENDPOINT", source);
|
|
234
|
+
addFlowZonePlanTypedItems(items, record, "labelingTasks", "LABELING_TASK", source);
|
|
235
|
+
addFlowZonePlanTypedItems(items, record, "knowledgeBanks", "RETRIEVABLE_KNOWLEDGE", source);
|
|
236
|
+
return {
|
|
237
|
+
...(id ? { id, } : {}),
|
|
238
|
+
...(name ? { name, } : {}),
|
|
239
|
+
...(record.color !== undefined
|
|
240
|
+
? { color: flowZonePlanColor(record.color, `${source}.color`), }
|
|
241
|
+
: {}),
|
|
242
|
+
...(record.position !== undefined
|
|
243
|
+
? { position: flowZonePlanPosition(record.position, `${source}.position`), }
|
|
244
|
+
: {}),
|
|
245
|
+
items: dedupeFlowZonePlanItems(items),
|
|
246
|
+
};
|
|
247
|
+
}),
|
|
248
|
+
};
|
|
249
|
+
validateUniqueFlowZoneAssignments(plan);
|
|
250
|
+
return plan;
|
|
251
|
+
}
|
|
252
|
+
export function readFlowZoneOrganizePlan(flags, usage) {
|
|
253
|
+
const data = typeof flags["file"] === "string"
|
|
254
|
+
? parseJsonObject(readFileSync(flags["file"], "utf-8"), flags["file"])
|
|
255
|
+
: jsonInput(flags);
|
|
256
|
+
if (!data) {
|
|
257
|
+
throw new UsageError(`--data, --data-file, --file, or --stdin is required. Usage: ${usage}`, "missing_required_flag");
|
|
258
|
+
}
|
|
259
|
+
return parseFlowZoneOrganizePlan(data);
|
|
260
|
+
}
|
|
261
|
+
export function findFlowZoneForPlan(zones, plan) {
|
|
262
|
+
if (plan.id) {
|
|
263
|
+
const byId = zones.find((zone) => zone.id === plan.id);
|
|
264
|
+
if (byId)
|
|
265
|
+
return byId;
|
|
266
|
+
}
|
|
267
|
+
if (!plan.name)
|
|
268
|
+
return undefined;
|
|
269
|
+
const byName = zones.filter((zone) => zone.name === plan.name);
|
|
270
|
+
if (byName.length > 1) {
|
|
271
|
+
throw new UsageError(`Multiple flow zones named "${plan.name}" exist; use id.`, "validation_failed");
|
|
272
|
+
}
|
|
273
|
+
return byName[0];
|
|
274
|
+
}
|
|
275
|
+
export function ensureFlowZonePlanTarget(plan, existing) {
|
|
276
|
+
if (existing || plan.name)
|
|
277
|
+
return;
|
|
278
|
+
throw new UsageError(`Flow zone ${plan.id ?? "<unknown>"} was not found and cannot be created without name.`, "validation_failed");
|
|
279
|
+
}
|
|
280
|
+
export function flowZoneExplicitItems(zone) {
|
|
281
|
+
return (zone.items ?? []).map((item) => ({
|
|
282
|
+
objectId: item.objectId,
|
|
283
|
+
objectType: item.objectType,
|
|
284
|
+
...(item.projectKey ? { projectKey: item.projectKey, } : {}),
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
export function flowZonePruneItems(existing, plannedItemKeys) {
|
|
288
|
+
if (!existing)
|
|
289
|
+
return [];
|
|
290
|
+
return flowZoneExplicitItems(existing).filter((item) => !plannedItemKeys.has(flowZoneItemKey(item)));
|
|
291
|
+
}
|
|
292
|
+
export function flowZoneOrganizeStep(plan, existing, sync, plannedItemKeys) {
|
|
293
|
+
ensureFlowZonePlanTarget(plan, existing);
|
|
294
|
+
const update = {};
|
|
295
|
+
if (existing && plan.name && plan.name !== existing.name)
|
|
296
|
+
update.name = plan.name;
|
|
297
|
+
if (existing && plan.color && plan.color !== existing.color)
|
|
298
|
+
update.color = plan.color;
|
|
299
|
+
if (existing && plan.position !== undefined
|
|
300
|
+
&& !flowZoneSamePosition(flowZoneCurrentPosition(existing), plan.position)) {
|
|
301
|
+
update.position = plan.position;
|
|
302
|
+
}
|
|
303
|
+
const pruneItems = sync ? flowZonePruneItems(existing, plannedItemKeys) : [];
|
|
304
|
+
return {
|
|
305
|
+
target: {
|
|
306
|
+
...(plan.id ? { id: plan.id, } : {}),
|
|
307
|
+
...(plan.name ? { name: plan.name, } : {}),
|
|
308
|
+
...(plan.color ? { color: plan.color, } : {}),
|
|
309
|
+
...(plan.position ? { position: plan.position, } : {}),
|
|
310
|
+
},
|
|
311
|
+
...(existing ? { existing: flowZoneSummary(existing), } : { create: true, }),
|
|
312
|
+
...(Object.keys(update).length > 0 ? { update, } : {}),
|
|
313
|
+
moveItems: plan.items,
|
|
314
|
+
...(pruneItems.length > 0 ? { pruneItems, } : {}),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
export function flowZoneValidationBucket(index, objectType) {
|
|
318
|
+
switch (objectType) {
|
|
319
|
+
case "DATASET":
|
|
320
|
+
return index.datasets;
|
|
321
|
+
case "RECIPE":
|
|
322
|
+
return index.recipes;
|
|
323
|
+
case "MANAGED_FOLDER":
|
|
324
|
+
return index.folders;
|
|
325
|
+
case "SAVED_MODEL":
|
|
326
|
+
case "MODEL_EVALUATION_STORE":
|
|
327
|
+
case "STREAMING_ENDPOINT":
|
|
328
|
+
case "LABELING_TASK":
|
|
329
|
+
case "RETRIEVABLE_KNOWLEDGE":
|
|
330
|
+
return index.all;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
export async function flowZoneValidationIndex(client, projectKey) {
|
|
334
|
+
const result = await client.projects.map({
|
|
335
|
+
projectKey,
|
|
336
|
+
maxNodes: 100_000,
|
|
337
|
+
maxEdges: 100_000,
|
|
338
|
+
});
|
|
339
|
+
const index = {
|
|
340
|
+
projectKey: result.map.projectKey,
|
|
341
|
+
all: new Set(),
|
|
342
|
+
datasets: new Set(),
|
|
343
|
+
recipes: new Set(),
|
|
344
|
+
folders: new Set(),
|
|
345
|
+
};
|
|
346
|
+
for (const node of result.map.nodes) {
|
|
347
|
+
index.all.add(node.id);
|
|
348
|
+
switch (node.kind) {
|
|
349
|
+
case "dataset":
|
|
350
|
+
index.datasets.add(node.id);
|
|
351
|
+
break;
|
|
352
|
+
case "recipe":
|
|
353
|
+
index.recipes.add(node.id);
|
|
354
|
+
break;
|
|
355
|
+
case "folder":
|
|
356
|
+
index.folders.add(node.id);
|
|
357
|
+
break;
|
|
358
|
+
case "other":
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return index;
|
|
363
|
+
}
|
|
364
|
+
export async function validateFlowZoneOrganizeObjects(client, plan, projectKey) {
|
|
365
|
+
const indexes = new Map();
|
|
366
|
+
const missing = [];
|
|
367
|
+
const getIndex = async (itemProjectKey) => {
|
|
368
|
+
const requestedProjectKey = itemProjectKey ?? projectKey;
|
|
369
|
+
const cacheKey = requestedProjectKey ?? "";
|
|
370
|
+
const cached = indexes.get(cacheKey);
|
|
371
|
+
if (cached)
|
|
372
|
+
return cached;
|
|
373
|
+
const index = await flowZoneValidationIndex(client, requestedProjectKey);
|
|
374
|
+
indexes.set(cacheKey, index);
|
|
375
|
+
return index;
|
|
376
|
+
};
|
|
377
|
+
for (const zone of plan.zones) {
|
|
378
|
+
for (const item of zone.items) {
|
|
379
|
+
const index = await getIndex(item.projectKey);
|
|
380
|
+
const bucket = flowZoneValidationBucket(index, item.objectType);
|
|
381
|
+
if (bucket.has(item.objectId))
|
|
382
|
+
continue;
|
|
383
|
+
missing.push({
|
|
384
|
+
zone: flowZonePlanLabel(zone),
|
|
385
|
+
objectId: item.objectId,
|
|
386
|
+
objectType: item.objectType,
|
|
387
|
+
...(item.projectKey ? { projectKey: item.projectKey, } : {}),
|
|
388
|
+
reason: `Object not found in project ${item.projectKey ?? index.projectKey}.`,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return { valid: missing.length === 0, missing, };
|
|
393
|
+
}
|
|
394
|
+
export function throwFlowZoneValidationError(validation) {
|
|
395
|
+
if (validation.valid)
|
|
396
|
+
return;
|
|
397
|
+
const first = validation.missing[0];
|
|
398
|
+
const suffix = validation.missing.length > 1 ? ` and ${validation.missing.length - 1} more` : "";
|
|
399
|
+
throw new UsageError(`Flow zone organize validation failed: ${first?.objectType}:${first?.objectId} in zone "${first?.zone}" was not found${suffix}.`, "validation_failed");
|
|
400
|
+
}
|
|
401
|
+
export async function resolveFlowZoneIdFromFlags(client, flags, projectKey) {
|
|
402
|
+
const zoneId = typeof flags["zone-id"] === "string" ? flags["zone-id"].trim() : "";
|
|
403
|
+
if (zoneId)
|
|
404
|
+
return zoneId;
|
|
405
|
+
const zone = typeof flags["zone"] === "string" ? flags["zone"].trim() : "";
|
|
406
|
+
if (!zone)
|
|
407
|
+
return undefined;
|
|
408
|
+
const zones = await client.flowZones.list(projectKey);
|
|
409
|
+
const match = zones.find((candidate) => candidate.id === zone || candidate.name === zone);
|
|
410
|
+
if (!match)
|
|
411
|
+
throw new UsageError(`Flow zone not found: ${zone}`, "invalid_enum");
|
|
412
|
+
return match.id;
|
|
413
|
+
}
|
|
414
|
+
export async function moveCreatedItemsToZone(client, flags, items, projectKey) {
|
|
415
|
+
const zoneId = await resolveFlowZoneIdFromFlags(client, flags, projectKey);
|
|
416
|
+
if (!zoneId || items.length === 0)
|
|
417
|
+
return {};
|
|
418
|
+
await client.flowZones.moveItems(zoneId, items, projectKey);
|
|
419
|
+
return { zoneId, moved: items, };
|
|
420
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function recipeBackupPath(recipeName: string, backupDir: string): string;
|
|
2
|
+
export declare function recipeCodeEnv(recipe: Record<string, unknown>): unknown;
|
|
3
|
+
export declare function recipeGraph(recipe: Record<string, unknown>): Record<string, unknown>;
|
|
4
|
+
export declare function recipeBackupDocument(recipeName: string, projectKey: string | undefined, current: {
|
|
5
|
+
recipe: Record<string, unknown>;
|
|
6
|
+
payload?: string;
|
|
7
|
+
}): Record<string, unknown>;
|
|
8
|
+
export declare function readRecipeBackup(backupPath: string): Record<string, unknown>;
|
|
9
|
+
export declare function recipeRunShouldWait(flags: Record<string, string | boolean>): boolean;
|
|
10
|
+
export declare function recipeRoleInputItems(recipe: Record<string, unknown>, role: string): unknown[];
|
|
11
|
+
export declare function recipeInputItemRef(item: unknown): string | undefined;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileSync, } from "node:fs";
|
|
2
|
+
import { join, } from "node:path";
|
|
3
|
+
import { sanitizeFileName, } from "../../utils/sanitize.js";
|
|
4
|
+
import { normalizeLineEndings, sha256Hex, stableHash, } from "../coerce.js";
|
|
5
|
+
import { UsageError, } from "../usage.js";
|
|
6
|
+
export function recipeBackupPath(recipeName, backupDir) {
|
|
7
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
8
|
+
return join(backupDir, `${sanitizeFileName(recipeName, "recipe")}-${stamp}.recipe-backup.json`);
|
|
9
|
+
}
|
|
10
|
+
export function recipeCodeEnv(recipe) {
|
|
11
|
+
const params = recipe.params;
|
|
12
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
13
|
+
return undefined;
|
|
14
|
+
return params.envSelection;
|
|
15
|
+
}
|
|
16
|
+
export function recipeGraph(recipe) {
|
|
17
|
+
return {
|
|
18
|
+
inputs: recipe.inputs,
|
|
19
|
+
outputs: recipe.outputs,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function recipeBackupDocument(recipeName, projectKey, current) {
|
|
23
|
+
return {
|
|
24
|
+
resource: "recipe",
|
|
25
|
+
recipeName,
|
|
26
|
+
projectKey,
|
|
27
|
+
createdAt: new Date().toISOString(),
|
|
28
|
+
versionTag: current.recipe.versionTag,
|
|
29
|
+
payloadHash: sha256Hex(current.payload ?? ""),
|
|
30
|
+
graphHash: stableHash(recipeGraph(current.recipe)),
|
|
31
|
+
normalizedPayloadHash: sha256Hex(normalizeLineEndings(current.payload ?? "")),
|
|
32
|
+
codeEnvHash: stableHash(recipeCodeEnv(current.recipe)),
|
|
33
|
+
codeEnv: recipeCodeEnv(current.recipe),
|
|
34
|
+
recipe: current.recipe,
|
|
35
|
+
payload: current.payload ?? "",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function readRecipeBackup(backupPath) {
|
|
39
|
+
const raw = readFileSync(backupPath, "utf-8");
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (parsed && typeof parsed === "object" && parsed.resource === "recipe")
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Backward-compatible payload-only backups are handled below.
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
resource: "recipe",
|
|
50
|
+
recipeName: "unknown",
|
|
51
|
+
payloadHash: sha256Hex(raw),
|
|
52
|
+
payload: raw,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function recipeRunShouldWait(flags) {
|
|
56
|
+
if (flags["wait"] === true && flags["no-wait"] === true) {
|
|
57
|
+
throw new UsageError("--wait and --no-wait are mutually exclusive.", "invalid_enum");
|
|
58
|
+
}
|
|
59
|
+
const waitImplied = flags["include-logs"] === true
|
|
60
|
+
|| flags["summary"] === true
|
|
61
|
+
|| flags["timeout"] !== undefined
|
|
62
|
+
|| flags["poll-interval"] !== undefined;
|
|
63
|
+
if (flags["no-wait"] === true && waitImplied) {
|
|
64
|
+
throw new UsageError("--include-logs, --summary, --timeout, and --poll-interval require waiting; remove --no-wait.", "invalid_enum");
|
|
65
|
+
}
|
|
66
|
+
return flags["no-wait"] !== true && (flags["wait"] === true || waitImplied);
|
|
67
|
+
}
|
|
68
|
+
export function recipeRoleInputItems(recipe, role) {
|
|
69
|
+
const inputs = recipe["inputs"];
|
|
70
|
+
if (!inputs || typeof inputs !== "object")
|
|
71
|
+
return [];
|
|
72
|
+
const roleEntry = inputs[role];
|
|
73
|
+
if (!roleEntry || typeof roleEntry !== "object")
|
|
74
|
+
return [];
|
|
75
|
+
const items = roleEntry["items"];
|
|
76
|
+
return Array.isArray(items) ? items : [];
|
|
77
|
+
}
|
|
78
|
+
export function recipeInputItemRef(item) {
|
|
79
|
+
if (!item || typeof item !== "object")
|
|
80
|
+
return undefined;
|
|
81
|
+
const ref = item["ref"];
|
|
82
|
+
return typeof ref === "string" && ref.length > 0 ? ref : undefined;
|
|
83
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DataikuClient } from "../client.js";
|
|
2
|
+
export declare function setOutputFieldProjection(fields: string[] | undefined): void;
|
|
3
|
+
export declare function enqueueCliWarning(warning: Record<string, unknown>): void;
|
|
4
|
+
export declare function pickResultFields(item: unknown, fields: string[]): unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Project the top-level fields callers asked for via --fields. Arrays are mapped
|
|
7
|
+
* element-wise; scalars and string results pass through untouched. Requested keys
|
|
8
|
+
* that are absent become null so every row keeps a stable, predictable shape.
|
|
9
|
+
*/
|
|
10
|
+
export declare function projectResultFields(result: unknown, fields: string[]): unknown;
|
|
11
|
+
export declare function writeCommandResult(result: unknown): void;
|
|
12
|
+
export declare function transientBodyWithTargetContext(body: string, target: string, elapsedMs: number): string;
|
|
13
|
+
export declare function addTransientTargetContext(error: unknown, target: string, elapsedMs: number): never;
|
|
14
|
+
export declare function isFailedWaitResult(result: unknown): boolean;
|
|
15
|
+
export declare function commandFailureExitCode(result: unknown): number | undefined;
|
|
16
|
+
export declare class CommandResultFailure extends Error {
|
|
17
|
+
readonly result: unknown;
|
|
18
|
+
readonly exitCode: number;
|
|
19
|
+
constructor(result: unknown, exitCode: number);
|
|
20
|
+
}
|
|
21
|
+
export declare function commandFailureMessage(result: unknown): string;
|
|
22
|
+
export declare function isNotFoundError(error: unknown): boolean;
|
|
23
|
+
export declare function readIfExists<T>(reader: () => Promise<T>): Promise<T | undefined>;
|
|
24
|
+
export declare function skipResult(resource: string, id: string, reason: "exists" | "missing", extra?: Record<string, unknown>): Record<string, unknown>;
|
|
25
|
+
export declare function planResult(resource: string, action: string, options: {
|
|
26
|
+
asyncKind: string;
|
|
27
|
+
endpoint?: string;
|
|
28
|
+
exitCodesOnFailure: Record<string, number>;
|
|
29
|
+
identifiers?: Record<string, unknown>;
|
|
30
|
+
idempotency: string;
|
|
31
|
+
method?: string;
|
|
32
|
+
payload?: unknown;
|
|
33
|
+
localWrites?: unknown;
|
|
34
|
+
plannedAndDryRun?: boolean;
|
|
35
|
+
wait?: unknown;
|
|
36
|
+
}): Record<string, unknown>;
|
|
37
|
+
export declare function encodedProjectEndpoint(client: DataikuClient, projectKey: string | undefined, suffix: string): string;
|
|
38
|
+
export declare function encodedProjectEndpointForPlan(projectKey: string, suffix: string): string;
|