dataiku-sdk 0.12.7 → 1.0.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 (51) hide show
  1. package/README.md +60 -14
  2. package/bin/dss.js +1 -1
  3. package/dist/packages/types/src/index.d.ts +128 -3
  4. package/dist/packages/types/src/index.js +106 -3
  5. package/dist/src/cli/coerce.d.ts +2 -1
  6. package/dist/src/cli/coerce.js +84 -26
  7. package/dist/src/cli/commands/app.js +6 -6
  8. package/dist/src/cli/commands/code.js +2 -2
  9. package/dist/src/cli/commands/data-quality.js +8 -8
  10. package/dist/src/cli/commands/dataset.js +11 -7
  11. package/dist/src/cli/commands/folder.js +5 -4
  12. package/dist/src/cli/commands/future.js +2 -2
  13. package/dist/src/cli/commands/index.js +2 -0
  14. package/dist/src/cli/commands/job.js +11 -11
  15. package/dist/src/cli/commands/notebook.js +1 -1
  16. package/dist/src/cli/commands/project-git.d.ts +2 -0
  17. package/dist/src/cli/commands/project-git.js +563 -0
  18. package/dist/src/cli/commands/project.js +2 -2
  19. package/dist/src/cli/commands/recipe.js +14 -17
  20. package/dist/src/cli/commands/scenario.js +4 -4
  21. package/dist/src/cli/contract.d.ts +5 -4
  22. package/dist/src/cli/contract.js +413 -55
  23. package/dist/src/cli/doctor.js +6 -6
  24. package/dist/src/cli/flags.js +21 -2
  25. package/dist/src/cli/helpers/dataset.js +1 -1
  26. package/dist/src/cli/output.d.ts +2 -1
  27. package/dist/src/cli/output.js +2 -6
  28. package/dist/src/cli/usage.d.ts +1 -1
  29. package/dist/src/cli/usage.js +2 -1
  30. package/dist/src/cli/version.d.ts +2 -2
  31. package/dist/src/cli/version.js +2 -2
  32. package/dist/src/cli.js +43 -26
  33. package/dist/src/client.d.ts +3 -0
  34. package/dist/src/client.js +5 -0
  35. package/dist/src/errors.d.ts +1 -1
  36. package/dist/src/index.d.ts +3 -2
  37. package/dist/src/index.js +2 -1
  38. package/dist/src/resources/connections.d.ts +3 -1
  39. package/dist/src/resources/connections.js +82 -21
  40. package/dist/src/resources/datasets.js +42 -2
  41. package/dist/src/resources/folders.js +7 -3
  42. package/dist/src/resources/project-git.d.ts +154 -0
  43. package/dist/src/resources/project-git.js +427 -0
  44. package/dist/src/resources/recipes.js +4 -4
  45. package/dist/src/schemas.d.ts +2 -2
  46. package/dist/src/schemas.js +1 -1
  47. package/package.json +1 -1
  48. package/packages/types/dist/index.d.ts +128 -3
  49. package/packages/types/dist/index.js +106 -3
  50. package/plugin.json +1 -1
  51. package/skills/dataiku-dss/SKILL.md +26 -19
@@ -0,0 +1,563 @@
1
+ import { num, parseBooleanOption, requiredStringFlag, } from "../coerce.js";
2
+ import { CommandResultFailure, } from "../output.js";
3
+ import { requireArgs, requireNoArgs, UsageError, } from "../usage.js";
4
+ /** DSS names the default remote `origin`; the remote flag falls back to it. */
5
+ const DEFAULT_REMOTE = "origin";
6
+ /**
7
+ * A Git action DSS refused is a command-level failure, not a transport failure,
8
+ * so it exits with the generic error code rather than a transient one.
9
+ */
10
+ const GIT_ACTION_FAILURE_EXIT_CODE = 2;
11
+ /**
12
+ * Reject a flag passed without a usable value, so an empty `--commit` never
13
+ * silently degrades into "no commit filter" and changes what the call means.
14
+ */
15
+ function optionalStringFlag(flags, name) {
16
+ const value = flags[name];
17
+ if (value === undefined)
18
+ return undefined;
19
+ if (typeof value !== "string" || value.trim().length === 0) {
20
+ throw new UsageError(`--${name} requires a value.`, "invalid_flag_value", undefined, { flag: `--${name}`, });
21
+ }
22
+ return value.trim();
23
+ }
24
+ const SENSITIVE_GIT_OUTPUT_KEYS = {
25
+ apikey: true,
26
+ authorization: true,
27
+ password: true,
28
+ token: true,
29
+ };
30
+ /**
31
+ * DSS may already contain legacy remotes with credentials in the URL, and Git
32
+ * error text can echo them. Sanitize every server-derived Project Git value
33
+ * before it reaches stdout or a structured command failure.
34
+ */
35
+ function redactGitUrlUserinfo(text) {
36
+ return text.replace(/https?:[\\/]{1,2}[^\s"'<>]+/giu, (url) => {
37
+ const schemeEnd = url.indexOf(":") + 1;
38
+ let authorityStart = schemeEnd;
39
+ while (url[authorityStart] === "/" || url[authorityStart] === "\\")
40
+ authorityStart++;
41
+ const slash = url.slice(authorityStart).search(/[\\/]/u);
42
+ const authorityEnd = slash === -1 ? url.length : authorityStart + slash;
43
+ const at = url.lastIndexOf("@", authorityEnd - 1);
44
+ if (at < authorityStart)
45
+ return url;
46
+ return `${url.slice(0, authorityStart)}[redacted]@${url.slice(at + 1)}`;
47
+ });
48
+ }
49
+ function sanitizeProjectGitOutput(value, secrets = []) {
50
+ if (typeof value === "string") {
51
+ let sanitized = redactGitUrlUserinfo(value);
52
+ for (const secret of secrets) {
53
+ if (secret !== "")
54
+ sanitized = sanitized.split(secret).join("[redacted]");
55
+ }
56
+ return sanitized;
57
+ }
58
+ if (Array.isArray(value)) {
59
+ return value.map((item) => sanitizeProjectGitOutput(item, secrets));
60
+ }
61
+ if (value !== null && typeof value === "object") {
62
+ const sanitized = {};
63
+ for (const [key, item,] of Object.entries(value)) {
64
+ const normalizedKey = key.replace(/[-_]/g, "").toLowerCase();
65
+ sanitized[key] = SENSITIVE_GIT_OUTPUT_KEYS[normalizedKey] === true
66
+ ? "[redacted]"
67
+ : sanitizeProjectGitOutput(item, secrets);
68
+ }
69
+ return sanitized;
70
+ }
71
+ return value;
72
+ }
73
+ /**
74
+ * Most Git actions answer HTTP 200 with `{success: false}` and the Git stderr
75
+ * in the payload, so the transport layer sees a success. Surface the server's
76
+ * own detail and exit non-zero instead of reporting a completed action.
77
+ */
78
+ function assertGitActionSucceeded(result, action) {
79
+ const sanitized = sanitizeProjectGitOutput(result);
80
+ if (sanitized.success === false) {
81
+ throw new CommandResultFailure({ ...sanitized, resource: "project-git", action, }, GIT_ACTION_FAILURE_EXIT_CODE, "command_result_failure");
82
+ }
83
+ return sanitized;
84
+ }
85
+ function sanitizeProjectGitError(error) {
86
+ if (!(error instanceof Error))
87
+ return error;
88
+ error.message = sanitizeProjectGitOutput(error.message);
89
+ if (error.stack !== undefined)
90
+ error.stack = sanitizeProjectGitOutput(error.stack);
91
+ const errorWithBody = error;
92
+ if (typeof errorWithBody.body === "string") {
93
+ errorWithBody.body = sanitizeProjectGitOutput(errorWithBody.body);
94
+ }
95
+ return error;
96
+ }
97
+ /**
98
+ * External library credentials are read only from the environment variable
99
+ * named by `--password-env`. The secret never reaches argv, a mutation plan, a
100
+ * returned payload, or an error message: only the variable name travels.
101
+ */
102
+ function libraryPassword(flags) {
103
+ const raw = flags["password-env"];
104
+ if (raw === undefined)
105
+ return undefined;
106
+ if (typeof raw !== "string" || raw.trim().length === 0) {
107
+ throw new UsageError("--password-env requires the name of an environment variable holding the password.", "invalid_flag_value", "Pass --password-env DSS_GIT_LIBRARY_PASSWORD, never the password itself.", { flag: "--password-env", });
108
+ }
109
+ const name = raw.trim();
110
+ const value = process.env[name];
111
+ if (value === undefined || value === "") {
112
+ throw new UsageError(`Environment variable ${name} is not set or is empty.`, "missing_required_flag", "--password-env names the environment variable that holds the Git repository password; export that variable before running the command.", { flag: "--password-env", env: name, });
113
+ }
114
+ return value;
115
+ }
116
+ const STATUS_USAGE = "dss project-git status --project-key KEY";
117
+ const GET_REMOTE_USAGE = "dss project-git get-remote --project-key KEY [--name NAME]";
118
+ const SET_REMOTE_USAGE = "dss project-git set-remote --repository URL --project-key KEY [--name NAME]";
119
+ const REMOVE_REMOTE_USAGE = "dss project-git remove-remote --project-key KEY [--name NAME]";
120
+ const BRANCHES_USAGE = "dss project-git branches --project-key KEY [--remote]";
121
+ const CREATE_BRANCH_USAGE = "dss project-git create-branch <name> --project-key KEY [--commit COMMIT] [--duplicate-project] [--target-project-key KEY] [--target-project-folder-id ID]";
122
+ const DELETE_BRANCH_USAGE = "dss project-git delete-branch <name> --project-key KEY [--remote] [--delete-remotely] [--force-delete]";
123
+ const CURRENT_BRANCH_USAGE = "dss project-git current-branch --project-key KEY";
124
+ const TAGS_USAGE = "dss project-git tags --project-key KEY";
125
+ const CREATE_TAG_USAGE = "dss project-git create-tag <name> --project-key KEY [--reference REF] [--message MESSAGE]";
126
+ const DELETE_TAG_USAGE = "dss project-git delete-tag <name> --project-key KEY";
127
+ const SWITCH_USAGE = "dss project-git switch <branch> --project-key KEY";
128
+ const FETCH_USAGE = "dss project-git fetch --project-key KEY";
129
+ const PULL_USAGE = "dss project-git pull --project-key KEY [--branch NAME]";
130
+ const PUSH_USAGE = "dss project-git push --project-key KEY [--branch NAME]";
131
+ const LOG_USAGE = "dss project-git log --project-key KEY [--path PATH] [--start-commit COMMIT] [--count N]";
132
+ const DIFF_USAGE = "dss project-git diff --project-key KEY [--from COMMIT] [--to COMMIT]";
133
+ const COMMIT_USAGE = "dss project-git commit --message MESSAGE --project-key KEY";
134
+ const REVERT_TO_REVISION_USAGE = "dss project-git revert-to-revision <commit> --project-key KEY";
135
+ const REVERT_COMMIT_USAGE = "dss project-git revert-commit <commit> --project-key KEY";
136
+ const RESET_TO_HEAD_USAGE = "dss project-git reset-to-head --project-key KEY";
137
+ const RESET_TO_UPSTREAM_USAGE = "dss project-git reset-to-upstream --project-key KEY";
138
+ const DROP_AND_REBUILD_USAGE = "dss project-git drop-and-rebuild --i-know-what-i-am-doing --project-key KEY";
139
+ const LIST_LIBRARIES_USAGE = "dss project-git list-libraries --project-key KEY";
140
+ const ADD_LIBRARY_USAGE = "dss project-git add-library <target-path> --repository URL --checkout REF --project-key KEY [--path-in-repository PATH] [--login LOGIN] [--password-env ENV_NAME] [--no-add-to-python-path]";
141
+ const SET_LIBRARY_USAGE = "dss project-git set-library <target-path> --repository URL --checkout REF --project-key KEY [--path-in-repository PATH] [--login LOGIN] [--password-env ENV_NAME]";
142
+ const REMOVE_LIBRARY_USAGE = "dss project-git remove-library <target-path> --project-key KEY [--delete-directory]";
143
+ const RESET_LIBRARY_USAGE = "dss project-git reset-library <target-path> --project-key KEY";
144
+ const PUSH_LIBRARY_USAGE = "dss project-git push-library <target-path> --message MESSAGE --project-key KEY";
145
+ const PUSH_ALL_LIBRARIES_USAGE = "dss project-git push-all-libraries --message MESSAGE --project-key KEY";
146
+ const RESET_ALL_LIBRARIES_USAGE = "dss project-git reset-all-libraries --project-key KEY";
147
+ const FUTURE_STATUS_USAGE = "dss project-git future-status <job-id> [--peek]";
148
+ const FUTURE_WAIT_USAGE = "dss project-git future-wait <job-id> [--timeout MS] [--poll-interval MS]";
149
+ const FUTURE_ABORT_USAGE = "dss project-git future-abort <job-id>";
150
+ export const projectGitCommands = {
151
+ status: {
152
+ handler: async (c, a, f) => {
153
+ requireNoArgs(a, STATUS_USAGE);
154
+ return sanitizeProjectGitOutput(await c.projectGit.status(requiredStringFlag(f, "project-key", STATUS_USAGE)));
155
+ },
156
+ usage: STATUS_USAGE,
157
+ description: "Get the project's Git working-copy status: current branch, remotes, tracking counts, and pending changes.",
158
+ examples: ["dss project-git status --project-key MYPROJECT",],
159
+ },
160
+ "get-remote": {
161
+ handler: async (c, a, f) => {
162
+ requireNoArgs(a, GET_REMOTE_USAGE);
163
+ return sanitizeProjectGitOutput(await c.projectGit.getRemote(requiredStringFlag(f, "project-key", GET_REMOTE_USAGE), optionalStringFlag(f, "name") ?? DEFAULT_REMOTE));
164
+ },
165
+ usage: GET_REMOTE_USAGE,
166
+ description: "Read one configured Git remote, origin by default. DSS answers an empty object when the remote is not configured.",
167
+ examples: ["dss project-git get-remote --project-key MYPROJECT",],
168
+ },
169
+ "set-remote": {
170
+ handler: async (c, a, f) => {
171
+ requireNoArgs(a, SET_REMOTE_USAGE);
172
+ const result = await c.projectGit.setRemote(requiredStringFlag(f, "project-key", SET_REMOTE_USAGE), {
173
+ url: requiredStringFlag(f, "repository", SET_REMOTE_USAGE),
174
+ name: optionalStringFlag(f, "name") ?? DEFAULT_REMOTE,
175
+ });
176
+ return assertGitActionSucceeded(result, "set-remote");
177
+ },
178
+ usage: SET_REMOTE_USAGE,
179
+ description: "Create or replace a Git remote. HTTP(S) URLs must not embed credentials; use an SSH remote or DSS-managed Git credentials instead.",
180
+ examples: [
181
+ "dss project-git set-remote --repository git@github.com:acme/analytics.git --project-key MYPROJECT",
182
+ ],
183
+ },
184
+ "remove-remote": {
185
+ handler: async (c, a, f) => {
186
+ requireNoArgs(a, REMOVE_REMOTE_USAGE);
187
+ const name = optionalStringFlag(f, "name") ?? DEFAULT_REMOTE;
188
+ await c.projectGit.removeRemote(requiredStringFlag(f, "project-key", REMOVE_REMOTE_USAGE), name);
189
+ return { removed: name, resource: "project-git-remote", };
190
+ },
191
+ usage: REMOVE_REMOTE_USAGE,
192
+ description: "Delete a Git remote from the project, origin by default.",
193
+ examples: ["dss project-git remove-remote --name upstream --project-key MYPROJECT",],
194
+ },
195
+ branches: {
196
+ handler: async (c, a, f) => {
197
+ requireNoArgs(a, BRANCHES_USAGE);
198
+ return sanitizeProjectGitOutput(await c.projectGit.listBranches(requiredStringFlag(f, "project-key", BRANCHES_USAGE), parseBooleanOption(f["remote"], "--remote")));
199
+ },
200
+ usage: BRANCHES_USAGE,
201
+ description: "List local branch names, or remote-tracking branches with --remote.",
202
+ examples: [
203
+ "dss project-git branches --project-key MYPROJECT",
204
+ "dss project-git branches --remote --project-key MYPROJECT",
205
+ ],
206
+ },
207
+ "create-branch": {
208
+ handler: async (c, a, f) => {
209
+ requireArgs(a, 1, CREATE_BRANCH_USAGE);
210
+ const result = await c.projectGit.createBranch(requiredStringFlag(f, "project-key", CREATE_BRANCH_USAGE), a[0], {
211
+ commit: optionalStringFlag(f, "commit"),
212
+ duplicateProject: parseBooleanOption(f["duplicate-project"], "--duplicate-project"),
213
+ targetProjectKey: optionalStringFlag(f, "target-project-key"),
214
+ targetProjectFolderId: optionalStringFlag(f, "target-project-folder-id"),
215
+ });
216
+ return assertGitActionSucceeded(result, "create-branch");
217
+ },
218
+ usage: CREATE_BRANCH_USAGE,
219
+ description: "Create a branch, optionally from a specific commit. With --duplicate-project DSS copies the project onto the new branch instead of switching this one.",
220
+ examples: [
221
+ "dss project-git create-branch feature/pricing --project-key MYPROJECT",
222
+ "dss project-git create-branch release/2024 --commit 4f1c2ab --project-key MYPROJECT",
223
+ ],
224
+ },
225
+ "delete-branch": {
226
+ handler: async (c, a, f) => {
227
+ requireArgs(a, 1, DELETE_BRANCH_USAGE);
228
+ const result = await c.projectGit.deleteBranch(requiredStringFlag(f, "project-key", DELETE_BRANCH_USAGE), a[0], {
229
+ remote: parseBooleanOption(f["remote"], "--remote"),
230
+ deleteRemotely: parseBooleanOption(f["delete-remotely"], "--delete-remotely"),
231
+ forceDelete: parseBooleanOption(f["force-delete"], "--force-delete"),
232
+ });
233
+ return assertGitActionSucceeded(result, "delete-branch");
234
+ },
235
+ usage: DELETE_BRANCH_USAGE,
236
+ description: "Delete a branch. --remote targets a remote-tracking branch, --delete-remotely also deletes it on the remote, and --force-delete drops unmerged commits.",
237
+ examples: ["dss project-git delete-branch feature/pricing --project-key MYPROJECT",],
238
+ },
239
+ "current-branch": {
240
+ handler: async (c, a, f) => {
241
+ requireNoArgs(a, CURRENT_BRANCH_USAGE);
242
+ const branch = await c.projectGit.currentBranch(requiredStringFlag(f, "project-key", CURRENT_BRANCH_USAGE));
243
+ return { branch, };
244
+ },
245
+ usage: CURRENT_BRANCH_USAGE,
246
+ description: "Get the project's current branch name, or null when DSS reports none.",
247
+ examples: ["dss project-git current-branch --project-key MYPROJECT",],
248
+ },
249
+ tags: {
250
+ handler: async (c, a, f) => {
251
+ requireNoArgs(a, TAGS_USAGE);
252
+ return sanitizeProjectGitOutput(await c.projectGit.listTags(requiredStringFlag(f, "project-key", TAGS_USAGE)));
253
+ },
254
+ usage: TAGS_USAGE,
255
+ description: "List the project's Git tags.",
256
+ examples: ["dss project-git tags --project-key MYPROJECT",],
257
+ },
258
+ "create-tag": {
259
+ handler: async (c, a, f) => {
260
+ requireArgs(a, 1, CREATE_TAG_USAGE);
261
+ const result = await c.projectGit.createTag(requiredStringFlag(f, "project-key", CREATE_TAG_USAGE), a[0], {
262
+ reference: optionalStringFlag(f, "reference"),
263
+ message: optionalStringFlag(f, "message"),
264
+ });
265
+ return assertGitActionSucceeded(result, "create-tag");
266
+ },
267
+ usage: CREATE_TAG_USAGE,
268
+ description: "Create a Git tag. --reference defaults to HEAD; passing --message creates an annotated tag.",
269
+ examples: [
270
+ 'dss project-git create-tag v1.2.0 --message "Release 1.2.0" --project-key MYPROJECT',
271
+ ],
272
+ },
273
+ "delete-tag": {
274
+ handler: async (c, a, f) => {
275
+ requireArgs(a, 1, DELETE_TAG_USAGE);
276
+ const result = await c.projectGit.deleteTag(requiredStringFlag(f, "project-key", DELETE_TAG_USAGE), a[0]);
277
+ return assertGitActionSucceeded(result, "delete-tag");
278
+ },
279
+ usage: DELETE_TAG_USAGE,
280
+ description: "Delete a Git tag from the project.",
281
+ examples: ["dss project-git delete-tag v1.2.0 --project-key MYPROJECT",],
282
+ },
283
+ switch: {
284
+ handler: async (c, a, f) => {
285
+ requireArgs(a, 1, SWITCH_USAGE);
286
+ const result = await c.projectGit.switchBranch(requiredStringFlag(f, "project-key", SWITCH_USAGE), a[0]);
287
+ return assertGitActionSucceeded(result, "switch");
288
+ },
289
+ usage: SWITCH_USAGE,
290
+ description: "Switch the project to another branch. DSS rewrites the project's working copy in place.",
291
+ examples: ["dss project-git switch main --project-key MYPROJECT",],
292
+ },
293
+ fetch: {
294
+ handler: async (c, a, f) => {
295
+ requireNoArgs(a, FETCH_USAGE);
296
+ const result = await c.projectGit.fetch(requiredStringFlag(f, "project-key", FETCH_USAGE));
297
+ return assertGitActionSucceeded(result, "fetch");
298
+ },
299
+ usage: FETCH_USAGE,
300
+ description: "Fetch from the project's Git remote without changing the working copy.",
301
+ examples: ["dss project-git fetch --project-key MYPROJECT",],
302
+ },
303
+ pull: {
304
+ handler: async (c, a, f) => {
305
+ requireNoArgs(a, PULL_USAGE);
306
+ const result = await c.projectGit.pull(requiredStringFlag(f, "project-key", PULL_USAGE), { branchName: optionalStringFlag(f, "branch"), });
307
+ return assertGitActionSucceeded(result, "pull");
308
+ },
309
+ usage: PULL_USAGE,
310
+ description: "Pull with rebase. DSS exposes no merge variant of pull.",
311
+ examples: ["dss project-git pull --project-key MYPROJECT",],
312
+ },
313
+ push: {
314
+ handler: async (c, a, f) => {
315
+ requireNoArgs(a, PUSH_USAGE);
316
+ const result = await c.projectGit.push(requiredStringFlag(f, "project-key", PUSH_USAGE), { branchName: optionalStringFlag(f, "branch"), });
317
+ return assertGitActionSucceeded(result, "push");
318
+ },
319
+ usage: PUSH_USAGE,
320
+ description: "Push the project's committed changes to its Git remote.",
321
+ examples: ["dss project-git push --project-key MYPROJECT",],
322
+ },
323
+ log: {
324
+ handler: async (c, a, f) => {
325
+ requireNoArgs(a, LOG_USAGE);
326
+ return sanitizeProjectGitOutput(await c.projectGit.log(requiredStringFlag(f, "project-key", LOG_USAGE), {
327
+ path: optionalStringFlag(f, "path"),
328
+ startCommit: optionalStringFlag(f, "start-commit"),
329
+ count: num(f["count"], "--count"),
330
+ }));
331
+ },
332
+ usage: LOG_USAGE,
333
+ description: "Read a page of the project's commit log. Paginate by passing the previous response's nextCommit as --start-commit.",
334
+ examples: ["dss project-git log --count 20 --project-key MYPROJECT",],
335
+ },
336
+ diff: {
337
+ handler: async (c, a, f) => {
338
+ requireNoArgs(a, DIFF_USAGE);
339
+ return sanitizeProjectGitOutput(await c.projectGit.diff(requiredStringFlag(f, "project-key", DIFF_USAGE), {
340
+ commitFrom: optionalStringFlag(f, "from"),
341
+ commitTo: optionalStringFlag(f, "to"),
342
+ }));
343
+ },
344
+ usage: DIFF_USAGE,
345
+ description: "Diff the project between two commits. Omitting both bounds diffs the working copy against HEAD.",
346
+ examples: ["dss project-git diff --from 4f1c2ab --to 9de77c1 --project-key MYPROJECT",],
347
+ },
348
+ commit: {
349
+ handler: async (c, a, f) => {
350
+ requireNoArgs(a, COMMIT_USAGE);
351
+ const result = await c.projectGit.commit(requiredStringFlag(f, "project-key", COMMIT_USAGE), { message: requiredStringFlag(f, "message", COMMIT_USAGE), });
352
+ return assertGitActionSucceeded(result, "commit");
353
+ },
354
+ usage: COMMIT_USAGE,
355
+ description: "Commit the project. DSS stages untracked files before committing.",
356
+ examples: [
357
+ 'dss project-git commit --message "Update pricing recipe" --project-key MYPROJECT',
358
+ ],
359
+ },
360
+ "revert-to-revision": {
361
+ handler: async (c, a, f) => {
362
+ requireArgs(a, 1, REVERT_TO_REVISION_USAGE);
363
+ const result = await c.projectGit.revertToRevision(requiredStringFlag(f, "project-key", REVERT_TO_REVISION_USAGE), a[0]);
364
+ return assertGitActionSucceeded(result, "revert-to-revision");
365
+ },
366
+ usage: REVERT_TO_REVISION_USAGE,
367
+ description: "Restore the whole project to the state of a past commit, discarding everything after it in the working copy.",
368
+ examples: ["dss project-git revert-to-revision 4f1c2ab --project-key MYPROJECT",],
369
+ },
370
+ "revert-commit": {
371
+ handler: async (c, a, f) => {
372
+ requireArgs(a, 1, REVERT_COMMIT_USAGE);
373
+ const result = await c.projectGit.revertCommit(requiredStringFlag(f, "project-key", REVERT_COMMIT_USAGE), a[0]);
374
+ return assertGitActionSucceeded(result, "revert-commit");
375
+ },
376
+ usage: REVERT_COMMIT_USAGE,
377
+ description: "Revert a single commit while keeping the commits that follow it.",
378
+ examples: ["dss project-git revert-commit 4f1c2ab --project-key MYPROJECT",],
379
+ },
380
+ "reset-to-head": {
381
+ handler: async (c, a, f) => {
382
+ requireNoArgs(a, RESET_TO_HEAD_USAGE);
383
+ const result = await c.projectGit.resetToHead(requiredStringFlag(f, "project-key", RESET_TO_HEAD_USAGE));
384
+ return assertGitActionSucceeded(result, "reset-to-head");
385
+ },
386
+ usage: RESET_TO_HEAD_USAGE,
387
+ description: "Hard reset the project to local HEAD, discarding uncommitted changes.",
388
+ examples: ["dss project-git reset-to-head --project-key MYPROJECT",],
389
+ },
390
+ "reset-to-upstream": {
391
+ handler: async (c, a, f) => {
392
+ requireNoArgs(a, RESET_TO_UPSTREAM_USAGE);
393
+ const result = await c.projectGit.resetToUpstream(requiredStringFlag(f, "project-key", RESET_TO_UPSTREAM_USAGE));
394
+ return assertGitActionSucceeded(result, "reset-to-upstream");
395
+ },
396
+ usage: RESET_TO_UPSTREAM_USAGE,
397
+ description: "Hard reset the project to its tracked upstream branch, discarding local commits.",
398
+ examples: ["dss project-git reset-to-upstream --project-key MYPROJECT",],
399
+ },
400
+ "drop-and-rebuild": {
401
+ handler: async (c, a, f) => {
402
+ requireNoArgs(a, DROP_AND_REBUILD_USAGE);
403
+ const projectKey = requiredStringFlag(f, "project-key", DROP_AND_REBUILD_USAGE);
404
+ // History-destructive and irreversible: refuse before issuing any
405
+ // request unless the caller acknowledged it on the command line.
406
+ if (parseBooleanOption(f["i-know-what-i-am-doing"], "--i-know-what-i-am-doing") !== true) {
407
+ throw new UsageError("drop-and-rebuild permanently destroys this project's Git history and rebuilds an empty repository.", "missing_required_flag", "Pass --i-know-what-i-am-doing to acknowledge the irreversible history loss.", { flag: "--i-know-what-i-am-doing", projectKey, });
408
+ }
409
+ const result = await c.projectGit.dropAndRebuild(projectKey, { confirmed: true, });
410
+ return assertGitActionSucceeded(result, "drop-and-rebuild");
411
+ },
412
+ usage: DROP_AND_REBUILD_USAGE,
413
+ description: "Wipe the project's entire Git history and rebuild a fresh repository. Irreversible, so it requires --i-know-what-i-am-doing.",
414
+ examples: [
415
+ "dss project-git drop-and-rebuild --i-know-what-i-am-doing --project-key MYPROJECT",
416
+ ],
417
+ },
418
+ "list-libraries": {
419
+ handler: async (c, a, f) => {
420
+ requireNoArgs(a, LIST_LIBRARIES_USAGE);
421
+ return sanitizeProjectGitOutput(await c.projectGit.listLibraries(requiredStringFlag(f, "project-key", LIST_LIBRARIES_USAGE)));
422
+ },
423
+ usage: LIST_LIBRARIES_USAGE,
424
+ description: "List the external Git libraries attached to the project's code library.",
425
+ examples: ["dss project-git list-libraries --project-key MYPROJECT",],
426
+ },
427
+ "add-library": {
428
+ handler: async (c, a, f) => {
429
+ requireArgs(a, 1, ADD_LIBRARY_USAGE);
430
+ const excludeFromPythonPath = parseBooleanOption(f["no-add-to-python-path"], "--no-add-to-python-path");
431
+ const password = libraryPassword(f);
432
+ return sanitizeProjectGitOutput(await c.projectGit.addLibrary(requiredStringFlag(f, "project-key", ADD_LIBRARY_USAGE), {
433
+ repository: requiredStringFlag(f, "repository", ADD_LIBRARY_USAGE),
434
+ localTargetPath: a[0],
435
+ checkout: requiredStringFlag(f, "checkout", ADD_LIBRARY_USAGE),
436
+ pathInGitRepository: optionalStringFlag(f, "path-in-repository"),
437
+ addToPythonPath: excludeFromPythonPath === true ? false : undefined,
438
+ login: optionalStringFlag(f, "login"),
439
+ password,
440
+ }), password === undefined ? [] : [password,]);
441
+ },
442
+ usage: ADD_LIBRARY_USAGE,
443
+ description: "Attach an external Git repository to the project's code library and return the DSS future that clones it. The password is read only from the environment variable named by --password-env.",
444
+ examples: [
445
+ "dss project-git add-library shared-utils --repository git@github.com:acme/utils.git --checkout main --project-key MYPROJECT",
446
+ ],
447
+ },
448
+ "set-library": {
449
+ handler: async (c, a, f) => {
450
+ requireArgs(a, 1, SET_LIBRARY_USAGE);
451
+ const password = libraryPassword(f);
452
+ return sanitizeProjectGitOutput(await c.projectGit.setLibrary(requiredStringFlag(f, "project-key", SET_LIBRARY_USAGE), a[0], {
453
+ repository: requiredStringFlag(f, "repository", SET_LIBRARY_USAGE),
454
+ pathInGitRepository: optionalStringFlag(f, "path-in-repository"),
455
+ checkout: requiredStringFlag(f, "checkout", SET_LIBRARY_USAGE),
456
+ login: optionalStringFlag(f, "login"),
457
+ password,
458
+ }), password === undefined ? [] : [password,]);
459
+ },
460
+ usage: SET_LIBRARY_USAGE,
461
+ description: "Update an attached library's repository, sub-path, or checkout. The password is read only from the environment variable named by --password-env.",
462
+ examples: [
463
+ "dss project-git set-library shared-utils --repository git@github.com:acme/utils.git --checkout v2 --path-in-repository python --project-key MYPROJECT",
464
+ ],
465
+ },
466
+ "remove-library": {
467
+ handler: async (c, a, f) => {
468
+ requireArgs(a, 1, REMOVE_LIBRARY_USAGE);
469
+ const deleteDirectory = parseBooleanOption(f["delete-directory"], "--delete-directory")
470
+ ?? false;
471
+ await c.projectGit.removeLibrary(requiredStringFlag(f, "project-key", REMOVE_LIBRARY_USAGE), a[0], deleteDirectory);
472
+ return { removed: a[0], resource: "project-git-library", deleteDirectory, };
473
+ },
474
+ usage: REMOVE_LIBRARY_USAGE,
475
+ description: "Detach an external Git library. --delete-directory also removes its local directory from the project library.",
476
+ examples: ["dss project-git remove-library shared-utils --project-key MYPROJECT",],
477
+ },
478
+ "reset-library": {
479
+ handler: (c, a, f) => {
480
+ requireArgs(a, 1, RESET_LIBRARY_USAGE);
481
+ return c.projectGit.resetLibrary(requiredStringFlag(f, "project-key", RESET_LIBRARY_USAGE), a[0]);
482
+ },
483
+ usage: RESET_LIBRARY_USAGE,
484
+ description: "Reset one attached library to its remote state, discarding local edits. Returns the DSS future that performs the reset.",
485
+ examples: ["dss project-git reset-library shared-utils --project-key MYPROJECT",],
486
+ },
487
+ "push-library": {
488
+ handler: (c, a, f) => {
489
+ requireArgs(a, 1, PUSH_LIBRARY_USAGE);
490
+ return c.projectGit.pushLibrary(requiredStringFlag(f, "project-key", PUSH_LIBRARY_USAGE), a[0], requiredStringFlag(f, "message", PUSH_LIBRARY_USAGE));
491
+ },
492
+ usage: PUSH_LIBRARY_USAGE,
493
+ description: "Commit and push one attached library. Returns the DSS future that performs the push.",
494
+ examples: [
495
+ 'dss project-git push-library shared-utils --message "Update helpers" --project-key MYPROJECT',
496
+ ],
497
+ },
498
+ "push-all-libraries": {
499
+ handler: (c, a, f) => {
500
+ requireNoArgs(a, PUSH_ALL_LIBRARIES_USAGE);
501
+ return c.projectGit.pushAllLibraries(requiredStringFlag(f, "project-key", PUSH_ALL_LIBRARIES_USAGE), requiredStringFlag(f, "message", PUSH_ALL_LIBRARIES_USAGE));
502
+ },
503
+ usage: PUSH_ALL_LIBRARIES_USAGE,
504
+ description: "Commit and push every attached library. Returns the DSS future that performs the pushes.",
505
+ examples: [
506
+ 'dss project-git push-all-libraries --message "Sync libraries" --project-key MYPROJECT',
507
+ ],
508
+ },
509
+ "reset-all-libraries": {
510
+ handler: (c, a, f) => {
511
+ requireNoArgs(a, RESET_ALL_LIBRARIES_USAGE);
512
+ return c.projectGit.resetAllLibraries(requiredStringFlag(f, "project-key", RESET_ALL_LIBRARIES_USAGE));
513
+ },
514
+ usage: RESET_ALL_LIBRARIES_USAGE,
515
+ description: "Reset every attached library to its remote state. Returns the DSS future that performs the resets.",
516
+ examples: ["dss project-git reset-all-libraries --project-key MYPROJECT",],
517
+ },
518
+ "future-status": {
519
+ handler: async (c, a, f) => {
520
+ requireArgs(a, 1, FUTURE_STATUS_USAGE);
521
+ return sanitizeProjectGitOutput(await c.projectGit.getFutureState(a[0], {
522
+ peek: parseBooleanOption(f["peek"], "--peek"),
523
+ }));
524
+ },
525
+ usage: FUTURE_STATUS_USAGE,
526
+ description: "Get the state of a Git library future by job id. --peek leaves the result queued for a later read.",
527
+ examples: ["dss project-git future-status FUTURE_ID --peek",],
528
+ },
529
+ "future-wait": {
530
+ handler: async (c, a, f) => {
531
+ requireArgs(a, 1, FUTURE_WAIT_USAGE);
532
+ try {
533
+ const result = sanitizeProjectGitOutput(await c.projectGit.waitForFuture(a[0], {
534
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
535
+ timeoutMs: num(f["timeout"], "--timeout"),
536
+ }));
537
+ if (result !== null
538
+ && typeof result === "object"
539
+ && !Array.isArray(result)
540
+ && result.success === false) {
541
+ return assertGitActionSucceeded(result, "future-wait");
542
+ }
543
+ return result;
544
+ }
545
+ catch (error) {
546
+ throw sanitizeProjectGitError(error);
547
+ }
548
+ },
549
+ usage: FUTURE_WAIT_USAGE,
550
+ description: "Wait for a Git library future to finish and return its result.",
551
+ examples: ["dss project-git future-wait FUTURE_ID --timeout 300000",],
552
+ },
553
+ "future-abort": {
554
+ handler: async (c, a) => {
555
+ requireArgs(a, 1, FUTURE_ABORT_USAGE);
556
+ await c.projectGit.abortFuture(a[0]);
557
+ return { aborted: a[0], resource: "project-git-future", };
558
+ },
559
+ usage: FUTURE_ABORT_USAGE,
560
+ description: "Abort a running Git library future by job id.",
561
+ examples: ["dss project-git future-abort FUTURE_ID",],
562
+ },
563
+ };
@@ -29,8 +29,8 @@ export const projectCommands = {
29
29
  map: {
30
30
  handler: (c, _a, f) => c.projects.map({
31
31
  projectKey: f["project-key"],
32
- maxNodes: num(f["max-nodes"]),
33
- maxEdges: num(f["max-edges"]),
32
+ maxNodes: num(f["max-nodes"], "--max-nodes"),
33
+ maxEdges: num(f["max-edges"], "--max-edges"),
34
34
  includeRaw: f["include-raw"] === true,
35
35
  }),
36
36
  usage: "dss project map [--max-nodes N] [--max-edges N] [--include-raw] [--project-key KEY]",
@@ -79,9 +79,9 @@ export const recipeCommands = {
79
79
  logFilter: jobLogFilterFromFlag(f["log-filter"]),
80
80
  maxLogLines: maxLogLinesFromFlags(f),
81
81
  partition: f["partition"],
82
- pollIntervalMs: num(f["poll-interval"]),
82
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
83
83
  projectKey: pk,
84
- timeoutMs: num(f["timeout"]),
84
+ timeoutMs: num(f["timeout"], "--timeout"),
85
85
  summary: f["summary"] === true,
86
86
  wait,
87
87
  };
@@ -164,7 +164,7 @@ export const recipeCommands = {
164
164
  handler: async (c, _a, f) => {
165
165
  const type = f["type"];
166
166
  if (!type) {
167
- throw new UsageError("--type is required. Usage: dss recipe create --type TYPE --input DS (--output DS | --output-folder FOLDER_ID)");
167
+ throw new UsageError("--type is required. Usage: dss recipe create --type TYPE [--input DS] (--output DS | --output-folder FOLDER_ID)");
168
168
  }
169
169
  const outputDataset = f["output"];
170
170
  const outputFolder = f["output-folder"];
@@ -172,7 +172,7 @@ export const recipeCommands = {
172
172
  throw new UsageError("--output and --output-folder are mutually exclusive.");
173
173
  }
174
174
  if (!outputDataset && !outputFolder) {
175
- throw new UsageError("--output or --output-folder is required. Usage: dss recipe create --type TYPE --input DS (--output DS | --output-folder FOLDER_ID)");
175
+ throw new UsageError("--output or --output-folder is required. Usage: dss recipe create --type TYPE [--input DS] (--output DS | --output-folder FOLDER_ID)");
176
176
  }
177
177
  if (outputFolder && !f["output-connection"]) {
178
178
  throw new UsageError("--output-connection is required when using --output-folder.");
@@ -197,10 +197,7 @@ export const recipeCommands = {
197
197
  }
198
198
  fuzzyDistance = normalized;
199
199
  }
200
- const fuzzyThreshold = num(f["fuzzy-threshold"]);
201
- if (typeof f["fuzzy-threshold"] === "string" && fuzzyThreshold === undefined) {
202
- throw new UsageError("--fuzzy-threshold must be a finite number.", "validation_failed");
203
- }
200
+ const fuzzyThreshold = num(f["fuzzy-threshold"], "--fuzzy-threshold");
204
201
  const payload = {
205
202
  type,
206
203
  name,
@@ -255,8 +252,8 @@ export const recipeCommands = {
255
252
  },], pk);
256
253
  return { created: createdName, resource: "recipe", ...created, ...moved, };
257
254
  },
258
- usage: "dss recipe create --type TYPE --input DS[,DS2] (--output DS | --output-folder FOLDER_ID) [--name NAME] [--output-connection CONN] [--zone ZONE|--zone-id ID] [--if-not-exists] [--dry-run] [--join-on COL|LEFT=RIGHT[,...]] [--join-type LEFT|INNER|RIGHT|FULL] [--fuzzy-on COL|LEFT=RIGHT[,...]] [--fuzzy-distance DAMERAU_LEVENSHTEIN|HAMMING|JACCARD|COSINE|EUCLIDEAN] [--fuzzy-threshold N] [--normalize] [--project-key KEY]",
259
- description: "Create a recipe with one or more inputs and a dataset or managed-folder output.",
255
+ usage: "dss recipe create --type TYPE [--input DS[,DS2]] (--output DS | --output-folder FOLDER_ID) [--name NAME] [--output-connection CONN] [--zone ZONE|--zone-id ID] [--if-not-exists] [--dry-run] [--join-on COL|LEFT=RIGHT[,...]] [--join-type LEFT|INNER|RIGHT|FULL] [--fuzzy-on COL|LEFT=RIGHT[,...]] [--fuzzy-distance DAMERAU_LEVENSHTEIN|HAMMING|JACCARD|COSINE|EUCLIDEAN] [--fuzzy-threshold N] [--normalize] [--project-key KEY]",
256
+ description: "Create a recipe with optional inputs and a dataset or managed-folder output.",
260
257
  examples: [
261
258
  "dss recipe create --type python --input raw_orders,lookup --output orders_clean",
262
259
  "dss recipe create --type python --input orders --input customers --output orders_clean --zone Experiments",
@@ -485,23 +482,23 @@ export const recipeCommands = {
485
482
  }
486
483
  return payload;
487
484
  },
488
- usage: "dss recipe get-payload <name> [--raw] [--output PATH] [--project-key KEY]",
489
- description: "Print the recipe code payload as JSON; use --raw for raw bytes, not JSON.",
485
+ usage: "dss recipe get-payload <name> [--output PATH] [--project-key KEY]",
486
+ description: "Print the recipe code payload as a JSON string, or write it to a file with --output.",
490
487
  examples: [
491
- "dss recipe get-payload compute_orders --raw",
488
+ "dss recipe get-payload compute_orders",
492
489
  "dss recipe get-payload compute_orders -o code.py",
493
490
  ],
494
491
  },
495
492
  cat: {
496
493
  handler: (c, a, f) => {
497
- requireArgs(a, 1, "dss recipe cat <name> [--raw]");
494
+ requireArgs(a, 1, "dss recipe cat <name>");
498
495
  return c.recipes.getPayload(a[0], {
499
496
  projectKey: f["project-key"],
500
497
  });
501
498
  },
502
- usage: "dss recipe cat <name> [--raw] [--project-key KEY]",
503
- description: "Print a recipe code payload as JSON; use --raw for raw bytes, not JSON.",
504
- examples: ["dss recipe cat compute_orders --raw",],
499
+ usage: "dss recipe cat <name> [--project-key KEY]",
500
+ description: "Print the recipe code payload as a JSON string.",
501
+ examples: ["dss recipe cat compute_orders",],
505
502
  },
506
503
  "set-payload": {
507
504
  handler: async (c, a, f) => {