dataiku-sdk 3.0.0 → 3.1.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 (79) hide show
  1. package/README.md +132 -17
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +39 -4
  4. package/dist/packages/types/src/index.js +31 -2
  5. package/dist/src/cli/commands/api-service.js +62 -16
  6. package/dist/src/cli/commands/bundle.js +49 -15
  7. package/dist/src/cli/commands/code-env.js +210 -49
  8. package/dist/src/cli/commands/code.d.ts +19 -0
  9. package/dist/src/cli/commands/code.js +87 -15
  10. package/dist/src/cli/commands/dataset.js +8 -3
  11. package/dist/src/cli/commands/flow-zone.js +33 -5
  12. package/dist/src/cli/commands/notebook.js +72 -41
  13. package/dist/src/cli/commands/project-git.js +8 -5
  14. package/dist/src/cli/commands/project-library.d.ts +21 -0
  15. package/dist/src/cli/commands/project-library.js +121 -50
  16. package/dist/src/cli/commands/project.js +16 -2
  17. package/dist/src/cli/commands/recipe.js +37 -12
  18. package/dist/src/cli/commands/sql.d.ts +10 -0
  19. package/dist/src/cli/commands/sql.js +39 -17
  20. package/dist/src/cli/commands/webapp.js +67 -13
  21. package/dist/src/cli/contract.d.ts +15 -0
  22. package/dist/src/cli/contract.js +870 -107
  23. package/dist/src/cli/env.d.ts +9 -0
  24. package/dist/src/cli/env.js +20 -2
  25. package/dist/src/cli/flags.js +9 -0
  26. package/dist/src/cli/helpers/cleanup.js +104 -3
  27. package/dist/src/cli/helpers/flow-zone.d.ts +3 -0
  28. package/dist/src/cli/helpers/flow-zone.js +33 -3
  29. package/dist/src/cli/helpers/recipe.d.ts +20 -0
  30. package/dist/src/cli/helpers/recipe.js +158 -3
  31. package/dist/src/cli/output.d.ts +2 -0
  32. package/dist/src/cli/output.js +33 -6
  33. package/dist/src/cli/runtime.d.ts +35 -1
  34. package/dist/src/cli/runtime.js +125 -18
  35. package/dist/src/cli/types.d.ts +7 -0
  36. package/dist/src/cli.js +135 -28
  37. package/dist/src/client.d.ts +60 -0
  38. package/dist/src/client.js +237 -42
  39. package/dist/src/errors.d.ts +8 -2
  40. package/dist/src/errors.js +40 -0
  41. package/dist/src/index.d.ts +4 -3
  42. package/dist/src/index.js +2 -1
  43. package/dist/src/resources/api-services.d.ts +48 -6
  44. package/dist/src/resources/api-services.js +54 -8
  45. package/dist/src/resources/bundles.d.ts +51 -6
  46. package/dist/src/resources/bundles.js +39 -9
  47. package/dist/src/resources/code-envs.d.ts +25 -3
  48. package/dist/src/resources/code-envs.js +104 -2
  49. package/dist/src/resources/datasets.d.ts +20 -0
  50. package/dist/src/resources/datasets.js +232 -52
  51. package/dist/src/resources/jobs.d.ts +35 -10
  52. package/dist/src/resources/jobs.js +92 -17
  53. package/dist/src/resources/notebooks.d.ts +77 -7
  54. package/dist/src/resources/notebooks.js +143 -19
  55. package/dist/src/resources/project-git.d.ts +12 -0
  56. package/dist/src/resources/project-git.js +39 -8
  57. package/dist/src/resources/project-library.d.ts +122 -7
  58. package/dist/src/resources/project-library.js +494 -25
  59. package/dist/src/resources/projects.d.ts +5 -2
  60. package/dist/src/resources/projects.js +18 -2
  61. package/dist/src/resources/scenarios.d.ts +30 -0
  62. package/dist/src/resources/scenarios.js +73 -27
  63. package/dist/src/resources/sql.d.ts +6 -0
  64. package/dist/src/resources/sql.js +22 -2
  65. package/dist/src/resources/webapps.d.ts +35 -4
  66. package/dist/src/resources/webapps.js +54 -5
  67. package/dist/src/schemas.d.ts +2 -2
  68. package/dist/src/schemas.js +1 -1
  69. package/dist/src/skill.d.ts +9 -0
  70. package/dist/src/skill.js +37 -4
  71. package/dist/src/utils/flow-analysis.d.ts +53 -0
  72. package/dist/src/utils/flow-analysis.js +382 -0
  73. package/dist/src/utils/project-archive.d.ts +41 -2
  74. package/dist/src/utils/project-archive.js +159 -16
  75. package/package.json +4 -4
  76. package/packages/types/dist/index.d.ts +39 -4
  77. package/packages/types/dist/index.js +31 -2
  78. package/plugin.json +1 -1
  79. package/skills/dataiku-dss/SKILL.md +74 -47
@@ -1,7 +1,33 @@
1
- import { readFileSync, } from "node:fs";
2
- import { json, jsonInput, parseBooleanOption, } from "../coerce.js";
3
- import { isNotFoundError, readIfExists, skipResult, } from "../output.js";
1
+ import { readFileSync, writeFileSync, } from "node:fs";
2
+ import { ClientValidationError, } from "../../errors.js";
3
+ import { stableHash, } from "../../utils/stable-hash.js";
4
+ import { json, jsonInput, num, numFlag, parseBooleanOption, } from "../coerce.js";
5
+ import { enqueueCliWarning, isNotFoundError, readIfExists, skipResult, } from "../output.js";
4
6
  import { requireArgs, UsageError, } from "../usage.js";
7
+ const CODE_ENV_LANGS = ["PYTHON", "R",];
8
+ /** Validate a positional `<lang>` argument: required, must be PYTHON or R. */
9
+ function requireEnvLang(value, usage) {
10
+ if (value !== "PYTHON" && value !== "R") {
11
+ throw new UsageError(`env language must be one of ${CODE_ENV_LANGS.join(", ")}. Usage: ${usage}`, "invalid_enum", undefined, { value, allowed: CODE_ENV_LANGS, });
12
+ }
13
+ return value;
14
+ }
15
+ function definitionHashMismatch(envLang, envName, expected, current) {
16
+ return new ClientValidationError(`Code env ${envLang}/${envName} changed since the expected definition hash was captured; refusing to overwrite it.`, "validation_failed", "Re-read the definition with dss code-env get-definition (definitionHash comes from code-env get), review the diff, and retry with the fresh hash.", {
17
+ envLang,
18
+ envName,
19
+ expectedDefinitionHash: expected,
20
+ ...(current !== undefined ? { currentDefinitionHash: current, } : {}),
21
+ });
22
+ }
23
+ function validateExpectHash(value) {
24
+ if (value === undefined || value === false)
25
+ return undefined;
26
+ if (typeof value !== "string" || !/^[0-9a-fA-F]{64}$/.test(value)) {
27
+ throw new ClientValidationError("Expected code env definition hash must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the definitionHash value returned by dss code-env get.");
28
+ }
29
+ return value;
30
+ }
5
31
  function codeEnvWait(flags) {
6
32
  return flags["no-wait"] !== true;
7
33
  }
@@ -33,34 +59,116 @@ function codeEnvPackageList(flags) {
33
59
  }
34
60
  export const codeEnvCommands = {
35
61
  list: {
36
- handler: (c, _a, f) => c.codeEnvs.list({
37
- envLang: f["lang"],
38
- }),
39
- usage: "dss code-env list [--lang LANG]",
40
- description: "List code environments.",
62
+ handler: (c, _a, f) => {
63
+ const usage = "dss code-env list [--lang PYTHON|R]";
64
+ const lang = f["lang"];
65
+ return c.codeEnvs.list({
66
+ envLang: typeof lang === "string"
67
+ ? requireEnvLang(lang, usage)
68
+ : undefined,
69
+ });
70
+ },
71
+ usage: "dss code-env list [--lang PYTHON|R]",
72
+ description: "List code environments, optionally filtered to one language.",
41
73
  examples: ["dss code-env list", "dss code-env list --lang PYTHON",],
42
74
  },
43
75
  get: {
44
76
  handler: (c, a) => {
45
- requireArgs(a, 2, "dss code-env get <lang> <name>");
46
- return c.codeEnvs.get(a[0], a[1]);
77
+ const usage = "dss code-env get <lang> <name>";
78
+ requireArgs(a, 2, usage);
79
+ const envLang = requireEnvLang(a[0], usage);
80
+ return c.codeEnvs.get(envLang, a[1]);
47
81
  },
48
82
  usage: "dss code-env get <lang> <name>",
49
- description: "Get code environment details.",
83
+ description: "Get code environment details: requested vs installed packages, Python interpreter, deployment mode, and the stable definition hash (definitionHash) used by set-definition --expect-hash.",
50
84
  examples: ["dss code-env get PYTHON my_env",],
51
85
  },
52
86
  "get-definition": {
53
87
  handler: (c, a) => {
54
- requireArgs(a, 2, "dss code-env get-definition <lang> <name>");
55
- return c.codeEnvs.getDefinition(a[0], a[1]);
88
+ const usage = "dss code-env get-definition <lang> <name>";
89
+ requireArgs(a, 2, usage);
90
+ const envLang = requireEnvLang(a[0], usage);
91
+ return c.codeEnvs.getDefinition(envLang, a[1]);
56
92
  },
57
93
  usage: "dss code-env get-definition <lang> <name>",
58
- description: "Get raw code environment definition.",
94
+ description: "Get the raw code environment definition exactly as DSS stores it; capture its hash (definitionHash from code-env get) before replacing it with set-definition --expect-hash.",
59
95
  examples: ["dss code-env get-definition PYTHON my_env",],
60
96
  },
97
+ "list-logs": {
98
+ handler: (c, a) => {
99
+ const usage = "dss code-env list-logs <lang> <name>";
100
+ requireArgs(a, 2, usage);
101
+ const envLang = requireEnvLang(a[0], usage);
102
+ return c.codeEnvs.listLogs(envLang, a[1]);
103
+ },
104
+ usage: "dss code-env list-logs <lang> <name>",
105
+ description: "List the build log files DSS keeps for a code environment.",
106
+ examples: ["dss code-env list-logs PYTHON my_env",],
107
+ },
108
+ "get-log": {
109
+ handler: async (c, a, f) => {
110
+ const usage = "dss code-env get-log <lang> <name> <logName> [--max-lines N|--max-log-lines N] [--max-log-bytes N] [--output PATH]";
111
+ requireArgs(a, 3, usage);
112
+ const envLang = requireEnvLang(a[0], usage);
113
+ const maxLines = numFlag(f, ["max-lines", "max-log-lines",]);
114
+ const maxBytes = num(f["max-log-bytes"], "--max-log-bytes");
115
+ const result = await c.codeEnvs.getLog(envLang, a[1], a[2], {
116
+ ...(maxLines !== undefined ? { maxLines, } : {}),
117
+ ...(maxBytes !== undefined ? { maxBytes, } : {}),
118
+ });
119
+ const output = f["output"];
120
+ if (typeof output === "string" && output.trim().length > 0) {
121
+ writeFileSync(output, result.log, "utf-8");
122
+ if (result.truncated || result.tailed) {
123
+ const limits = [
124
+ result.tailed ? `tail limited to ${maxLines ?? 500} lines` : undefined,
125
+ result.truncated ? "byte cap reached" : undefined,
126
+ ].filter((part) => part !== undefined);
127
+ enqueueCliWarning({
128
+ code: "code_env_log_truncated",
129
+ message: `Code env ${envLang}/${a[1]} log '${a[2]}' was truncated (${limits.join("; ")},). Pass --max-log-bytes 0 and --max-lines 0 for the full log, or refetch with higher limits.`,
130
+ envLang,
131
+ envName: a[1],
132
+ logName: a[2],
133
+ truncated: result.truncated,
134
+ tailed: result.tailed,
135
+ });
136
+ }
137
+ return {
138
+ path: output,
139
+ bytes: result.bytes,
140
+ truncated: result.truncated,
141
+ tailed: result.tailed,
142
+ envLang,
143
+ envName: a[1],
144
+ logName: a[2],
145
+ };
146
+ }
147
+ return { ...result, envLang, envName: a[1], logName: a[2], };
148
+ },
149
+ usage: "dss code-env get-log <lang> <name> <logName> [--max-lines N|--max-log-lines N] [--max-log-bytes N] [--output PATH]",
150
+ description: "Fetch one code-env build log, bounded for safe output: the last --max-lines lines (default 500, 0 for all) and at most --max-log-bytes bytes (default 10 MiB, 0 for all). --output PATH writes the kept content to a file; stdout then carries the path metadata. A truncation warning names which cap hit.",
151
+ examples: [
152
+ "dss code-env get-log PYTHON my_env install.log --max-lines 200",
153
+ "dss code-env get-log PYTHON my_env install.log --output ./install.log --max-log-bytes 0",
154
+ ],
155
+ },
156
+ version: {
157
+ handler: (c, a) => {
158
+ const usage = "dss code-env version <lang> <name> <projectKey>";
159
+ requireArgs(a, 3, usage);
160
+ const envLang = requireEnvLang(a[0], usage);
161
+ return c.codeEnvs.getVersionForProject(envLang, a[1], a[2]);
162
+ },
163
+ usage: "dss code-env version <lang> <name> <projectKey>",
164
+ description: "Resolve the code environment version a project uses (versioned automation environments); empty version for unversioned environments.",
165
+ examples: ["dss code-env version PYTHON my_env MY_PROJ",],
166
+ },
61
167
  create: {
62
168
  handler: async (c, a, f) => {
63
- requireArgs(a, 2, "dss code-env create <lang> <name> --deployment-mode MODE");
169
+ const usage = "dss code-env create <lang> <name> --deployment-mode MODE";
170
+ requireArgs(a, 2, usage);
171
+ const envLang = requireEnvLang(a[0], usage);
64
172
  const deploymentMode = f["deployment-mode"];
65
173
  if (!deploymentMode) {
66
174
  throw new UsageError("--deployment-mode is required. Usage: dss code-env create <lang> <name> --deployment-mode MODE");
@@ -68,38 +176,38 @@ export const codeEnvCommands = {
68
176
  const params = codeEnvParams(f);
69
177
  const wait = codeEnvWait(f);
70
178
  if (f["if-not-exists"] === true || f["dry-run"] === true) {
71
- const existing = (await c.codeEnvs.list({ envLang: a[0], }))
179
+ const existing = (await c.codeEnvs.list({ envLang, }))
72
180
  .find((env) => env.envName === a[1]);
73
181
  if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
74
- return skipResult("code-env", a[1], "exists", { envLang: a[0], current: existing, });
182
+ return skipResult("code-env", a[1], "exists", { envLang, current: existing, });
75
183
  }
76
184
  if (f["dry-run"] === true) {
77
185
  return {
78
186
  dryRun: true,
79
187
  action: "create",
80
188
  resource: "code-env",
81
- envLang: a[0],
189
+ envLang,
82
190
  envName: a[1],
83
191
  payload: {
84
192
  deploymentMode,
85
193
  params,
86
- wait,
87
194
  },
195
+ wait,
88
196
  ...(existing ? { current: existing, } : {}),
89
197
  };
90
198
  }
91
199
  }
92
200
  const created = await c.codeEnvs.create({
93
- envLang: a[0],
201
+ envLang,
94
202
  envName: a[1],
95
203
  deploymentMode,
96
204
  params,
97
205
  wait,
98
206
  });
99
- return { created: a[1], resource: "code-env", envLang: a[0], ...created, };
207
+ return { created: a[1], resource: "code-env", envLang, ...created, };
100
208
  },
101
209
  usage: "dss code-env create <lang> <name> --deployment-mode MODE [--params JSON|--data JSON|--data-file PATH|--stdin] [--python-interpreter PYTHON311] [--no-wait] [--if-not-exists] [--dry-run]",
102
- description: "Create a code environment.",
210
+ description: "Create a code environment; --no-wait hands back a DSS future payload you can settle with dss future wait.",
103
211
  examples: [
104
212
  "dss code-env create PYTHON my_env --deployment-mode DESIGN_MANAGED --python-interpreter PYTHON311",
105
213
  'dss code-env create PYTHON my_env --deployment-mode DESIGN_MANAGED --params \'{"pythonInterpreter":"PYTHON311"}\' --dry-run',
@@ -107,47 +215,67 @@ export const codeEnvCommands = {
107
215
  },
108
216
  "set-definition": {
109
217
  handler: async (c, a, f) => {
110
- requireArgs(a, 2, "dss code-env set-definition <lang> <name> --data JSON");
218
+ const usage = "dss code-env set-definition <lang> <name> (--data JSON|--data-file PATH|--stdin) [--expect-hash SHA256]";
219
+ requireArgs(a, 2, usage);
220
+ const envLang = requireEnvLang(a[0], usage);
111
221
  const definition = jsonInput(f);
112
222
  if (!definition) {
113
223
  throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss code-env set-definition <lang> <name> --data JSON");
114
224
  }
225
+ const expectHash = validateExpectHash(f["expect-hash"]);
226
+ const definitionHash = stableHash(definition);
115
227
  if (f["dry-run"] === true) {
228
+ const current = await readIfExists(() => c.codeEnvs.getDefinition(envLang, a[1]));
229
+ const currentHash = current ? stableHash(current) : undefined;
230
+ if (expectHash !== undefined && currentHash !== expectHash) {
231
+ throw definitionHashMismatch(envLang, a[1], expectHash, currentHash);
232
+ }
116
233
  return {
117
234
  dryRun: true,
118
235
  action: "set-definition",
119
236
  resource: "code-env",
120
- envLang: a[0],
237
+ envLang,
121
238
  envName: a[1],
122
239
  definition,
240
+ definitionHash,
241
+ ...(currentHash !== undefined
242
+ ? { currentDefinitionHash: currentHash, changed: currentHash !== definitionHash, }
243
+ : {}),
244
+ ...(expectHash !== undefined ? { expectHash, provenanceVerified: true, } : {}),
123
245
  };
124
246
  }
125
- return c.codeEnvs.setDefinition(a[0], a[1], definition);
247
+ return c.codeEnvs.setDefinition(envLang, a[1], definition, { expectHash, });
126
248
  },
127
- usage: "dss code-env set-definition <lang> <name> (--data JSON|--data-file PATH|--stdin) [--dry-run]",
128
- description: "Replace a code environment definition previously fetched from DSS.",
129
- examples: ["dss code-env set-definition PYTHON my_env --data-file code-env.json --dry-run",],
249
+ usage: "dss code-env set-definition <lang> <name> (--data JSON|--data-file PATH|--stdin) [--expect-hash SHA256] [--dry-run]",
250
+ description: "Replace a code environment definition previously fetched from DSS (get-definition). --expect-hash SHA256 refuses the PUT unless the current definition still hashes to the hash captured with code-env get, so concurrent edits are never clobbered; --dry-run also fetches the current definition and reports both hashes plus a changed flag.",
251
+ examples: [
252
+ "dss code-env set-definition PYTHON my_env --data-file code-env.json --expect-hash $HASH --dry-run",
253
+ ],
130
254
  },
131
255
  "set-packages": {
132
256
  handler: async (c, a, f) => {
133
- requireArgs(a, 2, "dss code-env set-packages <lang> <name> --packages PKGS");
257
+ const usage = "dss code-env set-packages <lang> <name> (--packages PKGS|--package PKG|--file PATH)";
258
+ requireArgs(a, 2, usage);
259
+ const envLang = requireEnvLang(a[0], usage);
134
260
  const packages = codeEnvPackageList(f);
135
261
  const installCorePackages = parseBooleanOption(f["install-core-packages"], "--install-core-packages");
262
+ const expectHash = validateExpectHash(f["expect-hash"]);
136
263
  if (f["dry-run"] === true) {
137
264
  return {
138
265
  dryRun: true,
139
266
  action: "set-packages",
140
267
  resource: "code-env",
141
- envLang: a[0],
268
+ envLang,
142
269
  envName: a[1],
143
270
  packages,
144
271
  installCorePackages,
272
+ ...(expectHash !== undefined ? { expectHash, } : {}),
145
273
  };
146
274
  }
147
- return c.codeEnvs.setPackages(a[0], a[1], packages, { installCorePackages, });
275
+ return c.codeEnvs.setPackages(envLang, a[1], packages, { installCorePackages, expectHash, });
148
276
  },
149
- usage: "dss code-env set-packages <lang> <name> (--packages PKGS|--package PKG|--file PATH) [--install-core-packages true|false] [--dry-run]",
150
- description: "Update requested package specs without rebuilding packages.",
277
+ usage: "dss code-env set-packages <lang> <name> (--packages PKGS|--package PKG|--file PATH) [--install-core-packages true|false] [--expect-hash SHA256] [--dry-run]",
278
+ description: "Fetch the current definition, merge the requested package specs (and --install-core-packages) into it, then PUT the merged definition, so fields outside the package list are never dropped; --expect-hash refuses the merge when DSS changed since the hash was captured.",
151
279
  examples: [
152
280
  "dss code-env set-packages PYTHON my_env --packages 'tabulate\\nnameparser' --dry-run",
153
281
  "dss code-env set-packages PYTHON my_env --file requirements.txt",
@@ -155,7 +283,9 @@ export const codeEnvCommands = {
155
283
  },
156
284
  "update-packages": {
157
285
  handler: async (c, a, f) => {
158
- requireArgs(a, 2, "dss code-env update-packages <lang> <name>");
286
+ const usage = "dss code-env update-packages <lang> <name>";
287
+ requireArgs(a, 2, usage);
288
+ const envLang = requireEnvLang(a[0], usage);
159
289
  const wait = codeEnvWait(f);
160
290
  const versionToUpdate = typeof f["env-version"] === "string"
161
291
  ? f["env-version"]
@@ -172,20 +302,49 @@ export const codeEnvCommands = {
172
302
  dryRun: true,
173
303
  action: "update-packages",
174
304
  resource: "code-env",
175
- envLang: a[0],
305
+ envLang,
176
306
  envName: a[1],
177
307
  ...opts,
178
308
  };
179
309
  }
180
- return c.codeEnvs.updatePackages(a[0], a[1], opts);
310
+ return c.codeEnvs.updatePackages(envLang, a[1], opts);
181
311
  },
182
312
  usage: "dss code-env update-packages <lang> <name> [--force-rebuild] [--env-version VERSION] [--no-wait] [--dry-run]",
183
- description: "Rebuild or update code environment packages to match the requested specs.",
313
+ description: "Rebuild or update code environment packages to match the requested specs; --no-wait hands back a DSS future payload you can settle with dss future wait.",
184
314
  examples: ["dss code-env update-packages PYTHON my_env --force-rebuild --dry-run",],
185
315
  },
316
+ "update-images": {
317
+ handler: async (c, a, f) => {
318
+ const usage = "dss code-env update-images <lang> <name>";
319
+ requireArgs(a, 2, usage);
320
+ const envLang = requireEnvLang(a[0], usage);
321
+ const wait = codeEnvWait(f);
322
+ const envVersion = typeof f["env-version"] === "string" ? f["env-version"] : undefined;
323
+ if (f["dry-run"] === true) {
324
+ return {
325
+ dryRun: true,
326
+ action: "update-images",
327
+ resource: "code-env",
328
+ envLang,
329
+ envName: a[1],
330
+ envVersion,
331
+ wait,
332
+ };
333
+ }
334
+ return c.codeEnvs.updateImages(envLang, a[1], { envVersion, wait, });
335
+ },
336
+ usage: "dss code-env update-images <lang> <name> [--env-version VERSION] [--no-wait] [--dry-run]",
337
+ description: "Rebuild the Docker image(s) of a code environment to match its settings; --env-version targets one version of a versioned environment and --no-wait hands back a DSS future payload you can settle with dss future wait.",
338
+ examples: [
339
+ "dss code-env update-images PYTHON my_env --dry-run",
340
+ "dss code-env update-images PYTHON my_env --env-version v3 --no-wait",
341
+ ],
342
+ },
186
343
  "set-jupyter": {
187
344
  handler: async (c, a, f) => {
188
- requireArgs(a, 2, "dss code-env set-jupyter <lang> <name> --active true|false");
345
+ const usage = "dss code-env set-jupyter <lang> <name> --active true|false";
346
+ requireArgs(a, 2, usage);
347
+ const envLang = requireEnvLang(a[0], usage);
189
348
  const active = parseBooleanOption(f["active"], "--active");
190
349
  if (active === undefined) {
191
350
  throw new UsageError("--active is required. Usage: dss code-env set-jupyter <lang> <name> --active true|false");
@@ -196,48 +355,50 @@ export const codeEnvCommands = {
196
355
  dryRun: true,
197
356
  action: "set-jupyter",
198
357
  resource: "code-env",
199
- envLang: a[0],
358
+ envLang,
200
359
  envName: a[1],
201
360
  active,
202
361
  wait,
203
362
  };
204
363
  }
205
- return c.codeEnvs.setJupyterSupport(a[0], a[1], active, { wait, });
364
+ return c.codeEnvs.setJupyterSupport(envLang, a[1], active, { wait, });
206
365
  },
207
366
  usage: "dss code-env set-jupyter <lang> <name> --active true|false [--no-wait] [--dry-run]",
208
- description: "Enable or disable Jupyter support for a code environment.",
367
+ description: "Enable or disable Jupyter support for a code environment; --no-wait hands back a DSS future payload you can settle with dss future wait.",
209
368
  examples: ["dss code-env set-jupyter PYTHON my_env --active true --dry-run",],
210
369
  },
211
370
  delete: {
212
371
  handler: async (c, a, f) => {
213
- requireArgs(a, 2, "dss code-env delete <lang> <name>");
372
+ const usage = "dss code-env delete <lang> <name>";
373
+ requireArgs(a, 2, usage);
374
+ const envLang = requireEnvLang(a[0], usage);
214
375
  const wait = codeEnvWait(f);
215
376
  if (f["dry-run"] === true) {
216
- const current = await readIfExists(() => c.codeEnvs.get(a[0], a[1]));
377
+ const current = await readIfExists(() => c.codeEnvs.get(envLang, a[1]));
217
378
  if (!current)
218
- return skipResult("code-env", a[1], "missing", { envLang: a[0], });
379
+ return skipResult("code-env", a[1], "missing", { envLang, });
219
380
  return {
220
381
  dryRun: true,
221
382
  action: "delete",
222
383
  resource: "code-env",
223
- envLang: a[0],
384
+ envLang,
224
385
  envName: a[1],
225
386
  wait,
226
387
  current,
227
388
  };
228
389
  }
229
390
  if (f["if-exists"] === true) {
230
- return await c.codeEnvs.delete(a[0], a[1], { wait, }).catch((error) => {
391
+ return await c.codeEnvs.delete(envLang, a[1], { wait, }).catch((error) => {
231
392
  if (isNotFoundError(error)) {
232
- return skipResult("code-env", a[1], "missing", { envLang: a[0], });
393
+ return skipResult("code-env", a[1], "missing", { envLang, });
233
394
  }
234
395
  throw error;
235
396
  });
236
397
  }
237
- return c.codeEnvs.delete(a[0], a[1], { wait, });
398
+ return c.codeEnvs.delete(envLang, a[1], { wait, });
238
399
  },
239
400
  usage: "dss code-env delete <lang> <name> [--no-wait] [--if-exists] [--dry-run]",
240
- description: "Delete a code environment.",
401
+ description: "Delete a code environment; --no-wait hands back a DSS future payload you can settle with dss future wait.",
241
402
  examples: ["dss code-env delete PYTHON my_env --dry-run",],
242
403
  },
243
404
  usages: {
@@ -1,2 +1,21 @@
1
1
  import type { CommandMeta } from "../types.js";
2
+ export interface CodeInputSource {
3
+ kind: "file" | "stdin";
4
+ path?: string;
5
+ }
6
+ export interface ResolvedCodeInput {
7
+ script: string;
8
+ source: CodeInputSource;
9
+ }
10
+ /**
11
+ * Canonical validation of `code run` input: rejects positional args and
12
+ * conflicting sources, reads the chosen source, strips a UTF-8 BOM, and
13
+ * rejects empty scripts. Returns the script plus source metadata so plan or
14
+ * replay payloads can hash the script and reference the source without
15
+ * embedding its contents.
16
+ */
17
+ export declare function resolveCodeInputWithSource(args: string[], flags: Record<string, string | boolean>): ResolvedCodeInput;
18
+ export declare function resolveCodeInput(args: string[], flags: Record<string, string | boolean>): string;
19
+ /** Parse an integer flag value, rejecting negative and fractional values before any DSS request. */
20
+ export declare function parseCodeRunIntegerFlag(value: string | boolean | undefined, flagName: string): number | undefined;
2
21
  export declare const codeCommands: Record<string, CommandMeta>;
@@ -1,17 +1,44 @@
1
1
  import { readFileSync, } from "node:fs";
2
+ import { ScenarioScriptRunWithCleanupFailureError, } from "../../resources/scenarios.js";
2
3
  import { num, readStdinText, stripUtf8Bom, } from "../coerce.js";
4
+ import { enqueueCliWarning, } from "../output.js";
3
5
  import { UsageError, } from "../usage.js";
4
- const CODE_RUN_USAGE = "dss code run (--file PATH | --stdin) [--env ENV] [--timeout MS] [--keep] [--full-log] [--max-log-bytes N] [--project-key KEY]";
5
- function resolveCodeInput(args, flags) {
6
+ const CODE_RUN_USAGE = "dss code run (--file PATH | --stdin) [--env ENV] [--timeout MS (default 120000)] [--keep] [--full-log] [--max-log-bytes N (default 1048576)] [--project-key KEY]";
7
+ function previewFlagValue(value) {
8
+ const text = typeof value === "string" ? value : String(value);
9
+ return text.length <= 32 ? text : `${text.slice(0, 32)}…`;
10
+ }
11
+ /** Read a local Python source file, mapping read failures to usage validation. */
12
+ function readCodeSourceFile(filePath) {
13
+ try {
14
+ return readFileSync(filePath, "utf-8");
15
+ }
16
+ catch (error) {
17
+ throw new UsageError(`Could not read --file ${filePath}: ${error instanceof Error ? error.message : String(error)}.`, "not_found", "Verify the file path and that it is readable.");
18
+ }
19
+ }
20
+ /**
21
+ * Canonical validation of `code run` input: rejects positional args and
22
+ * conflicting sources, reads the chosen source, strips a UTF-8 BOM, and
23
+ * rejects empty scripts. Returns the script plus source metadata so plan or
24
+ * replay payloads can hash the script and reference the source without
25
+ * embedding its contents.
26
+ */
27
+ export function resolveCodeInputWithSource(args, flags) {
6
28
  if (args.length > 0) {
7
29
  throw new UsageError(`code run takes no positional arguments; pass the script via --file PATH or --stdin. Usage: ${CODE_RUN_USAGE}`);
8
30
  }
9
31
  const sources = [];
10
32
  if (typeof flags["file"] === "string") {
11
- sources.push({ label: "--file", read: () => readFileSync(flags["file"], "utf-8"), });
33
+ sources.push({
34
+ kind: "file",
35
+ label: "--file",
36
+ path: flags["file"],
37
+ read: () => readCodeSourceFile(flags["file"]),
38
+ });
12
39
  }
13
40
  if (flags["stdin"] === true) {
14
- sources.push({ label: "--stdin", read: readStdinText, });
41
+ sources.push({ kind: "stdin", label: "--stdin", read: readStdinText, });
15
42
  }
16
43
  if (sources.length === 0) {
17
44
  throw new UsageError(`Python source is required: pass --file PATH or --stdin. Usage: ${CODE_RUN_USAGE}`);
@@ -19,23 +46,63 @@ function resolveCodeInput(args, flags) {
19
46
  if (sources.length > 1) {
20
47
  throw new UsageError(`Choose exactly one Python source: --file or --stdin. Usage: ${CODE_RUN_USAGE}`);
21
48
  }
22
- const script = stripUtf8Bom(sources[0].read());
49
+ const source = sources[0];
50
+ const script = stripUtf8Bom(source.read());
23
51
  if (script.trim().length === 0) {
24
- throw new UsageError(`Python source from ${sources[0].label} must not be empty. Usage: ${CODE_RUN_USAGE}`);
52
+ throw new UsageError(`Python source from ${source.label} must not be empty. Usage: ${CODE_RUN_USAGE}`);
53
+ }
54
+ return {
55
+ script,
56
+ source: source.kind === "file" ? { kind: "file", path: source.path, } : { kind: "stdin", },
57
+ };
58
+ }
59
+ export function resolveCodeInput(args, flags) {
60
+ return resolveCodeInputWithSource(args, flags).script;
61
+ }
62
+ /** Parse an integer flag value, rejecting negative and fractional values before any DSS request. */
63
+ export function parseCodeRunIntegerFlag(value, flagName) {
64
+ const parsed = num(value, flagName);
65
+ if (parsed === undefined)
66
+ return undefined;
67
+ if (!Number.isInteger(parsed) || parsed < 0) {
68
+ const valuePreview = previewFlagValue(value);
69
+ throw new UsageError(`Invalid value for ${flagName}: expected a non-negative integer, got ${valuePreview}.`, "invalid_flag_value", undefined, { flag: flagName, value: valuePreview, });
25
70
  }
26
- return script;
71
+ return parsed;
72
+ }
73
+ function reportCleanupFailure(cleanupFailure) {
74
+ enqueueCliWarning({
75
+ type: "code_run_cleanup_failed",
76
+ scenarioId: cleanupFailure.scenarioId,
77
+ error: cleanupFailure.error,
78
+ });
79
+ }
80
+ function unwrapCodeRunError(error) {
81
+ if (error instanceof ScenarioScriptRunWithCleanupFailureError) {
82
+ reportCleanupFailure(error.cleanupFailure);
83
+ return error.cause;
84
+ }
85
+ return error;
27
86
  }
28
87
  export const codeCommands = {
29
88
  run: {
30
89
  handler: async (c, a, f) => {
90
+ const timeoutMs = parseCodeRunIntegerFlag(f["timeout"], "--timeout");
91
+ const maxLogBytes = parseCodeRunIntegerFlag(f["max-log-bytes"], "--max-log-bytes");
31
92
  const script = resolveCodeInput(a, f);
32
- const run = await c.scenarios.runScript(script, {
33
- envName: f["env"],
34
- projectKey: f["project-key"],
35
- timeoutMs: num(f["timeout"], "--timeout"),
36
- keepScenario: f["keep"] === true,
37
- maxLogBytes: num(f["max-log-bytes"], "--max-log-bytes"),
38
- });
93
+ let run;
94
+ try {
95
+ run = await c.scenarios.runScript(script, {
96
+ envName: f["env"],
97
+ projectKey: f["project-key"],
98
+ timeoutMs,
99
+ keepScenario: f["keep"] === true,
100
+ maxLogBytes,
101
+ });
102
+ }
103
+ catch (error) {
104
+ throw unwrapCodeRunError(error);
105
+ }
39
106
  const result = {
40
107
  outcome: run.outcome,
41
108
  success: run.success,
@@ -45,14 +112,19 @@ export const codeCommands = {
45
112
  output: run.output ?? "",
46
113
  logTruncated: run.logTruncated,
47
114
  maxLogBytes: run.maxLogBytes,
115
+ cleanup: run.cleanup,
116
+ ...(run.timedOut === true ? { timedOut: true, timeoutMs: run.timeoutMs, } : {}),
48
117
  };
49
118
  if (f["full-log"] === true || run.output === undefined) {
50
119
  result.log = run.log;
51
120
  }
121
+ if (run.cleanup.status === "failed") {
122
+ reportCleanupFailure({ scenarioId: run.scenarioId, error: run.cleanup.error ?? "unknown", });
123
+ }
52
124
  return result;
53
125
  },
54
126
  usage: CODE_RUN_USAGE,
55
- description: "Run one-off Python in a DSS code env via a throwaway custom-python scenario; returns the script's captured output (stdout+stderr) plus outcome/success. Log retrieval is capped by --max-log-bytes (default 1048576); pass --full-log to include the capped raw DSS run log. Exits 4 on a non-SUCCESS outcome.",
127
+ description: "Run Python through a throwaway custom-python scenario and return captured output plus outcome. --timeout caps waiting; --max-log-bytes caps logs; --full-log returns the capped raw log. Exits 4 unless outcome is SUCCESS.",
56
128
  examples: [
57
129
  "dss code run --file inspect.py",
58
130
  "dss code run --file inspect.py --env py39_pandas",
@@ -314,6 +314,7 @@ export const datasetCommands = {
314
314
  outputPath: f["output"],
315
315
  projectKey: f["project-key"],
316
316
  limit: num(f["limit"], "--limit"),
317
+ rawData: f["raw-data"] === true,
317
318
  });
318
319
  if (result.truncated) {
319
320
  enqueueCliWarning({
@@ -337,9 +338,13 @@ export const datasetCommands = {
337
338
  }
338
339
  return result;
339
340
  },
340
- usage: "dss dataset download <name> [--output PATH] [--limit N] [--project-key KEY]",
341
- description: "Download up to --limit rows (default 100k) as CSV; returns { path, rows, truncated, limit }. When truncated, a dataset_download_truncated warning is written to stderr; when --output is omitted, a dataset_download_default_location warning names the file's path in the working directory so the write is never silent.",
342
- examples: ["dss dataset download orders", "dss dataset download orders --output ./data/",],
341
+ usage: "dss dataset download <name> [--output PATH] [--limit N] [--raw-data] [--project-key KEY]",
342
+ description: "Download up to --limit rows (default 100k) as CSV and return { path, rows, truncated, limit }. Formula-like cells are neutralized for spreadsheets; --raw-data preserves exact bytes. Warnings report truncation and the default output path.",
343
+ examples: [
344
+ "dss dataset download orders",
345
+ "dss dataset download orders --output ./data/",
346
+ "dss dataset download orders --raw-data",
347
+ ],
343
348
  },
344
349
  create: {
345
350
  handler: async (c, _a, f) => {