dataiku-sdk 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +149 -46
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +102 -0
  4. package/dist/packages/types/src/index.js +76 -0
  5. package/dist/src/cli/commands/app.js +57 -15
  6. package/dist/src/cli/commands/bundle.js +1 -1
  7. package/dist/src/cli/commands/code-env.js +13 -7
  8. package/dist/src/cli/commands/connection.d.ts +9 -0
  9. package/dist/src/cli/commands/connection.js +267 -1
  10. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  11. package/dist/src/cli/commands/data-collection.js +164 -0
  12. package/dist/src/cli/commands/dataset.js +161 -32
  13. package/dist/src/cli/commands/future.js +2 -2
  14. package/dist/src/cli/commands/group.d.ts +2 -0
  15. package/dist/src/cli/commands/group.js +109 -0
  16. package/dist/src/cli/commands/index.js +151 -78
  17. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  18. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  19. package/dist/src/cli/commands/llm.d.ts +2 -0
  20. package/dist/src/cli/commands/llm.js +93 -0
  21. package/dist/src/cli/commands/macro.d.ts +2 -0
  22. package/dist/src/cli/commands/macro.js +221 -0
  23. package/dist/src/cli/commands/ml-task.js +18 -6
  24. package/dist/src/cli/commands/notebook.js +6 -2
  25. package/dist/src/cli/commands/plugin.d.ts +2 -0
  26. package/dist/src/cli/commands/plugin.js +799 -0
  27. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  28. package/dist/src/cli/commands/project-folder.js +197 -0
  29. package/dist/src/cli/commands/project-git.js +14 -54
  30. package/dist/src/cli/commands/project.js +52 -6
  31. package/dist/src/cli/commands/recipe.js +36 -1
  32. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  33. package/dist/src/cli/commands/saved-model.js +388 -1
  34. package/dist/src/cli/commands/scenario.js +138 -1
  35. package/dist/src/cli/commands/user.d.ts +8 -0
  36. package/dist/src/cli/commands/user.js +296 -0
  37. package/dist/src/cli/contract.d.ts +1 -2
  38. package/dist/src/cli/contract.js +1089 -103
  39. package/dist/src/cli/flags.js +30 -1
  40. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  41. package/dist/src/cli/helpers/app-successor.js +90 -30
  42. package/dist/src/cli/helpers/cleanup.js +3 -1
  43. package/dist/src/cli/helpers/flow-zone.js +1 -2
  44. package/dist/src/cli/output.d.ts +1 -1
  45. package/dist/src/cli/output.js +23 -10
  46. package/dist/src/cli/usage.d.ts +1 -0
  47. package/dist/src/cli/usage.js +47 -1
  48. package/dist/src/cli.js +147 -41
  49. package/dist/src/client.d.ts +83 -5
  50. package/dist/src/client.js +185 -29
  51. package/dist/src/errors.d.ts +3 -0
  52. package/dist/src/errors.js +2 -0
  53. package/dist/src/index.d.ts +16 -7
  54. package/dist/src/index.js +11 -2
  55. package/dist/src/resources/analyses.js +1 -8
  56. package/dist/src/resources/applications.d.ts +8 -6
  57. package/dist/src/resources/applications.js +21 -21
  58. package/dist/src/resources/base.d.ts +4 -0
  59. package/dist/src/resources/base.js +15 -0
  60. package/dist/src/resources/bundles.d.ts +9 -4
  61. package/dist/src/resources/bundles.js +10 -5
  62. package/dist/src/resources/connections.d.ts +78 -1
  63. package/dist/src/resources/connections.js +132 -1
  64. package/dist/src/resources/data-collections.d.ts +118 -0
  65. package/dist/src/resources/data-collections.js +49 -0
  66. package/dist/src/resources/datasets.d.ts +69 -0
  67. package/dist/src/resources/datasets.js +99 -1
  68. package/dist/src/resources/futures.d.ts +4 -1
  69. package/dist/src/resources/futures.js +38 -6
  70. package/dist/src/resources/groups.d.ts +47 -0
  71. package/dist/src/resources/groups.js +56 -0
  72. package/dist/src/resources/jobs.d.ts +8 -13
  73. package/dist/src/resources/jobs.js +41 -16
  74. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  75. package/dist/src/resources/knowledge-banks.js +61 -0
  76. package/dist/src/resources/llms.d.ts +92 -0
  77. package/dist/src/resources/llms.js +62 -0
  78. package/dist/src/resources/macros.d.ts +121 -0
  79. package/dist/src/resources/macros.js +231 -0
  80. package/dist/src/resources/ml-tasks.d.ts +37 -2
  81. package/dist/src/resources/ml-tasks.js +100 -20
  82. package/dist/src/resources/model-evaluation-stores.js +1 -8
  83. package/dist/src/resources/notebooks.d.ts +43 -7
  84. package/dist/src/resources/notebooks.js +53 -13
  85. package/dist/src/resources/plugins.d.ts +265 -0
  86. package/dist/src/resources/plugins.js +458 -0
  87. package/dist/src/resources/project-folders.d.ts +71 -0
  88. package/dist/src/resources/project-folders.js +57 -0
  89. package/dist/src/resources/project-git.d.ts +6 -0
  90. package/dist/src/resources/project-git.js +69 -42
  91. package/dist/src/resources/project-library.d.ts +0 -1
  92. package/dist/src/resources/project-library.js +1 -17
  93. package/dist/src/resources/projects.d.ts +19 -1
  94. package/dist/src/resources/projects.js +27 -1
  95. package/dist/src/resources/recipes.d.ts +15 -1
  96. package/dist/src/resources/recipes.js +73 -6
  97. package/dist/src/resources/saved-models.d.ts +134 -1
  98. package/dist/src/resources/saved-models.js +214 -6
  99. package/dist/src/resources/scenarios.d.ts +105 -0
  100. package/dist/src/resources/scenarios.js +232 -106
  101. package/dist/src/resources/sql.js +5 -1
  102. package/dist/src/resources/users.d.ts +129 -0
  103. package/dist/src/resources/users.js +128 -0
  104. package/dist/src/resources/webapps.d.ts +15 -2
  105. package/dist/src/resources/webapps.js +18 -4
  106. package/dist/src/schemas.d.ts +2 -2
  107. package/dist/src/schemas.js +1 -1
  108. package/dist/src/utils/deep-merge.d.ts +1 -1
  109. package/dist/src/utils/polling.d.ts +20 -0
  110. package/dist/src/utils/polling.js +27 -0
  111. package/dist/src/utils/response-file.js +1 -1
  112. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  113. package/dist/src/utils/secret-sanitize.js +101 -0
  114. package/package.json +6 -5
  115. package/packages/types/dist/index.d.ts +102 -0
  116. package/packages/types/dist/index.js +76 -0
  117. package/plugin.json +1 -1
  118. package/skills/dataiku-dss/SKILL.md +7 -5
  119. package/skills/dataiku-dss/references/app-releases.md +8 -7
  120. package/skills/dataiku-dss/references/discovery.md +4 -4
@@ -0,0 +1,458 @@
1
+ import { ClientValidationError, DataikuError, } from "../errors.js";
2
+ import { BaseResource, requireNonEmpty, } from "./base.js";
3
+ import { validateGitReferencePath, } from "./project-git.js";
4
+ /* ------------------------------------------------------------------ */
5
+ /* Path helpers */
6
+ /* ------------------------------------------------------------------ */
7
+ const PLUGIN_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/;
8
+ /**
9
+ * Validate a plugin identifier. Plugin ids select the API route segment, so a
10
+ * hostile id must not be able to change which resource the URL addresses.
11
+ */
12
+ export function validatePluginId(pluginId) {
13
+ requireNonEmpty(pluginId, "pluginId");
14
+ if (!PLUGIN_ID_PATTERN.test(pluginId)) {
15
+ throw new ClientValidationError(`Invalid plugin id: ids use letters, digits, '-', '_', and '.', and start with a letter or digit.`, "validation_failed", undefined, { pluginId, });
16
+ }
17
+ return pluginId;
18
+ }
19
+ function pluginPathError(path, message) {
20
+ return new ClientValidationError(message, "validation_failed", "Use a plugin path relative to the plugin root: folders separated by '/', each segment a simple file or folder name.", { path, });
21
+ }
22
+ const CONTROL_CHARACTER_RE = /[\u0000-\u001f\u007f]/;
23
+ const BACKSLASH_RE = /\\/;
24
+ const TRAILING_AMBIGUOUS_RE = /[\s.]$/;
25
+ const WHITESPACE_ONLY_RE = /^\s+$/;
26
+ /**
27
+ * Validate and canonicalize a path inside a plugin. Mirrors the project
28
+ * library validator: rejects empty paths, control characters, backslashes,
29
+ * empty segments, '.'/'..' traversals, whitespace-only or edge-whitespace
30
+ * segments, and trailing dot/space segments.
31
+ *
32
+ * Returns the canonical path with any leading '/' stripped (the contents
33
+ * endpoints take paths relative to the plugin root).
34
+ */
35
+ export function validatePluginPath(path) {
36
+ if (typeof path !== "string" || path.trim() === "") {
37
+ throw pluginPathError(path, "A plugin path is required.");
38
+ }
39
+ if (CONTROL_CHARACTER_RE.test(path)) {
40
+ throw pluginPathError(path, "Plugin path must not contain control characters.");
41
+ }
42
+ const normalized = path.replace(/^\/+/, "");
43
+ if (normalized === "") {
44
+ throw pluginPathError(path, "Plugin path must name a file or folder below the plugin root.");
45
+ }
46
+ for (const segment of normalized.split("/")) {
47
+ if (segment === "") {
48
+ throw pluginPathError(path, "Plugin path must not contain empty segments (leading, trailing, or doubled '/').");
49
+ }
50
+ if (segment === "." || segment === "..") {
51
+ throw pluginPathError(path, "Plugin path must not contain '.' or '..' segments.");
52
+ }
53
+ if (BACKSLASH_RE.test(segment)) {
54
+ throw pluginPathError(path, "Plugin path must not contain backslashes.");
55
+ }
56
+ if (segment !== segment.trim() || WHITESPACE_ONLY_RE.test(segment)) {
57
+ throw pluginPathError(path, "Plugin path segments must not be whitespace-only, or start or end with whitespace.");
58
+ }
59
+ if (TRAILING_AMBIGUOUS_RE.test(segment)) {
60
+ throw pluginPathError(path, "Plugin path must not contain segments with a trailing space or dot (ambiguous across filesystem normalizations).");
61
+ }
62
+ }
63
+ return normalized;
64
+ }
65
+ /**
66
+ * Validate a destination folder for a plugin contents move. '/' (the plugin
67
+ * root) is valid; any other path must pass {@link validatePluginPath}.
68
+ * Returns the canonical destination ('/' or '/<path>').
69
+ */
70
+ export function validatePluginDestinationPath(path) {
71
+ if (path === "/")
72
+ return "/";
73
+ return `/${validatePluginPath(path)}`;
74
+ }
75
+ /** Validate a rename target: a single plugin path segment, no '/'. */
76
+ export function validatePluginName(name) {
77
+ if (typeof name !== "string" || name.trim() === "") {
78
+ throw new ClientValidationError("Plugin rename target must be a non-empty string.", "validation_failed", undefined, { newName: name, });
79
+ }
80
+ if (name.includes("/")) {
81
+ throw new ClientValidationError("Plugin rename target must be a single segment without '/'.", "validation_failed", "Rename within the current parent folder; use the move operation to change folders.", { newName: name, });
82
+ }
83
+ if (name === "." || name === "..") {
84
+ throw new ClientValidationError("Plugin rename target must not be '.' or '..'.", "validation_failed", undefined, { newName: name, });
85
+ }
86
+ if (BACKSLASH_RE.test(name)) {
87
+ throw new ClientValidationError("Plugin rename target must not contain backslashes.", "validation_failed", undefined, { newName: name, });
88
+ }
89
+ if (name !== name.trim() || WHITESPACE_ONLY_RE.test(name) || TRAILING_AMBIGUOUS_RE.test(name)) {
90
+ throw new ClientValidationError("Plugin rename target must not start or end with whitespace or a trailing dot.", "validation_failed", undefined, { newName: name, });
91
+ }
92
+ return name;
93
+ }
94
+ /** Validate and URL-encode each segment of a plugin path for endpoint use. */
95
+ export function encodePluginPath(path) {
96
+ const normalized = validatePluginPath(path);
97
+ return normalized
98
+ .split("/")
99
+ .map((segment) => encodeURIComponent(segment))
100
+ .join("/");
101
+ }
102
+ /** URL-encode a plugin id after validation. */
103
+ function enc(pluginId) {
104
+ return encodeURIComponent(validatePluginId(pluginId));
105
+ }
106
+ /* ------------------------------------------------------------------ */
107
+ /* Resource */
108
+ /* ------------------------------------------------------------------ */
109
+ /**
110
+ * The documented Plugins REST section (DSS 15): instance-level plugin
111
+ * administration under `/public/api/plugins/...`. All endpoints require admin
112
+ * or plugin-developer privileges server-side.
113
+ */
114
+ export class PluginsResource extends BaseResource {
115
+ /* ---- listing & lifecycle ---- */
116
+ /** List installed plugins (including dev plugins). */
117
+ async list() {
118
+ return this.client.get("/public/api/plugins/");
119
+ }
120
+ /**
121
+ * Install a plugin from a local zip file. Fails if already installed.
122
+ * Resolves only after the installation completed: when DSS answers with a
123
+ * future receipt the future is settled before returning.
124
+ */
125
+ async installFromZip(filePath) {
126
+ const receipt = await this.client.uploadJson("/public/api/plugins/actions/installFromZip", filePath);
127
+ await this.settleActionFuture(receipt, "installFromZip");
128
+ }
129
+ /** Install a plugin from the Dataiku store. Fails if already installed. */
130
+ async installFromStore(pluginId) {
131
+ const receipt = await this.client.post("/public/api/plugins/actions/installFromStore", { pluginId: validatePluginId(pluginId), });
132
+ await this.settleActionFuture(receipt, "installFromStore");
133
+ }
134
+ /** Install a plugin by checking out a Git repository. Fails if already installed. */
135
+ async installFromGit(options) {
136
+ const receipt = await this.client.post("/public/api/plugins/actions/installFromGit", {
137
+ gitRepositoryUrl: validatedPluginGitUrl(options.gitRepositoryUrl),
138
+ gitCheckout: options.gitCheckout ?? null,
139
+ gitSubpath: options.gitSubpath ?? null,
140
+ });
141
+ await this.settleActionFuture(receipt, "installFromGit");
142
+ }
143
+ /** Download a development plugin as a zip archive. */
144
+ async download(pluginId) {
145
+ return this.client.stream(`/public/api/plugins/${enc(pluginId)}/download`);
146
+ }
147
+ /**
148
+ * Update a plugin from a local zip file. Fails if not already installed.
149
+ * Resolves only after the update completed (future receipt settled).
150
+ */
151
+ async updateFromZip(pluginId, filePath) {
152
+ const receipt = await this.client.uploadJson(`/public/api/plugins/${enc(pluginId)}/actions/updateFromZip`, filePath);
153
+ await this.settleActionFuture(receipt, "updateFromZip");
154
+ }
155
+ /** Update a plugin from the Dataiku Store. */
156
+ async updateFromStore(pluginId) {
157
+ const receipt = await this.client.post(`/public/api/plugins/${enc(pluginId)}/actions/updateFromStore`, {});
158
+ await this.settleActionFuture(receipt, "updateFromStore");
159
+ }
160
+ /**
161
+ * Update an installed plugin from Git. Fails if the plugin is not already
162
+ * installed. Resolves only after the update completed (future settled).
163
+ */
164
+ async updateFromGit(pluginId, options) {
165
+ const receipt = await this.client.post(`/public/api/plugins/${enc(pluginId)}/actions/updateFromGit`, {
166
+ gitRepositoryUrl: validatedPluginGitUrl(options.gitRepositoryUrl),
167
+ gitCheckout: options.gitCheckout ?? null,
168
+ gitSubpath: options.gitSubpath ?? null,
169
+ });
170
+ await this.settleActionFuture(receipt, "updateFromGit");
171
+ }
172
+ /**
173
+ * Move an installed plugin to the development environment. Resolves only
174
+ * after the move completed (future receipt settled).
175
+ */
176
+ async moveToDev(pluginId) {
177
+ const receipt = await this.client.post(`/public/api/plugins/${enc(pluginId)}/actions/moveToDev`, {});
178
+ await this.settleActionFuture(receipt, "moveToDev");
179
+ }
180
+ /**
181
+ * Delete a plugin; refuses when usages exist unless `force` is set.
182
+ * Resolves only after the deletion completed: DSS answers this route with
183
+ * a future, so reporting success before the future settles would leave
184
+ * the plugin briefly present after the call returned.
185
+ */
186
+ async delete(pluginId, options = {}) {
187
+ const receipt = await this.client.post(`/public/api/plugins/${enc(pluginId)}/actions/delete`, { force: options.force === true, });
188
+ await this.settleActionFuture(receipt, "delete");
189
+ }
190
+ /* ---- settings ---- */
191
+ /** Get plugin settings (optionally scoped to a project). */
192
+ async getSettings(pluginId, options = {}) {
193
+ const query = scopeQuery(options.projectKey);
194
+ return this.client.get(`/public/api/plugins/${enc(pluginId)}/settings${query}`);
195
+ }
196
+ /**
197
+ * Replace plugin settings. DSS expects a settings object previously
198
+ * obtained through {@link getSettings}; `config` and `codeEnvName` are
199
+ * the documented fields and any extra keys the caller read are forwarded.
200
+ */
201
+ async setSettings(pluginId, settings, options = {}) {
202
+ if (settings === null || typeof settings !== "object" || Array.isArray(settings)) {
203
+ throw new ClientValidationError("Plugin settings must be an object (use the object returned by plugins.getSettings).");
204
+ }
205
+ await this.client.post(`/public/api/plugins/${enc(pluginId)}/settings${scopeQuery(options.projectKey)}`, settings);
206
+ }
207
+ /* ---- usages ---- */
208
+ /** List usages of the plugin's elements, optionally scoped to a project. */
209
+ async listUsages(pluginId, options = {}) {
210
+ const raw = await this.client.get(`/public/api/plugins/${enc(pluginId)}/actions/listUsages${scopeQuery(options.projectKey)}`);
211
+ return raw ?? {};
212
+ }
213
+ /* ---- code env ---- */
214
+ /**
215
+ * Create the code env of a plugin. Returns the DSS future handle
216
+ * (`{jobId}`). The request carries the plugin-managed deployment mode and
217
+ * an explicit interpreter (null = the plugin's default), exactly as the
218
+ * official Python client sends it.
219
+ */
220
+ async createCodeEnv(pluginId, options = {}) {
221
+ const raw = await this.client.post(`/public/api/plugins/${enc(pluginId)}/code-env/actions/create`, {
222
+ deploymentMode: "PLUGIN_MANAGED",
223
+ conda: options.conda === true,
224
+ pythonInterpreter: options.pythonInterpreter ?? null,
225
+ });
226
+ return requireJobId(raw, "plugins.createCodeEnv");
227
+ }
228
+ /** Update the code env of a plugin. Returns the DSS future handle. */
229
+ async updateCodeEnv(pluginId) {
230
+ const raw = await this.client.post(`/public/api/plugins/${enc(pluginId)}/code-env/actions/update`, {});
231
+ return requireJobId(raw, "plugins.updateCodeEnv");
232
+ }
233
+ /* ---- development plugins ---- */
234
+ /** Create a new development plugin (empty, cloned, or exported from Git). */
235
+ async createDev(options) {
236
+ if (options.creationMode !== "EMPTY" && options.creationMode !== "GIT_CLONE"
237
+ && options.creationMode !== "GIT_EXPORT") {
238
+ throw new ClientValidationError("creationMode must be one of EMPTY, GIT_CLONE, GIT_EXPORT.");
239
+ }
240
+ const needsGit = options.creationMode !== "EMPTY";
241
+ if (needsGit && !options.gitRepository?.trim()) {
242
+ throw new ClientValidationError(`gitRepository is required when creationMode is ${options.creationMode}.`);
243
+ }
244
+ await this.client.post("/public/api/plugins/actions/createDev", {
245
+ pluginId: validatePluginId(options.pluginId),
246
+ creationMode: options.creationMode,
247
+ gitRepository: needsGit ? validatedPluginGitUrl(options.gitRepository) : null,
248
+ gitCheckout: needsGit ? options.gitCheckout ?? null : null,
249
+ gitSubpath: options.creationMode === "GIT_EXPORT" ? options.gitSubpath ?? null : null,
250
+ });
251
+ }
252
+ /* ---- dev plugin Git remote ---- */
253
+ /** Get the Git remote declared for a dev plugin (null when unset). */
254
+ async getGitRemote(pluginId) {
255
+ const raw = await this.client.get(`/public/api/plugins/${enc(pluginId)}/gitRemote`);
256
+ if (raw === undefined || raw === null)
257
+ return { repositoryUrl: null, };
258
+ if (typeof raw !== "object" || Array.isArray(raw)) {
259
+ return { repositoryUrl: null, ...wrapUnknown(raw), };
260
+ }
261
+ const record = raw;
262
+ return {
263
+ repositoryUrl: typeof record.repositoryUrl === "string" ? record.repositoryUrl : null,
264
+ ...record,
265
+ };
266
+ }
267
+ /** Set (create or replace) the Git remote of a dev plugin. */
268
+ async setGitRemote(pluginId, repositoryUrl) {
269
+ const validated = validatedPluginGitUrl(repositoryUrl);
270
+ const raw = await this.client.post(`/public/api/plugins/${enc(pluginId)}/gitRemote`, { repositoryUrl: validated, });
271
+ return raw ?? { repositoryUrl: validated, };
272
+ }
273
+ /** Delete the Git remote declared for a dev plugin. */
274
+ async deleteGitRemote(pluginId) {
275
+ await this.client.del(`/public/api/plugins/${enc(pluginId)}/gitRemote`);
276
+ }
277
+ /**
278
+ * List the Git branches of a dev plugin. Observed DSS 15 contract: GET
279
+ * `/plugins/{pluginId}/gitBranches` answers `200 ["master"]`. The upstream
280
+ * DSS 15 REST reference documents POST for this path, but the live API
281
+ * answers that POST with 405 Method Not Allowed (upstream method wrong);
282
+ * this SDK uses the observed GET, with no POST fallback.
283
+ */
284
+ async listGitBranches(pluginId) {
285
+ const raw = await this.client.get(`/public/api/plugins/${enc(pluginId)}/gitBranches`);
286
+ return Array.isArray(raw) ? raw : [];
287
+ }
288
+ /** Push the dev plugin's content to its previously-declared Git remote. */
289
+ async push(pluginId) {
290
+ return this.devGitAction(pluginId, "push");
291
+ }
292
+ /** Pull (and rebase) the dev plugin's content from its Git remote. */
293
+ async pull(pluginId) {
294
+ return this.devGitAction(pluginId, "pullRebase");
295
+ }
296
+ /** Fetch the dev plugin's content from its Git remote. */
297
+ async fetch(pluginId) {
298
+ return this.devGitAction(pluginId, "fetch");
299
+ }
300
+ /** Reset the dev plugin to its local HEAD state (discards local edits). */
301
+ async resetToLocalHeadState(pluginId) {
302
+ return this.devGitAction(pluginId, "resetToLocalHeadState");
303
+ }
304
+ /** Reset the dev plugin to the remote HEAD state (discards local work). */
305
+ async resetToRemoteHeadState(pluginId) {
306
+ return this.devGitAction(pluginId, "resetToRemoteHeadState");
307
+ }
308
+ async devGitAction(pluginId, action) {
309
+ const raw = await this.client.post(`/public/api/plugins/${enc(pluginId)}/actions/${action}`, {});
310
+ const receipt = raw ?? {};
311
+ await this.settleActionFuture(receipt, action);
312
+ return receipt;
313
+ }
314
+ /**
315
+ * Settle a plugin action's future receipt. Plugin action endpoints answer
316
+ * with `{jobId}` when DSS processes the action asynchronously, and with an
317
+ * empty or already-settled body for synchronous ones — the official
318
+ * Python client's `DSSFuture.from_resp` accepts a missing jobId and its
319
+ * `wait_for_result` treats a jobless state as already complete. A jobless
320
+ * receipt is returned as-is (no fake wait); a jobId is polled through the
321
+ * shared futures waiter so callers only observe completion.
322
+ */
323
+ async settleActionFuture(receipt, action) {
324
+ const jobId = receipt?.["jobId"];
325
+ if (typeof jobId !== "string" || jobId.length === 0)
326
+ return;
327
+ const waited = await this.client.futures.wait(jobId, {});
328
+ if (waited.state !== "DONE") {
329
+ throw new DataikuError(200, "Unexpected Response", `plugin ${action} future ${jobId} ended in state ${waited.state}${waited.timedOut === true ? " (wait timed out)" : ""}`);
330
+ }
331
+ }
332
+ /* ---- dev plugin contents ---- */
333
+ /** List the contents tree of a dev plugin. */
334
+ async listContents(pluginId) {
335
+ return this.client.get(`/public/api/plugins/${enc(pluginId)}/contents/`);
336
+ }
337
+ /**
338
+ * Read a dev plugin file as text. DSS answers this route with the raw file
339
+ * body (no JSON envelope, `dataEncoding` is not honored), so the response
340
+ * is read as text directly.
341
+ */
342
+ async getFile(pluginId, path) {
343
+ const valid = validatePluginPath(path);
344
+ return await this.client.getText(this.contentsPath(pluginId, valid));
345
+ }
346
+ /** Read a dev plugin file as raw bytes (same raw-body route as getFile). */
347
+ async getFileBytes(pluginId, path) {
348
+ const valid = validatePluginPath(path);
349
+ const res = await this.client.stream(this.contentsPath(pluginId, valid));
350
+ return new Uint8Array(await res.arrayBuffer());
351
+ }
352
+ /** Download a dev plugin file as a raw stream (binary-safe). */
353
+ async downloadFile(pluginId, path) {
354
+ return this.client.stream(this.contentsPath(pluginId, validatePluginPath(path)));
355
+ }
356
+ /** Get a single plugin file's detail record (size, mime, timestamps). */
357
+ async getFileDetails(pluginId, path) {
358
+ const valid = validatePluginPath(path);
359
+ const raw = await this.client.get(`/public/api/plugins/${enc(pluginId)}/details/${encodePluginPath(valid)}`);
360
+ return raw ?? {};
361
+ }
362
+ /** Upload (create or replace) a dev plugin file with text or binary content. */
363
+ async putFile(pluginId, path, content) {
364
+ const valid = validatePluginPath(path);
365
+ if (typeof content !== "string" && !(content instanceof Uint8Array)) {
366
+ throw new ClientValidationError("Plugin file content must be a string or raw bytes.", "validation_failed");
367
+ }
368
+ const payload = typeof content === "string" ? Buffer.from(content, "utf8") : content;
369
+ await this.client.postRawBody(this.contentsPath(pluginId, valid), payload);
370
+ }
371
+ /** Delete a file (or folder) from a dev plugin. */
372
+ async deleteFile(pluginId, path) {
373
+ await this.client.del(this.contentsPath(pluginId, validatePluginPath(path)));
374
+ }
375
+ /** Add a folder to a dev plugin. */
376
+ async addFolder(pluginId, path) {
377
+ const valid = validatePluginPath(path);
378
+ await this.client.post(`/public/api/plugins/${enc(pluginId)}/folders/${encodePluginPath(valid)}`, {});
379
+ }
380
+ /** Rename a file or folder inside a dev plugin (same parent folder). */
381
+ async rename(pluginId, path, newName) {
382
+ const validPath = validatePluginPath(path);
383
+ const validName = validatePluginName(newName);
384
+ await this.client.post(`/public/api/plugins/${enc(pluginId)}/contents-actions/rename`, { oldPath: `/${validPath}`, newName: validName, });
385
+ }
386
+ /** Move a file or folder inside a dev plugin. The destination folder must exist. */
387
+ async move(pluginId, path, destinationFolderPath) {
388
+ const validPath = validatePluginPath(path);
389
+ const destination = validatePluginDestinationPath(destinationFolderPath);
390
+ await this.client.post(`/public/api/plugins/${enc(pluginId)}/contents-actions/move`, { oldPath: `/${validPath}`, newPath: destination, });
391
+ }
392
+ /* ---- private ---- */
393
+ contentsPath(pluginId, path) {
394
+ return `/public/api/plugins/${enc(pluginId)}/contents/${encodePluginPath(path)}`;
395
+ }
396
+ }
397
+ /* ------------------------------------------------------------------ */
398
+ /* Shared helpers */
399
+ /* ------------------------------------------------------------------ */
400
+ function wrapUnknown(value) {
401
+ return value !== null && typeof value === "object" && !Array.isArray(value)
402
+ ? value
403
+ : { value, };
404
+ }
405
+ function scopeQuery(projectKey) {
406
+ if (projectKey === undefined)
407
+ return "";
408
+ const trimmed = projectKey.trim();
409
+ if (trimmed === "")
410
+ return "";
411
+ return `?projectKey=${encodeURIComponent(trimmed)}`;
412
+ }
413
+ function requireJobId(raw, method) {
414
+ if (raw === undefined || raw === null || typeof raw.jobId !== "string" || raw.jobId === "") {
415
+ throw new DataikuError(200, "Unexpected Response", `${method}: DSS did not return a future jobId.`);
416
+ }
417
+ return raw;
418
+ }
419
+ /**
420
+ * Reject `https://user:token@host/repo` before it reaches DSS for plugin Git
421
+ * install/update/dev-create and the dev-plugin Git remote. Same policy as the
422
+ * project Git remotes: HTTP(S) URLs must not embed credentials; SSH and
423
+ * scp-style remotes stay allowed.
424
+ */
425
+ export function validatedPluginGitUrl(repository, field = "Git repository URL") {
426
+ const candidate = repository.trim();
427
+ if (candidate === "")
428
+ throw new ClientValidationError(`${field} is required.`);
429
+ if (/[\u0000-\u001f\u007f]/u.test(candidate)) {
430
+ throw new ClientValidationError(`${field} must not contain control characters.`);
431
+ }
432
+ if (/^https?:/i.test(candidate)) {
433
+ if (!/^https?:\/\//i.test(candidate) || candidate.includes("\\")) {
434
+ throw new ClientValidationError(`${field} must be a valid HTTP(S) URL.`);
435
+ }
436
+ let parsed;
437
+ try {
438
+ parsed = new URL(candidate);
439
+ }
440
+ catch {
441
+ throw new ClientValidationError(`${field} must be a valid HTTP(S) URL.`);
442
+ }
443
+ if (parsed.username !== "" || parsed.password !== "") {
444
+ throw new ClientValidationError(`${field} must not embed credentials (user:password@host). `
445
+ + "Use an SSH remote or configure the Git credentials in DSS instead.");
446
+ }
447
+ }
448
+ return candidate;
449
+ }
450
+ /**
451
+ * True when a value is a usable plugin id shape without throwing — used by the
452
+ * CLI to decide which actions need a plugin id positional.
453
+ */
454
+ export function isPluginIdCandidate(value) {
455
+ return typeof value === "string" && PLUGIN_ID_PATTERN.test(value);
456
+ }
457
+ // Re-exported for CLI consumers so a single import site covers path rules.
458
+ export { validateGitReferencePath, };
@@ -0,0 +1,71 @@
1
+ import { BaseResource } from "./base.js";
2
+ /**
3
+ * Project folders form the instance-wide hierarchy in which projects live.
4
+ * These are NOT managed folders (see {@link FoldersResource} for project data
5
+ * storage); the project-folder API is an instance-level tree of containers.
6
+ *
7
+ * @see https://doc.dataiku.com/dss/api/15/rest/ (Project Folders section)
8
+ */
9
+ export interface ProjectFolder extends Record<string, unknown> {
10
+ /** ID of the project folder. */
11
+ id?: string;
12
+ /** Name of the project folder (null for the root project folder). */
13
+ name?: string | null;
14
+ /** ID of the parent project folder (null for the root project folder). */
15
+ parentId?: string | null;
16
+ /** IDs of this project folder's children. */
17
+ childrenIds?: string[];
18
+ /** Project keys inside this project folder. */
19
+ projectKeys?: string[];
20
+ }
21
+ export interface ProjectFolderSettings extends Record<string, unknown> {
22
+ /** Name of the project folder. */
23
+ name?: string;
24
+ /** Login of the owner of the project folder. */
25
+ owner?: string;
26
+ /** Permissions of the project folder. */
27
+ permissions?: ProjectFolderPermission[] | null;
28
+ }
29
+ export interface ProjectFolderPermission extends Record<string, unknown> {
30
+ group?: string;
31
+ read?: boolean;
32
+ writeContents?: boolean;
33
+ admin?: boolean;
34
+ }
35
+ export declare class ProjectFoldersResource extends BaseResource {
36
+ /**
37
+ * Get the definition of the root project folder. Only items on which the
38
+ * API key has READ privilege for project folders (children) and READ_CONF
39
+ * for projects are listed.
40
+ */
41
+ root(): Promise<ProjectFolder>;
42
+ /** Get the definition of a project folder. Requires READ on the folder. */
43
+ get(folderId: string): Promise<ProjectFolder>;
44
+ /** Get the settings of a project folder. Requires ADMIN on the folder. */
45
+ getSettings(folderId: string): Promise<ProjectFolderSettings>;
46
+ /** Replace the settings of a project folder. Requires ADMIN on the folder. */
47
+ updateSettings(folderId: string, settings: ProjectFolderSettings): Promise<void>;
48
+ /**
49
+ * Move a project folder into another project folder (change its parent)
50
+ * along with its content. A folder cannot be moved into one of its own
51
+ * sub-folders. Requires ADMIN on the folder and WRITE_CONTENTS on the
52
+ * destination.
53
+ */
54
+ move(folderId: string, destinationId: string): Promise<void>;
55
+ /**
56
+ * Permanently delete a project folder. It must be empty: no sub-folders
57
+ * and no projects inside. Requires ADMIN on the folder.
58
+ */
59
+ delete(folderId: string): Promise<void>;
60
+ /**
61
+ * Create a sub-project folder within the given project folder. Requires
62
+ * WRITE_CONTENTS on the parent.
63
+ */
64
+ createChild(folderId: string, name: string): Promise<ProjectFolder>;
65
+ /**
66
+ * Move a project from the project folder it lives in to another project
67
+ * folder. Requires WRITE_CONTENTS on the destination folder and admin on
68
+ * the project.
69
+ */
70
+ moveProject(folderId: string, projectKey: string, destinationId: string): Promise<void>;
71
+ }
@@ -0,0 +1,57 @@
1
+ import { BaseResource, } from "./base.js";
2
+ function folderPath(folderId) {
3
+ return `/public/api/project-folders/${encodeURIComponent(folderId)}`;
4
+ }
5
+ export class ProjectFoldersResource extends BaseResource {
6
+ /**
7
+ * Get the definition of the root project folder. Only items on which the
8
+ * API key has READ privilege for project folders (children) and READ_CONF
9
+ * for projects are listed.
10
+ */
11
+ async root() {
12
+ return this.client.get("/public/api/project-folders/");
13
+ }
14
+ /** Get the definition of a project folder. Requires READ on the folder. */
15
+ async get(folderId) {
16
+ return this.client.get(folderPath(folderId));
17
+ }
18
+ /** Get the settings of a project folder. Requires ADMIN on the folder. */
19
+ async getSettings(folderId) {
20
+ return this.client.get(`${folderPath(folderId)}/settings`);
21
+ }
22
+ /** Replace the settings of a project folder. Requires ADMIN on the folder. */
23
+ async updateSettings(folderId, settings) {
24
+ await this.client.putVoid(`${folderPath(folderId)}/settings`, settings);
25
+ }
26
+ /**
27
+ * Move a project folder into another project folder (change its parent)
28
+ * along with its content. A folder cannot be moved into one of its own
29
+ * sub-folders. Requires ADMIN on the folder and WRITE_CONTENTS on the
30
+ * destination.
31
+ */
32
+ async move(folderId, destinationId) {
33
+ await this.client.post(`${folderPath(folderId)}/move?destination=${encodeURIComponent(destinationId)}`);
34
+ }
35
+ /**
36
+ * Permanently delete a project folder. It must be empty: no sub-folders
37
+ * and no projects inside. Requires ADMIN on the folder.
38
+ */
39
+ async delete(folderId) {
40
+ await this.client.del(folderPath(folderId));
41
+ }
42
+ /**
43
+ * Create a sub-project folder within the given project folder. Requires
44
+ * WRITE_CONTENTS on the parent.
45
+ */
46
+ async createChild(folderId, name) {
47
+ return this.client.post(`${folderPath(folderId)}/children?name=${encodeURIComponent(name)}`);
48
+ }
49
+ /**
50
+ * Move a project from the project folder it lives in to another project
51
+ * folder. Requires WRITE_CONTENTS on the destination folder and admin on
52
+ * the project.
53
+ */
54
+ async moveProject(folderId, projectKey, destinationId) {
55
+ await this.client.post(`${folderPath(folderId)}/projects/${encodeURIComponent(projectKey)}/move?destination=${encodeURIComponent(destinationId)}`);
56
+ }
57
+ }
@@ -62,6 +62,12 @@ export interface ProjectGitSetLibraryOptions {
62
62
  }
63
63
  export interface ProjectGitFutureStateOptions {
64
64
  peek?: boolean;
65
+ /**
66
+ * Total duration budget in milliseconds for this single state request,
67
+ * covering headers, retries, backoff, and body read. Intended for polling
68
+ * loops that must not hang on a stalled endpoint.
69
+ */
70
+ timeoutMs?: number;
65
71
  }
66
72
  export interface ProjectGitFutureWaitOptions {
67
73
  pollIntervalMs?: number;