dataiku-sdk 3.1.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 (149) hide show
  1. package/README.md +253 -38
  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/auth.js +1 -1
  6. package/dist/src/cli/coerce.js +11 -2
  7. package/dist/src/cli/commands/analysis.js +4 -3
  8. package/dist/src/cli/commands/api-service.js +5 -3
  9. package/dist/src/cli/commands/app.js +61 -18
  10. package/dist/src/cli/commands/bundle.js +3 -2
  11. package/dist/src/cli/commands/code-env.js +23 -16
  12. package/dist/src/cli/commands/connection.d.ts +9 -0
  13. package/dist/src/cli/commands/connection.js +267 -1
  14. package/dist/src/cli/commands/dashboard.js +7 -6
  15. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  16. package/dist/src/cli/commands/data-collection.js +164 -0
  17. package/dist/src/cli/commands/data-quality.js +9 -8
  18. package/dist/src/cli/commands/dataset.js +170 -40
  19. package/dist/src/cli/commands/doctor.js +4 -2
  20. package/dist/src/cli/commands/flow-zone.js +9 -8
  21. package/dist/src/cli/commands/folder.js +9 -8
  22. package/dist/src/cli/commands/future.js +4 -3
  23. package/dist/src/cli/commands/group.d.ts +2 -0
  24. package/dist/src/cli/commands/group.js +109 -0
  25. package/dist/src/cli/commands/index.js +151 -78
  26. package/dist/src/cli/commands/insight.js +8 -7
  27. package/dist/src/cli/commands/job.js +4 -3
  28. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  29. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  30. package/dist/src/cli/commands/llm.d.ts +2 -0
  31. package/dist/src/cli/commands/llm.js +93 -0
  32. package/dist/src/cli/commands/macro.d.ts +2 -0
  33. package/dist/src/cli/commands/macro.js +221 -0
  34. package/dist/src/cli/commands/meaning.js +5 -4
  35. package/dist/src/cli/commands/ml-task.js +21 -8
  36. package/dist/src/cli/commands/model-evaluation-store.js +4 -3
  37. package/dist/src/cli/commands/notebook.js +17 -12
  38. package/dist/src/cli/commands/plugin.d.ts +2 -0
  39. package/dist/src/cli/commands/plugin.js +799 -0
  40. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  41. package/dist/src/cli/commands/project-folder.js +197 -0
  42. package/dist/src/cli/commands/project-git.js +14 -54
  43. package/dist/src/cli/commands/project-library.js +7 -6
  44. package/dist/src/cli/commands/project.js +55 -8
  45. package/dist/src/cli/commands/recipe.js +50 -14
  46. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  47. package/dist/src/cli/commands/saved-model.js +392 -4
  48. package/dist/src/cli/commands/scenario.js +147 -9
  49. package/dist/src/cli/commands/sql.js +3 -2
  50. package/dist/src/cli/commands/statistics.js +7 -6
  51. package/dist/src/cli/commands/user.d.ts +8 -0
  52. package/dist/src/cli/commands/user.js +296 -0
  53. package/dist/src/cli/commands/variable.js +2 -1
  54. package/dist/src/cli/commands/webapp.js +2 -1
  55. package/dist/src/cli/commands/wiki.js +7 -6
  56. package/dist/src/cli/contract.d.ts +2 -3
  57. package/dist/src/cli/contract.js +1167 -163
  58. package/dist/src/cli/flags.d.ts +4 -0
  59. package/dist/src/cli/flags.js +50 -2
  60. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  61. package/dist/src/cli/helpers/app-successor.js +90 -30
  62. package/dist/src/cli/helpers/cleanup.js +3 -1
  63. package/dist/src/cli/helpers/flow-zone.js +1 -2
  64. package/dist/src/cli/output.d.ts +1 -1
  65. package/dist/src/cli/output.js +23 -10
  66. package/dist/src/cli/usage.d.ts +1 -0
  67. package/dist/src/cli/usage.js +47 -1
  68. package/dist/src/cli.js +225 -95
  69. package/dist/src/client.d.ts +86 -6
  70. package/dist/src/client.js +263 -50
  71. package/dist/src/errors.d.ts +3 -0
  72. package/dist/src/errors.js +4 -0
  73. package/dist/src/index.d.ts +16 -7
  74. package/dist/src/index.js +11 -2
  75. package/dist/src/resources/analyses.js +1 -8
  76. package/dist/src/resources/applications.d.ts +8 -6
  77. package/dist/src/resources/applications.js +21 -21
  78. package/dist/src/resources/base.d.ts +4 -0
  79. package/dist/src/resources/base.js +15 -0
  80. package/dist/src/resources/bundles.d.ts +9 -4
  81. package/dist/src/resources/bundles.js +10 -5
  82. package/dist/src/resources/connections.d.ts +78 -1
  83. package/dist/src/resources/connections.js +132 -1
  84. package/dist/src/resources/data-collections.d.ts +118 -0
  85. package/dist/src/resources/data-collections.js +49 -0
  86. package/dist/src/resources/datasets.d.ts +69 -0
  87. package/dist/src/resources/datasets.js +99 -1
  88. package/dist/src/resources/folders.js +2 -1
  89. package/dist/src/resources/futures.d.ts +4 -1
  90. package/dist/src/resources/futures.js +38 -6
  91. package/dist/src/resources/groups.d.ts +47 -0
  92. package/dist/src/resources/groups.js +56 -0
  93. package/dist/src/resources/jobs.d.ts +8 -13
  94. package/dist/src/resources/jobs.js +41 -16
  95. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  96. package/dist/src/resources/knowledge-banks.js +61 -0
  97. package/dist/src/resources/llms.d.ts +92 -0
  98. package/dist/src/resources/llms.js +62 -0
  99. package/dist/src/resources/macros.d.ts +121 -0
  100. package/dist/src/resources/macros.js +231 -0
  101. package/dist/src/resources/ml-tasks.d.ts +37 -2
  102. package/dist/src/resources/ml-tasks.js +100 -20
  103. package/dist/src/resources/model-evaluation-stores.js +1 -8
  104. package/dist/src/resources/notebooks.d.ts +43 -7
  105. package/dist/src/resources/notebooks.js +53 -13
  106. package/dist/src/resources/plugins.d.ts +265 -0
  107. package/dist/src/resources/plugins.js +458 -0
  108. package/dist/src/resources/project-folders.d.ts +71 -0
  109. package/dist/src/resources/project-folders.js +57 -0
  110. package/dist/src/resources/project-git.d.ts +6 -0
  111. package/dist/src/resources/project-git.js +69 -42
  112. package/dist/src/resources/project-library.d.ts +0 -1
  113. package/dist/src/resources/project-library.js +1 -17
  114. package/dist/src/resources/projects.d.ts +19 -3
  115. package/dist/src/resources/projects.js +30 -18
  116. package/dist/src/resources/recipes.d.ts +15 -1
  117. package/dist/src/resources/recipes.js +73 -6
  118. package/dist/src/resources/saved-models.d.ts +134 -1
  119. package/dist/src/resources/saved-models.js +214 -6
  120. package/dist/src/resources/scenarios.d.ts +105 -0
  121. package/dist/src/resources/scenarios.js +232 -106
  122. package/dist/src/resources/sql.js +5 -1
  123. package/dist/src/resources/users.d.ts +129 -0
  124. package/dist/src/resources/users.js +128 -0
  125. package/dist/src/resources/webapps.d.ts +15 -2
  126. package/dist/src/resources/webapps.js +18 -4
  127. package/dist/src/schemas.d.ts +2 -2
  128. package/dist/src/schemas.js +1 -1
  129. package/dist/src/skill.d.ts +13 -4
  130. package/dist/src/skill.js +75 -24
  131. package/dist/src/utils/deep-merge.d.ts +1 -1
  132. package/dist/src/utils/polling.d.ts +20 -0
  133. package/dist/src/utils/polling.js +27 -0
  134. package/dist/src/utils/response-file.d.ts +2 -0
  135. package/dist/src/utils/response-file.js +34 -0
  136. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  137. package/dist/src/utils/secret-sanitize.js +101 -0
  138. package/package.json +8 -5
  139. package/packages/types/dist/index.d.ts +102 -0
  140. package/packages/types/dist/index.js +76 -0
  141. package/plugin.json +1 -1
  142. package/skills/dataiku-dss/SKILL.md +47 -225
  143. package/skills/dataiku-dss/references/app-releases.md +28 -0
  144. package/skills/dataiku-dss/references/authentication.md +39 -0
  145. package/skills/dataiku-dss/references/coding.md +8 -0
  146. package/skills/dataiku-dss/references/discovery.md +30 -0
  147. package/skills/dataiku-dss/references/flow-maps.md +12 -0
  148. package/skills/dataiku-dss/references/mutations.md +33 -0
  149. package/skills/dataiku-dss/references/troubleshooting.md +29 -0
@@ -0,0 +1,2 @@
1
+ import type { CommandMeta } from "../types.js";
2
+ export declare const projectFolderCommands: Record<string, CommandMeta>;
@@ -0,0 +1,197 @@
1
+ import { requiredJsonInput, } from "../coerce.js";
2
+ import { executionMode, } from "../flags.js";
3
+ import { isNotFoundError, planResult, skipResult, } from "../output.js";
4
+ import { requireArgs, UsageError, } from "../usage.js";
5
+ const PLAN_FAILURE_CODES = { usage: 1, error: 2, transient: 3, };
6
+ function folderEndpoint(folderId, suffix = "") {
7
+ return `/public/api/project-folders/${encodeURIComponent(folderId)}${suffix}`;
8
+ }
9
+ /**
10
+ * Positional ids are server keys: an empty string would silently target the
11
+ * parent collection (e.g. DELETE /public/api/project-folders/ = the root
12
+ * folder) instead of failing as a usage error. Runs for live, --plan, and
13
+ * --dry-run alike with zero network.
14
+ */
15
+ function nonEmptyPositionals(args, names, usage) {
16
+ args.forEach((value, index) => {
17
+ if (value.trim().length === 0) {
18
+ throw new UsageError(`<${names[index] ?? `arg${String(index + 1)}`}> must be a non-empty identifier. Usage: ${usage}`);
19
+ }
20
+ });
21
+ }
22
+ const GET_USAGE = "dss project-folder get <folderId>";
23
+ const SETTINGS_GET_USAGE = "dss project-folder settings-get <folderId>";
24
+ const SETTINGS_SET_USAGE = "dss project-folder settings-set <folderId> (--data JSON|--data-file PATH|--stdin) [--dry-run]";
25
+ const MOVE_USAGE = "dss project-folder move <folderId> <destinationFolderId> [--dry-run]";
26
+ const DELETE_USAGE = "dss project-folder delete <folderId> [--if-exists] [--dry-run]";
27
+ const CREATE_CHILD_USAGE = "dss project-folder create-child <parentFolderId> --name NAME [--dry-run]";
28
+ const MOVE_PROJECT_USAGE = "dss project-folder move-project <folderId> <projectKey> <destinationFolderId> [--dry-run]";
29
+ export const projectFolderCommands = {
30
+ root: {
31
+ handler: (c) => c.projectFolders.root(),
32
+ usage: "dss project-folder root",
33
+ description: "Get the root project folder definition (visible children and projects).",
34
+ examples: ["dss project-folder root",],
35
+ },
36
+ get: {
37
+ validate: (a) => {
38
+ requireArgs(a, 1, GET_USAGE);
39
+ nonEmptyPositionals(a, ["folderId",], GET_USAGE);
40
+ },
41
+ handler: (c, a) => c.projectFolders.get(a[0]),
42
+ usage: GET_USAGE,
43
+ description: "Get a project folder definition (children and project keys).",
44
+ examples: ["dss project-folder get KdLmPU6",],
45
+ },
46
+ "settings-get": {
47
+ validate: (a) => {
48
+ requireArgs(a, 1, SETTINGS_GET_USAGE);
49
+ nonEmptyPositionals(a, ["folderId",], SETTINGS_GET_USAGE);
50
+ },
51
+ handler: (c, a) => c.projectFolders.getSettings(a[0]),
52
+ usage: SETTINGS_GET_USAGE,
53
+ description: "Get project folder settings (name, owner, permissions). Admin required.",
54
+ examples: ["dss project-folder settings-get KdLmPU6",],
55
+ },
56
+ "settings-set": {
57
+ validate: (a) => {
58
+ requireArgs(a, 1, SETTINGS_SET_USAGE);
59
+ nonEmptyPositionals(a, ["folderId",], SETTINGS_SET_USAGE);
60
+ },
61
+ handler: async (c, a, f) => {
62
+ const settings = requiredJsonInput(f, "--data, --data-file, or --stdin is required (project folder settings).");
63
+ if (executionMode(f).dryRun) {
64
+ return planResult("project-folder", "settings-set", {
65
+ asyncKind: "none",
66
+ method: "PUT",
67
+ endpoint: folderEndpoint(a[0], "/settings"),
68
+ identifiers: { folderId: a[0], },
69
+ payload: settings,
70
+ idempotency: "none",
71
+ exitCodesOnFailure: PLAN_FAILURE_CODES,
72
+ plannedAndDryRun: true,
73
+ });
74
+ }
75
+ await c.projectFolders.updateSettings(a[0], settings);
76
+ return { updated: a[0], resource: "project-folder", };
77
+ },
78
+ usage: SETTINGS_SET_USAGE,
79
+ description: "Replace project folder settings (name, owner, permissions). Admin required.",
80
+ examples: [
81
+ "dss project-folder settings-set KdLmPU6 --data-file settings.json --dry-run",
82
+ ],
83
+ },
84
+ move: {
85
+ validate: (a) => {
86
+ requireArgs(a, 2, MOVE_USAGE);
87
+ nonEmptyPositionals(a, ["folderId", "destinationFolderId",], MOVE_USAGE);
88
+ },
89
+ handler: async (c, a, f) => {
90
+ if (executionMode(f).dryRun) {
91
+ return planResult("project-folder", "move", {
92
+ asyncKind: "none",
93
+ method: "POST",
94
+ endpoint: `${folderEndpoint(a[0], "/move")}?destination=${encodeURIComponent(a[1])}`,
95
+ identifiers: { folderId: a[0], destination: a[1], },
96
+ idempotency: "none",
97
+ exitCodesOnFailure: PLAN_FAILURE_CODES,
98
+ plannedAndDryRun: true,
99
+ });
100
+ }
101
+ await c.projectFolders.move(a[0], a[1]);
102
+ return { moved: a[0], destination: a[1], resource: "project-folder", };
103
+ },
104
+ usage: MOVE_USAGE,
105
+ description: "Move a project folder (and its content) into another project folder. Cannot move into a sub-folder of itself.",
106
+ examples: ["dss project-folder move KdLmPU6 dgKywsx",],
107
+ },
108
+ delete: {
109
+ validate: (a) => {
110
+ requireArgs(a, 1, DELETE_USAGE);
111
+ nonEmptyPositionals(a, ["folderId",], DELETE_USAGE);
112
+ },
113
+ handler: async (c, a, f) => {
114
+ // Dry-run must be a validated plan with zero HTTP: no existence GET.
115
+ if (executionMode(f).dryRun) {
116
+ return planResult("project-folder", "delete", {
117
+ asyncKind: "none",
118
+ method: "DELETE",
119
+ endpoint: folderEndpoint(a[0]),
120
+ identifiers: { folderId: a[0], },
121
+ idempotency: "if-exists",
122
+ exitCodesOnFailure: PLAN_FAILURE_CODES,
123
+ plannedAndDryRun: true,
124
+ });
125
+ }
126
+ // --if-exists (live only) pays one existence GET; a 404 becomes a skip.
127
+ if (f["if-exists"] === true) {
128
+ const current = await c.projectFolders.get(a[0]).catch((error) => {
129
+ if (isNotFoundError(error))
130
+ return undefined;
131
+ throw error;
132
+ });
133
+ if (!current)
134
+ return skipResult("project-folder", a[0], "missing");
135
+ }
136
+ await c.projectFolders.delete(a[0]);
137
+ return { deleted: a[0], resource: "project-folder", };
138
+ },
139
+ usage: DELETE_USAGE,
140
+ description: "Delete an empty project folder (no sub-folders, no projects). Admin required.",
141
+ examples: ["dss project-folder delete KdLmPU6 --dry-run",],
142
+ },
143
+ "create-child": {
144
+ validate: (a, f) => {
145
+ const name = f["name"];
146
+ if (typeof name === "string" && name.trim().length === 0) {
147
+ throw new UsageError(`--name must be a non-empty folder name. Usage: ${CREATE_CHILD_USAGE}`);
148
+ }
149
+ if (!a[0] || typeof name !== "string" || name.length === 0) {
150
+ throw new UsageError(`Usage: ${CREATE_CHILD_USAGE}`);
151
+ }
152
+ },
153
+ handler: async (c, a, f) => {
154
+ const name = f["name"];
155
+ if (executionMode(f).dryRun) {
156
+ return planResult("project-folder", "create-child", {
157
+ asyncKind: "none",
158
+ method: "POST",
159
+ endpoint: `${folderEndpoint(a[0], "/children")}?name=${encodeURIComponent(name)}`,
160
+ identifiers: { parentFolderId: a[0], name, },
161
+ idempotency: "none",
162
+ exitCodesOnFailure: PLAN_FAILURE_CODES,
163
+ plannedAndDryRun: true,
164
+ });
165
+ }
166
+ const created = await c.projectFolders.createChild(a[0], name);
167
+ return { created: created.id ?? name, resource: "project-folder", ...created, };
168
+ },
169
+ usage: CREATE_CHILD_USAGE,
170
+ description: "Create a sub-project folder. WRITE_CONTENTS required on the parent.",
171
+ examples: ["dss project-folder create-child KdLmPU6 --name my_sub_folder",],
172
+ },
173
+ "move-project": {
174
+ validate: (a) => {
175
+ requireArgs(a, 3, MOVE_PROJECT_USAGE);
176
+ nonEmptyPositionals(a, ["folderId", "projectKey", "destinationFolderId",], MOVE_PROJECT_USAGE);
177
+ },
178
+ handler: async (c, a, f) => {
179
+ if (executionMode(f).dryRun) {
180
+ return planResult("project-folder", "move-project", {
181
+ asyncKind: "none",
182
+ method: "POST",
183
+ endpoint: `${folderEndpoint(a[0], `/projects/${encodeURIComponent(a[1])}/move`)}?destination=${encodeURIComponent(a[2])}`,
184
+ identifiers: { folderId: a[0], projectKey: a[1], destination: a[2], },
185
+ idempotency: "none",
186
+ exitCodesOnFailure: PLAN_FAILURE_CODES,
187
+ plannedAndDryRun: true,
188
+ });
189
+ }
190
+ await c.projectFolders.moveProject(a[0], a[1], a[2]);
191
+ return { moved: a[1], destination: a[2], resource: "project-folder", };
192
+ },
193
+ usage: MOVE_PROJECT_USAGE,
194
+ description: "Move a project from its project folder to another project folder. WRITE_CONTENTS on destination and project admin required.",
195
+ examples: ["dss project-folder move-project KdLmPU6 MYPROJECT dgKywsx",],
196
+ },
197
+ };
@@ -1,4 +1,5 @@
1
1
  import { validateGitReferencePath, } from "../../resources/project-git.js";
2
+ import { sanitizeErrorSecrets, sanitizeSecrets, } from "../../utils/secret-sanitize.js";
2
3
  import { num, parseBooleanOption, requiredStringFlag, } from "../coerce.js";
3
4
  import { CommandResultFailure, } from "../output.js";
4
5
  import { requireArgs, requireNoArgs, UsageError, } from "../usage.js";
@@ -28,48 +29,17 @@ const SENSITIVE_GIT_OUTPUT_KEYS = {
28
29
  password: true,
29
30
  token: true,
30
31
  };
32
+ const GIT_SANITIZE_OPTIONS = {
33
+ sensitiveKeys: SENSITIVE_GIT_OUTPUT_KEYS,
34
+ redactUrlUserinfo: true,
35
+ };
31
36
  /**
32
37
  * DSS may already contain legacy remotes with credentials in the URL, and Git
33
38
  * error text can echo them. Sanitize every server-derived Project Git value
34
39
  * before it reaches stdout or a structured command failure.
35
40
  */
36
- function redactGitUrlUserinfo(text) {
37
- return text.replace(/https?:[\\/]{1,2}[^\s"'<>]+/giu, (url) => {
38
- const schemeEnd = url.indexOf(":") + 1;
39
- let authorityStart = schemeEnd;
40
- while (url[authorityStart] === "/" || url[authorityStart] === "\\")
41
- authorityStart++;
42
- const slash = url.slice(authorityStart).search(/[\\/]/u);
43
- const authorityEnd = slash === -1 ? url.length : authorityStart + slash;
44
- const at = url.lastIndexOf("@", authorityEnd - 1);
45
- if (at < authorityStart)
46
- return url;
47
- return `${url.slice(0, authorityStart)}[redacted]@${url.slice(at + 1)}`;
48
- });
49
- }
50
41
  function sanitizeProjectGitOutput(value, secrets = []) {
51
- if (typeof value === "string") {
52
- let sanitized = redactGitUrlUserinfo(value);
53
- for (const secret of secrets) {
54
- if (secret !== "")
55
- sanitized = sanitized.split(secret).join("[redacted]");
56
- }
57
- return sanitized;
58
- }
59
- if (Array.isArray(value)) {
60
- return value.map((item) => sanitizeProjectGitOutput(item, secrets));
61
- }
62
- if (value !== null && typeof value === "object") {
63
- const sanitized = {};
64
- for (const [key, item,] of Object.entries(value)) {
65
- const normalizedKey = key.replace(/[-_]/g, "").toLowerCase();
66
- sanitized[key] = SENSITIVE_GIT_OUTPUT_KEYS[normalizedKey] === true
67
- ? "[redacted]"
68
- : sanitizeProjectGitOutput(item, secrets);
69
- }
70
- return sanitized;
71
- }
72
- return value;
42
+ return sanitizeSecrets(value, { ...GIT_SANITIZE_OPTIONS, secrets, });
73
43
  }
74
44
  /**
75
45
  * Most Git actions answer HTTP 200 with `{success: false}` and the Git stderr
@@ -84,16 +54,7 @@ function assertGitActionSucceeded(result, action) {
84
54
  return sanitized;
85
55
  }
86
56
  function sanitizeProjectGitError(error) {
87
- if (!(error instanceof Error))
88
- return error;
89
- error.message = sanitizeProjectGitOutput(error.message);
90
- if (error.stack !== undefined)
91
- error.stack = sanitizeProjectGitOutput(error.stack);
92
- const errorWithBody = error;
93
- if (typeof errorWithBody.body === "string") {
94
- errorWithBody.body = sanitizeProjectGitOutput(errorWithBody.body);
95
- }
96
- return error;
57
+ return sanitizeErrorSecrets(error, { ...GIT_SANITIZE_OPTIONS, });
97
58
  }
98
59
  /**
99
60
  * External library credentials are read only from the environment variable
@@ -533,17 +494,16 @@ export const projectGitCommands = {
533
494
  handler: async (c, a, f) => {
534
495
  requireArgs(a, 1, FUTURE_WAIT_USAGE);
535
496
  try {
536
- const result = sanitizeProjectGitOutput(await c.projectGit.waitForFuture(a[0], {
497
+ // The official Python client returns the future result verbatim
498
+ // (wait_for_result never inspects result fields): a completed
499
+ // future's payload is caller-interpreted data, and a
500
+ // {success:false} bag from DSS means "no diagnostics to show",
501
+ // not an operation failure. Only the git ACTION endpoints carry
502
+ // the {success:false} = failed convention.
503
+ return sanitizeProjectGitOutput(await c.projectGit.waitForFuture(a[0], {
537
504
  pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
538
505
  timeoutMs: num(f["timeout"], "--timeout"),
539
506
  }));
540
- if (result !== null
541
- && typeof result === "object"
542
- && !Array.isArray(result)
543
- && result.success === false) {
544
- return assertGitActionSucceeded(result, "future-wait");
545
- }
546
- return result;
547
507
  }
548
508
  catch (error) {
549
509
  throw sanitizeProjectGitError(error);
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import { encodeLibraryPath, EXPECT_SHA256_PATTERN, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../../resources/project-library.js";
3
3
  import { numFlag, readStdinText, sha256Hex, } from "../coerce.js";
4
+ import { executionMode, } from "../flags.js";
4
5
  import { encodedProjectEndpoint, planResult, skipResult, } from "../output.js";
5
6
  import { requireArgs, UsageError, } from "../usage.js";
6
7
  const PROJECT_LIBRARY_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
@@ -100,7 +101,7 @@ export const projectLibraryCommands = {
100
101
  requireArgs(a, 1, "dss project-library create-file <path> [--if-not-exists] [--dry-run] [--project-key KEY]");
101
102
  const pk = f["project-key"];
102
103
  validateLibraryPath(a[0]);
103
- if (f["dry-run"] === true) {
104
+ if (executionMode(f).dryRun) {
104
105
  return projectLibraryPlan("create-file", {
105
106
  method: "POST",
106
107
  endpoint: projectLibraryContentsEndpoint(c, pk, a[0]),
@@ -126,7 +127,7 @@ export const projectLibraryCommands = {
126
127
  requireArgs(a, 1, "dss project-library create-folder <path> [--if-not-exists] [--dry-run] [--project-key KEY]");
127
128
  const pk = f["project-key"];
128
129
  validateLibraryPath(a[0]);
129
- if (f["dry-run"] === true) {
130
+ if (executionMode(f).dryRun) {
130
131
  return projectLibraryPlan("create-folder", {
131
132
  method: "POST",
132
133
  endpoint: projectLibraryFolderEndpoint(c, pk, a[0]),
@@ -153,7 +154,7 @@ export const projectLibraryCommands = {
153
154
  const pk = f["project-key"];
154
155
  const expectSha256 = expectSha256FromFlags(f);
155
156
  validateLibraryPath(a[0]);
156
- if (f["dry-run"] === true) {
157
+ if (executionMode(f).dryRun) {
157
158
  return projectLibraryPlan("put", {
158
159
  method: "POST",
159
160
  endpoint: projectLibraryContentsEndpoint(c, pk, a[0]),
@@ -206,7 +207,7 @@ export const projectLibraryCommands = {
206
207
  requireArgs(a, 1, "dss project-library delete <path> [--dry-run] [--project-key KEY]");
207
208
  const pk = f["project-key"];
208
209
  validateLibraryPath(a[0]);
209
- if (f["dry-run"] === true) {
210
+ if (executionMode(f).dryRun) {
210
211
  return projectLibraryPlan("delete", {
211
212
  method: "DELETE",
212
213
  endpoint: projectLibraryContentsEndpoint(c, pk, a[0]),
@@ -226,7 +227,7 @@ export const projectLibraryCommands = {
226
227
  const pk = f["project-key"];
227
228
  const validPath = validateLibraryPath(a[0]);
228
229
  const validName = validateLibraryName(a[1]);
229
- if (f["dry-run"] === true) {
230
+ if (executionMode(f).dryRun) {
230
231
  return projectLibraryPlan("rename", {
231
232
  method: "POST",
232
233
  endpoint: projectLibraryActionEndpoint(c, pk, "rename/"),
@@ -247,7 +248,7 @@ export const projectLibraryCommands = {
247
248
  const pk = f["project-key"];
248
249
  const validPath = validateLibraryPath(a[0]);
249
250
  const destination = validateLibraryDestinationPath(a[1]);
250
- if (f["dry-run"] === true) {
251
+ if (executionMode(f).dryRun) {
251
252
  return projectLibraryPlan("move", {
252
253
  method: "POST",
253
254
  endpoint: projectLibraryActionEndpoint(c, pk, "move"),
@@ -3,6 +3,7 @@ import { deepMerge, } from "../../utils/deep-merge.js";
3
3
  import { inspectProjectArchive, } from "../../utils/project-archive.js";
4
4
  import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
5
5
  import { jsonInput, num, requiredJsonInput, requiredStringFlag, } from "../coerce.js";
6
+ import { executionMode, } from "../flags.js";
6
7
  import { CommandResultFailure, } from "../output.js";
7
8
  import { requireArgs, UsageError, } from "../usage.js";
8
9
  const INSPECT_ARCHIVE_USAGE = "dss project inspect-archive <file>";
@@ -91,7 +92,7 @@ export const projectCommands = {
91
92
  throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
92
93
  }
93
94
  const guarded = f["if-exists"] === true
94
- || f["dry-run"] === true
95
+ || executionMode(f).dryRun
95
96
  || expectedIncarnation !== undefined;
96
97
  if (!guarded) {
97
98
  // Purely user-requested delete without any guard flag: keep the
@@ -128,7 +129,7 @@ export const projectCommands = {
128
129
  }
129
130
  throw error;
130
131
  }
131
- if (f["dry-run"] === true) {
132
+ if (executionMode(f).dryRun) {
132
133
  return {
133
134
  deleted: false,
134
135
  dryRun: true,
@@ -213,12 +214,6 @@ export const projectCommands = {
213
214
  description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues, and the source project key. Never contacts DSS.",
214
215
  examples: ["dss project inspect-archive ./my_proj.zip",],
215
216
  },
216
- "permissions-get": {
217
- handler: (c, _a, f) => c.projects.getPermissions(f["project-key"]),
218
- usage: "dss project permissions-get [--project-key KEY]",
219
- description: "Get a project's permissions.",
220
- examples: ["dss project permissions-get --project-key MY_PROJ",],
221
- },
222
217
  "permissions-set": {
223
218
  handler: async (c, _a, f) => {
224
219
  const body = requiredJsonInput(f, "--data, --data-file, or --stdin is required (permissions object).");
@@ -229,6 +224,58 @@ export const projectCommands = {
229
224
  description: "Replace a project's permissions.",
230
225
  examples: ["dss project permissions-set --data-file perms.json --project-key MY_PROJ",],
231
226
  },
227
+ "permissions-get": {
228
+ handler: (c, _a, f) => c.projects.getPermissions(f["project-key"]),
229
+ usage: "dss project permissions-get [--project-key KEY]",
230
+ description: "Get a project's permissions.",
231
+ examples: ["dss project permissions-get --project-key MY_PROJ",],
232
+ },
233
+ "metadata-set": {
234
+ handler: async (c, _a, f) => {
235
+ const metadata = requiredJsonInput(f, "--data, --data-file, or --stdin is required (full metadata object).");
236
+ if (executionMode(f).dryRun) {
237
+ return {
238
+ dryRun: true,
239
+ action: "metadata-set",
240
+ resource: "project",
241
+ next: metadata,
242
+ };
243
+ }
244
+ await c.projects.setMetadata(f["project-key"], metadata);
245
+ return { updated: true, };
246
+ },
247
+ usage: "dss project metadata-set (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
248
+ description: "Replace project metadata with a full object obtained from a previous metadata GET. Fields absent from the payload are removed.",
249
+ examples: [
250
+ "dss project metadata-set --data-file metadata.json --project-key MY_PROJ --dry-run",
251
+ ],
252
+ },
253
+ "tags-get": {
254
+ handler: (c, _a, f) => c.projects.tags(f["project-key"]),
255
+ usage: "dss project tags-get [--project-key KEY]",
256
+ description: "Get project-level tags as a dictionary of tag name to display settings.",
257
+ examples: ["dss project tags-get", "dss project tags-get --project-key MY_PROJ",],
258
+ },
259
+ "tags-set": {
260
+ handler: async (c, _a, f) => {
261
+ const tags = requiredJsonInput(f, "--data, --data-file, or --stdin is required (full tags object).");
262
+ if (executionMode(f).dryRun) {
263
+ return {
264
+ dryRun: true,
265
+ action: "tags-set",
266
+ resource: "project",
267
+ next: tags,
268
+ };
269
+ }
270
+ await c.projects.setTags(f["project-key"], tags);
271
+ return { updated: true, };
272
+ },
273
+ usage: "dss project tags-set (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
274
+ description: "Replace project tags with a full object obtained from a previous tags GET. Tags absent from the payload are removed.",
275
+ examples: [
276
+ 'dss project tags-set --data \'{"tags":{"production":{"color":"#28aadd"}}}\' --project-key MY_PROJ --dry-run',
277
+ ],
278
+ },
232
279
  "settings-get": {
233
280
  handler: (c, _a, f) => c.projects.getSettings(f["project-key"]),
234
281
  usage: "dss project settings-get [--project-key KEY]",
@@ -2,7 +2,8 @@ import { readFileSync, } from "node:fs";
2
2
  import { writeFile, } from "node:fs/promises";
3
3
  import { join, } from "node:path";
4
4
  import { deepMerge, } from "../../utils/deep-merge.js";
5
- import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, recipeInputDatasetsFromFlags, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
5
+ import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
6
+ import { executionMode, } from "../flags.js";
6
7
  import { moveCreatedItemsToZone, resolveFlowZoneIdFromFlags, } from "../helpers/flow-zone.js";
7
8
  import { ensureRecipeBackupDir, readRecipeBackup, recipeBackupDocument, recipeBackupPath, recipeCodeEnv, recipeGraph, recipeInputItemRef, recipeRoleInputItems, recipeRunShouldWait, writeRecipeBackup, } from "../helpers/recipe.js";
8
9
  import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
@@ -65,6 +66,41 @@ export const recipeCommands = {
65
66
  description: "List all recipes in a project.",
66
67
  examples: ["dss recipe list",],
67
68
  },
69
+ metadata: {
70
+ handler: (c, a, f) => {
71
+ requireArgs(a, 1, "dss recipe metadata <name>");
72
+ return c.recipes.metadata(a[0], {
73
+ projectKey: f["project-key"],
74
+ });
75
+ },
76
+ usage: "dss recipe metadata <name> [--project-key KEY]",
77
+ description: "Get recipe metadata (label, description, tags, custom fields).",
78
+ examples: ["dss recipe metadata compute_orders",],
79
+ },
80
+ "metadata-set": {
81
+ handler: async (c, a, f) => {
82
+ const usage = "dss recipe metadata-set <name> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]";
83
+ requireArgs(a, 1, usage);
84
+ const metadata = requiredJsonInput(f, "--data, --data-file, or --stdin is required (full metadata object).");
85
+ const pk = f["project-key"];
86
+ if (executionMode(f).dryRun) {
87
+ return {
88
+ dryRun: true,
89
+ action: "metadata-set",
90
+ resource: "recipe",
91
+ name: a[0],
92
+ next: metadata,
93
+ };
94
+ }
95
+ await c.recipes.setMetadata(a[0], metadata, { projectKey: pk, });
96
+ return { updated: a[0], resource: "recipe", };
97
+ },
98
+ usage: "dss recipe metadata-set <name> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
99
+ description: "Replace recipe metadata with a full object obtained from a previous metadata GET. Fields absent from the payload are removed.",
100
+ examples: [
101
+ 'dss recipe metadata-set compute_orders --data \'{"label":"Orders"}\' --dry-run',
102
+ ],
103
+ },
68
104
  get: {
69
105
  handler: (c, a, f) => {
70
106
  requireArgs(a, 1, "dss recipe get <name>");
@@ -109,7 +145,7 @@ export const recipeCommands = {
109
145
  summary: f["summary"] === true,
110
146
  wait,
111
147
  };
112
- if (f["dry-run"] === true) {
148
+ if (executionMode(f).dryRun) {
113
149
  const outputs = await c.recipes.resolveRunOutputs(a[0], {
114
150
  partition: options.partition,
115
151
  projectKey: pk,
@@ -139,11 +175,11 @@ export const recipeCommands = {
139
175
  handler: async (c, a, f) => {
140
176
  requireArgs(a, 1, "dss recipe delete <name>");
141
177
  const pk = f["project-key"];
142
- if (f["dry-run"] === true || f["if-exists"] === true) {
178
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
143
179
  const current = await readIfExists(() => c.recipes.get(a[0], { projectKey: pk, includePayload: true, }));
144
180
  if (!current)
145
181
  return skipResult("recipe", a[0], "missing");
146
- if (f["dry-run"] === true) {
182
+ if (executionMode(f).dryRun) {
147
183
  return { dryRun: true, action: "delete", resource: "recipe", name: a[0], current, };
148
184
  }
149
185
  }
@@ -241,13 +277,13 @@ export const recipeCommands = {
241
277
  const zoneMove = zoneId && name
242
278
  ? [{ objectId: name, objectType: "RECIPE", },]
243
279
  : undefined;
244
- if ((f["if-not-exists"] === true || f["dry-run"] === true) && name) {
280
+ if ((f["if-not-exists"] === true || executionMode(f).dryRun) && name) {
245
281
  const list = await c.recipes.list(pk);
246
282
  const existing = list.find((r) => r.name === name);
247
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
283
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
248
284
  return skipResult("recipe", name, "exists", { current: existing, });
249
285
  }
250
- if (f["dry-run"] === true) {
286
+ if (executionMode(f).dryRun) {
251
287
  return {
252
288
  dryRun: true,
253
289
  action: "create",
@@ -259,7 +295,7 @@ export const recipeCommands = {
259
295
  };
260
296
  }
261
297
  }
262
- if (f["dry-run"] === true) {
298
+ if (executionMode(f).dryRun) {
263
299
  return {
264
300
  dryRun: true,
265
301
  action: "create",
@@ -328,7 +364,7 @@ export const recipeCommands = {
328
364
  throw new UsageError("Cannot reuse --path or --metastore-table for multiple cloned output datasets.", "invalid_enum");
329
365
  }
330
366
  const zoneId = await resolveFlowZoneIdFromFlags(c, f, pk);
331
- if (f["dry-run"] === true) {
367
+ if (executionMode(f).dryRun) {
332
368
  return {
333
369
  dryRun: true,
334
370
  action: "clone",
@@ -391,7 +427,7 @@ export const recipeCommands = {
391
427
  throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss recipe update <name> [--data '{...}' | --data-file PATH | --stdin]");
392
428
  }
393
429
  const pk = f["project-key"];
394
- if (f["dry-run"] === true) {
430
+ if (executionMode(f).dryRun) {
395
431
  const current = await c.recipes.get(a[0], { projectKey: pk, includePayload: true, });
396
432
  const currentRecipe = current.recipe;
397
433
  const next = {
@@ -430,7 +466,7 @@ export const recipeCommands = {
430
466
  }
431
467
  const nextItems = [...items, { ref: a[1], deps: [], },];
432
468
  const inputs = nextItems.map(recipeInputItemRef).filter((ref) => Boolean(ref));
433
- if (f["dry-run"] === true) {
469
+ if (executionMode(f).dryRun) {
434
470
  return {
435
471
  dryRun: true,
436
472
  action: "add-input",
@@ -467,7 +503,7 @@ export const recipeCommands = {
467
503
  }
468
504
  const nextItems = items.filter((item) => recipeInputItemRef(item) !== a[1]);
469
505
  const inputs = nextItems.map(recipeInputItemRef).filter((ref) => Boolean(ref));
470
- if (f["dry-run"] === true) {
506
+ if (executionMode(f).dryRun) {
471
507
  return {
472
508
  dryRun: true,
473
509
  action: "remove-input",
@@ -542,7 +578,7 @@ export const recipeCommands = {
542
578
  projectKey: pk,
543
579
  includePayload: true,
544
580
  });
545
- if (f["dry-run"] === true) {
581
+ if (executionMode(f).dryRun) {
546
582
  return {
547
583
  dryRun: true,
548
584
  action: "set-payload",
@@ -593,7 +629,7 @@ export const recipeCommands = {
593
629
  const next = f["payload-only"] === true || !restoredRecipe
594
630
  ? { ...current, payload, }
595
631
  : { ...current, recipe: restoredRecipe, payload, };
596
- if (f["dry-run"] === true) {
632
+ if (executionMode(f).dryRun) {
597
633
  return {
598
634
  dryRun: true,
599
635
  action: "restore",
@@ -1,2 +1,7 @@
1
1
  import type { CommandMeta } from "../types.js";
2
+ /**
3
+ * Mutating actions accept --plan (buildMutationPlan in contract.ts owns the
4
+ * --plan path centrally); the handlers below keep --dry-run support with a
5
+ * zero-request planned payload, mirroring set-active/delete.
6
+ */
2
7
  export declare const savedModelCommands: Record<string, CommandMeta>;