dataiku-sdk 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -19
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +39 -4
- package/dist/packages/types/src/index.js +31 -2
- package/dist/src/auth.js +1 -1
- package/dist/src/cli/coerce.js +11 -2
- package/dist/src/cli/commands/analysis.js +4 -3
- package/dist/src/cli/commands/api-service.js +66 -18
- package/dist/src/cli/commands/app.js +4 -3
- package/dist/src/cli/commands/bundle.js +51 -16
- package/dist/src/cli/commands/code-env.js +219 -57
- package/dist/src/cli/commands/code.d.ts +19 -0
- package/dist/src/cli/commands/code.js +87 -15
- package/dist/src/cli/commands/dashboard.js +7 -6
- package/dist/src/cli/commands/data-quality.js +9 -8
- package/dist/src/cli/commands/dataset.js +17 -11
- package/dist/src/cli/commands/doctor.js +4 -2
- package/dist/src/cli/commands/flow-zone.js +42 -13
- package/dist/src/cli/commands/folder.js +9 -8
- package/dist/src/cli/commands/future.js +2 -1
- package/dist/src/cli/commands/insight.js +8 -7
- package/dist/src/cli/commands/job.js +4 -3
- package/dist/src/cli/commands/meaning.js +5 -4
- package/dist/src/cli/commands/ml-task.js +3 -2
- package/dist/src/cli/commands/model-evaluation-store.js +4 -3
- package/dist/src/cli/commands/notebook.js +82 -50
- package/dist/src/cli/commands/project-git.js +8 -5
- package/dist/src/cli/commands/project-library.d.ts +21 -0
- package/dist/src/cli/commands/project-library.js +128 -56
- package/dist/src/cli/commands/project.js +19 -4
- package/dist/src/cli/commands/recipe.js +51 -25
- package/dist/src/cli/commands/saved-model.js +4 -3
- package/dist/src/cli/commands/scenario.js +9 -8
- package/dist/src/cli/commands/sql.d.ts +10 -0
- package/dist/src/cli/commands/sql.js +42 -19
- package/dist/src/cli/commands/statistics.js +7 -6
- package/dist/src/cli/commands/variable.js +2 -1
- package/dist/src/cli/commands/webapp.js +68 -13
- package/dist/src/cli/commands/wiki.js +7 -6
- package/dist/src/cli/contract.d.ts +16 -1
- package/dist/src/cli/contract.js +967 -186
- package/dist/src/cli/env.d.ts +9 -0
- package/dist/src/cli/env.js +20 -2
- package/dist/src/cli/flags.d.ts +4 -0
- package/dist/src/cli/flags.js +29 -1
- package/dist/src/cli/helpers/cleanup.js +104 -3
- package/dist/src/cli/helpers/flow-zone.d.ts +3 -0
- package/dist/src/cli/helpers/flow-zone.js +33 -3
- package/dist/src/cli/helpers/recipe.d.ts +20 -0
- package/dist/src/cli/helpers/recipe.js +158 -3
- package/dist/src/cli/output.d.ts +2 -0
- package/dist/src/cli/output.js +33 -6
- package/dist/src/cli/runtime.d.ts +35 -1
- package/dist/src/cli/runtime.js +125 -18
- package/dist/src/cli/types.d.ts +7 -0
- package/dist/src/cli.js +226 -95
- package/dist/src/client.d.ts +63 -1
- package/dist/src/client.js +305 -53
- package/dist/src/errors.d.ts +8 -2
- package/dist/src/errors.js +42 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.js +2 -1
- package/dist/src/resources/api-services.d.ts +48 -6
- package/dist/src/resources/api-services.js +54 -8
- package/dist/src/resources/bundles.d.ts +51 -6
- package/dist/src/resources/bundles.js +39 -9
- package/dist/src/resources/code-envs.d.ts +25 -3
- package/dist/src/resources/code-envs.js +104 -2
- package/dist/src/resources/datasets.d.ts +20 -0
- package/dist/src/resources/datasets.js +232 -52
- package/dist/src/resources/folders.js +2 -1
- package/dist/src/resources/jobs.d.ts +35 -10
- package/dist/src/resources/jobs.js +92 -17
- package/dist/src/resources/notebooks.d.ts +77 -7
- package/dist/src/resources/notebooks.js +143 -19
- package/dist/src/resources/project-git.d.ts +12 -0
- package/dist/src/resources/project-git.js +39 -8
- package/dist/src/resources/project-library.d.ts +122 -7
- package/dist/src/resources/project-library.js +494 -25
- package/dist/src/resources/projects.d.ts +5 -4
- package/dist/src/resources/projects.js +21 -19
- package/dist/src/resources/scenarios.d.ts +30 -0
- package/dist/src/resources/scenarios.js +73 -27
- package/dist/src/resources/sql.d.ts +6 -0
- package/dist/src/resources/sql.js +23 -3
- package/dist/src/resources/webapps.d.ts +35 -4
- package/dist/src/resources/webapps.js +54 -5
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.d.ts +18 -0
- package/dist/src/skill.js +93 -9
- package/dist/src/utils/flow-analysis.d.ts +53 -0
- package/dist/src/utils/flow-analysis.js +382 -0
- package/dist/src/utils/project-archive.d.ts +41 -2
- package/dist/src/utils/project-archive.js +159 -16
- package/dist/src/utils/response-file.d.ts +2 -0
- package/dist/src/utils/response-file.js +34 -0
- package/package.json +9 -7
- package/packages/types/dist/index.d.ts +39 -4
- package/packages/types/dist/index.js +31 -2
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +44 -197
- package/skills/dataiku-dss/references/app-releases.md +27 -0
- package/skills/dataiku-dss/references/authentication.md +39 -0
- package/skills/dataiku-dss/references/coding.md +8 -0
- package/skills/dataiku-dss/references/discovery.md +30 -0
- package/skills/dataiku-dss/references/flow-maps.md +12 -0
- package/skills/dataiku-dss/references/mutations.md +33 -0
- package/skills/dataiku-dss/references/troubleshooting.md +29 -0
|
@@ -1,7 +1,34 @@
|
|
|
1
|
-
import { readFileSync, } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 { executionMode, } from "../flags.js";
|
|
6
|
+
import { enqueueCliWarning, isNotFoundError, readIfExists, skipResult, } from "../output.js";
|
|
4
7
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
8
|
+
const CODE_ENV_LANGS = ["PYTHON", "R",];
|
|
9
|
+
/** Validate a positional `<lang>` argument: required, must be PYTHON or R. */
|
|
10
|
+
function requireEnvLang(value, usage) {
|
|
11
|
+
if (value !== "PYTHON" && value !== "R") {
|
|
12
|
+
throw new UsageError(`env language must be one of ${CODE_ENV_LANGS.join(", ")}. Usage: ${usage}`, "invalid_enum", undefined, { value, allowed: CODE_ENV_LANGS, });
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function definitionHashMismatch(envLang, envName, expected, current) {
|
|
17
|
+
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.", {
|
|
18
|
+
envLang,
|
|
19
|
+
envName,
|
|
20
|
+
expectedDefinitionHash: expected,
|
|
21
|
+
...(current !== undefined ? { currentDefinitionHash: current, } : {}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function validateExpectHash(value) {
|
|
25
|
+
if (value === undefined || value === false)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (typeof value !== "string" || !/^[0-9a-fA-F]{64}$/.test(value)) {
|
|
28
|
+
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.");
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
5
32
|
function codeEnvWait(flags) {
|
|
6
33
|
return flags["no-wait"] !== true;
|
|
7
34
|
}
|
|
@@ -33,73 +60,155 @@ function codeEnvPackageList(flags) {
|
|
|
33
60
|
}
|
|
34
61
|
export const codeEnvCommands = {
|
|
35
62
|
list: {
|
|
36
|
-
handler: (c, _a, f) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
handler: (c, _a, f) => {
|
|
64
|
+
const usage = "dss code-env list [--lang PYTHON|R]";
|
|
65
|
+
const lang = f["lang"];
|
|
66
|
+
return c.codeEnvs.list({
|
|
67
|
+
envLang: typeof lang === "string"
|
|
68
|
+
? requireEnvLang(lang, usage)
|
|
69
|
+
: undefined,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
usage: "dss code-env list [--lang PYTHON|R]",
|
|
73
|
+
description: "List code environments, optionally filtered to one language.",
|
|
41
74
|
examples: ["dss code-env list", "dss code-env list --lang PYTHON",],
|
|
42
75
|
},
|
|
43
76
|
get: {
|
|
44
77
|
handler: (c, a) => {
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
const usage = "dss code-env get <lang> <name>";
|
|
79
|
+
requireArgs(a, 2, usage);
|
|
80
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
81
|
+
return c.codeEnvs.get(envLang, a[1]);
|
|
47
82
|
},
|
|
48
83
|
usage: "dss code-env get <lang> <name>",
|
|
49
|
-
description: "Get code environment details.",
|
|
84
|
+
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
85
|
examples: ["dss code-env get PYTHON my_env",],
|
|
51
86
|
},
|
|
52
87
|
"get-definition": {
|
|
53
88
|
handler: (c, a) => {
|
|
54
|
-
|
|
55
|
-
|
|
89
|
+
const usage = "dss code-env get-definition <lang> <name>";
|
|
90
|
+
requireArgs(a, 2, usage);
|
|
91
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
92
|
+
return c.codeEnvs.getDefinition(envLang, a[1]);
|
|
56
93
|
},
|
|
57
94
|
usage: "dss code-env get-definition <lang> <name>",
|
|
58
|
-
description: "Get raw code environment definition.",
|
|
95
|
+
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
96
|
examples: ["dss code-env get-definition PYTHON my_env",],
|
|
60
97
|
},
|
|
98
|
+
"list-logs": {
|
|
99
|
+
handler: (c, a) => {
|
|
100
|
+
const usage = "dss code-env list-logs <lang> <name>";
|
|
101
|
+
requireArgs(a, 2, usage);
|
|
102
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
103
|
+
return c.codeEnvs.listLogs(envLang, a[1]);
|
|
104
|
+
},
|
|
105
|
+
usage: "dss code-env list-logs <lang> <name>",
|
|
106
|
+
description: "List the build log files DSS keeps for a code environment.",
|
|
107
|
+
examples: ["dss code-env list-logs PYTHON my_env",],
|
|
108
|
+
},
|
|
109
|
+
"get-log": {
|
|
110
|
+
handler: async (c, a, f) => {
|
|
111
|
+
const usage = "dss code-env get-log <lang> <name> <logName> [--max-lines N|--max-log-lines N] [--max-log-bytes N] [--output PATH]";
|
|
112
|
+
requireArgs(a, 3, usage);
|
|
113
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
114
|
+
const maxLines = numFlag(f, ["max-lines", "max-log-lines",]);
|
|
115
|
+
const maxBytes = num(f["max-log-bytes"], "--max-log-bytes");
|
|
116
|
+
const result = await c.codeEnvs.getLog(envLang, a[1], a[2], {
|
|
117
|
+
...(maxLines !== undefined ? { maxLines, } : {}),
|
|
118
|
+
...(maxBytes !== undefined ? { maxBytes, } : {}),
|
|
119
|
+
});
|
|
120
|
+
const output = f["output"];
|
|
121
|
+
if (typeof output === "string" && output.trim().length > 0) {
|
|
122
|
+
writeFileSync(output, result.log, "utf-8");
|
|
123
|
+
if (result.truncated || result.tailed) {
|
|
124
|
+
const limits = [
|
|
125
|
+
result.tailed ? `tail limited to ${maxLines ?? 500} lines` : undefined,
|
|
126
|
+
result.truncated ? "byte cap reached" : undefined,
|
|
127
|
+
].filter((part) => part !== undefined);
|
|
128
|
+
enqueueCliWarning({
|
|
129
|
+
code: "code_env_log_truncated",
|
|
130
|
+
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.`,
|
|
131
|
+
envLang,
|
|
132
|
+
envName: a[1],
|
|
133
|
+
logName: a[2],
|
|
134
|
+
truncated: result.truncated,
|
|
135
|
+
tailed: result.tailed,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
path: output,
|
|
140
|
+
bytes: result.bytes,
|
|
141
|
+
truncated: result.truncated,
|
|
142
|
+
tailed: result.tailed,
|
|
143
|
+
envLang,
|
|
144
|
+
envName: a[1],
|
|
145
|
+
logName: a[2],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return { ...result, envLang, envName: a[1], logName: a[2], };
|
|
149
|
+
},
|
|
150
|
+
usage: "dss code-env get-log <lang> <name> <logName> [--max-lines N|--max-log-lines N] [--max-log-bytes N] [--output PATH]",
|
|
151
|
+
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.",
|
|
152
|
+
examples: [
|
|
153
|
+
"dss code-env get-log PYTHON my_env install.log --max-lines 200",
|
|
154
|
+
"dss code-env get-log PYTHON my_env install.log --output ./install.log --max-log-bytes 0",
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
version: {
|
|
158
|
+
handler: (c, a) => {
|
|
159
|
+
const usage = "dss code-env version <lang> <name> <projectKey>";
|
|
160
|
+
requireArgs(a, 3, usage);
|
|
161
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
162
|
+
return c.codeEnvs.getVersionForProject(envLang, a[1], a[2]);
|
|
163
|
+
},
|
|
164
|
+
usage: "dss code-env version <lang> <name> <projectKey>",
|
|
165
|
+
description: "Resolve the code environment version a project uses (versioned automation environments); empty version for unversioned environments.",
|
|
166
|
+
examples: ["dss code-env version PYTHON my_env MY_PROJ",],
|
|
167
|
+
},
|
|
61
168
|
create: {
|
|
62
169
|
handler: async (c, a, f) => {
|
|
63
|
-
|
|
170
|
+
const usage = "dss code-env create <lang> <name> --deployment-mode MODE";
|
|
171
|
+
requireArgs(a, 2, usage);
|
|
172
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
64
173
|
const deploymentMode = f["deployment-mode"];
|
|
65
174
|
if (!deploymentMode) {
|
|
66
175
|
throw new UsageError("--deployment-mode is required. Usage: dss code-env create <lang> <name> --deployment-mode MODE");
|
|
67
176
|
}
|
|
68
177
|
const params = codeEnvParams(f);
|
|
69
178
|
const wait = codeEnvWait(f);
|
|
70
|
-
if (f["if-not-exists"] === true || f
|
|
71
|
-
const existing = (await c.codeEnvs.list({ envLang
|
|
179
|
+
if (f["if-not-exists"] === true || executionMode(f).dryRun) {
|
|
180
|
+
const existing = (await c.codeEnvs.list({ envLang, }))
|
|
72
181
|
.find((env) => env.envName === a[1]);
|
|
73
|
-
if (existing && f["if-not-exists"] === true && f
|
|
74
|
-
return skipResult("code-env", a[1], "exists", { envLang
|
|
182
|
+
if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
|
|
183
|
+
return skipResult("code-env", a[1], "exists", { envLang, current: existing, });
|
|
75
184
|
}
|
|
76
|
-
if (f
|
|
185
|
+
if (executionMode(f).dryRun) {
|
|
77
186
|
return {
|
|
78
187
|
dryRun: true,
|
|
79
188
|
action: "create",
|
|
80
189
|
resource: "code-env",
|
|
81
|
-
envLang
|
|
190
|
+
envLang,
|
|
82
191
|
envName: a[1],
|
|
83
192
|
payload: {
|
|
84
193
|
deploymentMode,
|
|
85
194
|
params,
|
|
86
|
-
wait,
|
|
87
195
|
},
|
|
196
|
+
wait,
|
|
88
197
|
...(existing ? { current: existing, } : {}),
|
|
89
198
|
};
|
|
90
199
|
}
|
|
91
200
|
}
|
|
92
201
|
const created = await c.codeEnvs.create({
|
|
93
|
-
envLang
|
|
202
|
+
envLang,
|
|
94
203
|
envName: a[1],
|
|
95
204
|
deploymentMode,
|
|
96
205
|
params,
|
|
97
206
|
wait,
|
|
98
207
|
});
|
|
99
|
-
return { created: a[1], resource: "code-env", envLang
|
|
208
|
+
return { created: a[1], resource: "code-env", envLang, ...created, };
|
|
100
209
|
},
|
|
101
210
|
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.",
|
|
211
|
+
description: "Create a code environment; --no-wait hands back a DSS future payload you can settle with dss future wait.",
|
|
103
212
|
examples: [
|
|
104
213
|
"dss code-env create PYTHON my_env --deployment-mode DESIGN_MANAGED --python-interpreter PYTHON311",
|
|
105
214
|
'dss code-env create PYTHON my_env --deployment-mode DESIGN_MANAGED --params \'{"pythonInterpreter":"PYTHON311"}\' --dry-run',
|
|
@@ -107,47 +216,67 @@ export const codeEnvCommands = {
|
|
|
107
216
|
},
|
|
108
217
|
"set-definition": {
|
|
109
218
|
handler: async (c, a, f) => {
|
|
110
|
-
|
|
219
|
+
const usage = "dss code-env set-definition <lang> <name> (--data JSON|--data-file PATH|--stdin) [--expect-hash SHA256]";
|
|
220
|
+
requireArgs(a, 2, usage);
|
|
221
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
111
222
|
const definition = jsonInput(f);
|
|
112
223
|
if (!definition) {
|
|
113
224
|
throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss code-env set-definition <lang> <name> --data JSON");
|
|
114
225
|
}
|
|
115
|
-
|
|
226
|
+
const expectHash = validateExpectHash(f["expect-hash"]);
|
|
227
|
+
const definitionHash = stableHash(definition);
|
|
228
|
+
if (executionMode(f).dryRun) {
|
|
229
|
+
const current = await readIfExists(() => c.codeEnvs.getDefinition(envLang, a[1]));
|
|
230
|
+
const currentHash = current ? stableHash(current) : undefined;
|
|
231
|
+
if (expectHash !== undefined && currentHash !== expectHash) {
|
|
232
|
+
throw definitionHashMismatch(envLang, a[1], expectHash, currentHash);
|
|
233
|
+
}
|
|
116
234
|
return {
|
|
117
235
|
dryRun: true,
|
|
118
236
|
action: "set-definition",
|
|
119
237
|
resource: "code-env",
|
|
120
|
-
envLang
|
|
238
|
+
envLang,
|
|
121
239
|
envName: a[1],
|
|
122
240
|
definition,
|
|
241
|
+
definitionHash,
|
|
242
|
+
...(currentHash !== undefined
|
|
243
|
+
? { currentDefinitionHash: currentHash, changed: currentHash !== definitionHash, }
|
|
244
|
+
: {}),
|
|
245
|
+
...(expectHash !== undefined ? { expectHash, provenanceVerified: true, } : {}),
|
|
123
246
|
};
|
|
124
247
|
}
|
|
125
|
-
return c.codeEnvs.setDefinition(
|
|
248
|
+
return c.codeEnvs.setDefinition(envLang, a[1], definition, { expectHash, });
|
|
126
249
|
},
|
|
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: [
|
|
250
|
+
usage: "dss code-env set-definition <lang> <name> (--data JSON|--data-file PATH|--stdin) [--expect-hash SHA256] [--dry-run]",
|
|
251
|
+
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.",
|
|
252
|
+
examples: [
|
|
253
|
+
"dss code-env set-definition PYTHON my_env --data-file code-env.json --expect-hash $HASH --dry-run",
|
|
254
|
+
],
|
|
130
255
|
},
|
|
131
256
|
"set-packages": {
|
|
132
257
|
handler: async (c, a, f) => {
|
|
133
|
-
|
|
258
|
+
const usage = "dss code-env set-packages <lang> <name> (--packages PKGS|--package PKG|--file PATH)";
|
|
259
|
+
requireArgs(a, 2, usage);
|
|
260
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
134
261
|
const packages = codeEnvPackageList(f);
|
|
135
262
|
const installCorePackages = parseBooleanOption(f["install-core-packages"], "--install-core-packages");
|
|
136
|
-
|
|
263
|
+
const expectHash = validateExpectHash(f["expect-hash"]);
|
|
264
|
+
if (executionMode(f).dryRun) {
|
|
137
265
|
return {
|
|
138
266
|
dryRun: true,
|
|
139
267
|
action: "set-packages",
|
|
140
268
|
resource: "code-env",
|
|
141
|
-
envLang
|
|
269
|
+
envLang,
|
|
142
270
|
envName: a[1],
|
|
143
271
|
packages,
|
|
144
272
|
installCorePackages,
|
|
273
|
+
...(expectHash !== undefined ? { expectHash, } : {}),
|
|
145
274
|
};
|
|
146
275
|
}
|
|
147
|
-
return c.codeEnvs.setPackages(
|
|
276
|
+
return c.codeEnvs.setPackages(envLang, a[1], packages, { installCorePackages, expectHash, });
|
|
148
277
|
},
|
|
149
|
-
usage: "dss code-env set-packages <lang> <name> (--packages PKGS|--package PKG|--file PATH) [--install-core-packages true|false] [--dry-run]",
|
|
150
|
-
description: "
|
|
278
|
+
usage: "dss code-env set-packages <lang> <name> (--packages PKGS|--package PKG|--file PATH) [--install-core-packages true|false] [--expect-hash SHA256] [--dry-run]",
|
|
279
|
+
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
280
|
examples: [
|
|
152
281
|
"dss code-env set-packages PYTHON my_env --packages 'tabulate\\nnameparser' --dry-run",
|
|
153
282
|
"dss code-env set-packages PYTHON my_env --file requirements.txt",
|
|
@@ -155,7 +284,9 @@ export const codeEnvCommands = {
|
|
|
155
284
|
},
|
|
156
285
|
"update-packages": {
|
|
157
286
|
handler: async (c, a, f) => {
|
|
158
|
-
|
|
287
|
+
const usage = "dss code-env update-packages <lang> <name>";
|
|
288
|
+
requireArgs(a, 2, usage);
|
|
289
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
159
290
|
const wait = codeEnvWait(f);
|
|
160
291
|
const versionToUpdate = typeof f["env-version"] === "string"
|
|
161
292
|
? f["env-version"]
|
|
@@ -167,77 +298,108 @@ export const codeEnvCommands = {
|
|
|
167
298
|
versionToUpdate,
|
|
168
299
|
wait,
|
|
169
300
|
};
|
|
170
|
-
if (f
|
|
301
|
+
if (executionMode(f).dryRun) {
|
|
171
302
|
return {
|
|
172
303
|
dryRun: true,
|
|
173
304
|
action: "update-packages",
|
|
174
305
|
resource: "code-env",
|
|
175
|
-
envLang
|
|
306
|
+
envLang,
|
|
176
307
|
envName: a[1],
|
|
177
308
|
...opts,
|
|
178
309
|
};
|
|
179
310
|
}
|
|
180
|
-
return c.codeEnvs.updatePackages(
|
|
311
|
+
return c.codeEnvs.updatePackages(envLang, a[1], opts);
|
|
181
312
|
},
|
|
182
313
|
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.",
|
|
314
|
+
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
315
|
examples: ["dss code-env update-packages PYTHON my_env --force-rebuild --dry-run",],
|
|
185
316
|
},
|
|
317
|
+
"update-images": {
|
|
318
|
+
handler: async (c, a, f) => {
|
|
319
|
+
const usage = "dss code-env update-images <lang> <name>";
|
|
320
|
+
requireArgs(a, 2, usage);
|
|
321
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
322
|
+
const wait = codeEnvWait(f);
|
|
323
|
+
const envVersion = typeof f["env-version"] === "string" ? f["env-version"] : undefined;
|
|
324
|
+
if (executionMode(f).dryRun) {
|
|
325
|
+
return {
|
|
326
|
+
dryRun: true,
|
|
327
|
+
action: "update-images",
|
|
328
|
+
resource: "code-env",
|
|
329
|
+
envLang,
|
|
330
|
+
envName: a[1],
|
|
331
|
+
envVersion,
|
|
332
|
+
wait,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
return c.codeEnvs.updateImages(envLang, a[1], { envVersion, wait, });
|
|
336
|
+
},
|
|
337
|
+
usage: "dss code-env update-images <lang> <name> [--env-version VERSION] [--no-wait] [--dry-run]",
|
|
338
|
+
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.",
|
|
339
|
+
examples: [
|
|
340
|
+
"dss code-env update-images PYTHON my_env --dry-run",
|
|
341
|
+
"dss code-env update-images PYTHON my_env --env-version v3 --no-wait",
|
|
342
|
+
],
|
|
343
|
+
},
|
|
186
344
|
"set-jupyter": {
|
|
187
345
|
handler: async (c, a, f) => {
|
|
188
|
-
|
|
346
|
+
const usage = "dss code-env set-jupyter <lang> <name> --active true|false";
|
|
347
|
+
requireArgs(a, 2, usage);
|
|
348
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
189
349
|
const active = parseBooleanOption(f["active"], "--active");
|
|
190
350
|
if (active === undefined) {
|
|
191
351
|
throw new UsageError("--active is required. Usage: dss code-env set-jupyter <lang> <name> --active true|false");
|
|
192
352
|
}
|
|
193
353
|
const wait = codeEnvWait(f);
|
|
194
|
-
if (f
|
|
354
|
+
if (executionMode(f).dryRun) {
|
|
195
355
|
return {
|
|
196
356
|
dryRun: true,
|
|
197
357
|
action: "set-jupyter",
|
|
198
358
|
resource: "code-env",
|
|
199
|
-
envLang
|
|
359
|
+
envLang,
|
|
200
360
|
envName: a[1],
|
|
201
361
|
active,
|
|
202
362
|
wait,
|
|
203
363
|
};
|
|
204
364
|
}
|
|
205
|
-
return c.codeEnvs.setJupyterSupport(
|
|
365
|
+
return c.codeEnvs.setJupyterSupport(envLang, a[1], active, { wait, });
|
|
206
366
|
},
|
|
207
367
|
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.",
|
|
368
|
+
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
369
|
examples: ["dss code-env set-jupyter PYTHON my_env --active true --dry-run",],
|
|
210
370
|
},
|
|
211
371
|
delete: {
|
|
212
372
|
handler: async (c, a, f) => {
|
|
213
|
-
|
|
373
|
+
const usage = "dss code-env delete <lang> <name>";
|
|
374
|
+
requireArgs(a, 2, usage);
|
|
375
|
+
const envLang = requireEnvLang(a[0], usage);
|
|
214
376
|
const wait = codeEnvWait(f);
|
|
215
|
-
if (f
|
|
216
|
-
const current = await readIfExists(() => c.codeEnvs.get(
|
|
377
|
+
if (executionMode(f).dryRun) {
|
|
378
|
+
const current = await readIfExists(() => c.codeEnvs.get(envLang, a[1]));
|
|
217
379
|
if (!current)
|
|
218
|
-
return skipResult("code-env", a[1], "missing", { envLang
|
|
380
|
+
return skipResult("code-env", a[1], "missing", { envLang, });
|
|
219
381
|
return {
|
|
220
382
|
dryRun: true,
|
|
221
383
|
action: "delete",
|
|
222
384
|
resource: "code-env",
|
|
223
|
-
envLang
|
|
385
|
+
envLang,
|
|
224
386
|
envName: a[1],
|
|
225
387
|
wait,
|
|
226
388
|
current,
|
|
227
389
|
};
|
|
228
390
|
}
|
|
229
391
|
if (f["if-exists"] === true) {
|
|
230
|
-
return await c.codeEnvs.delete(
|
|
392
|
+
return await c.codeEnvs.delete(envLang, a[1], { wait, }).catch((error) => {
|
|
231
393
|
if (isNotFoundError(error)) {
|
|
232
|
-
return skipResult("code-env", a[1], "missing", { envLang
|
|
394
|
+
return skipResult("code-env", a[1], "missing", { envLang, });
|
|
233
395
|
}
|
|
234
396
|
throw error;
|
|
235
397
|
});
|
|
236
398
|
}
|
|
237
|
-
return c.codeEnvs.delete(
|
|
399
|
+
return c.codeEnvs.delete(envLang, a[1], { wait, });
|
|
238
400
|
},
|
|
239
401
|
usage: "dss code-env delete <lang> <name> [--no-wait] [--if-exists] [--dry-run]",
|
|
240
|
-
description: "Delete a code environment.",
|
|
402
|
+
description: "Delete a code environment; --no-wait hands back a DSS future payload you can settle with dss future wait.",
|
|
241
403
|
examples: ["dss code-env delete PYTHON my_env --dry-run",],
|
|
242
404
|
},
|
|
243
405
|
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
|
|
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({
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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",
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { ClientValidationError, } from "../../errors.js";
|
|
3
3
|
import { deepMerge, } from "../../utils/deep-merge.js";
|
|
4
4
|
import { jsonInput, parseBooleanOption, } from "../coerce.js";
|
|
5
|
+
import { executionMode, } from "../flags.js";
|
|
5
6
|
import { readIfExists, skipResult, } from "../output.js";
|
|
6
7
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
7
8
|
export const dashboardCommands = {
|
|
@@ -106,12 +107,12 @@ export const dashboardCommands = {
|
|
|
106
107
|
if (listed !== undefined)
|
|
107
108
|
payload.listed = listed;
|
|
108
109
|
const pk = f["project-key"];
|
|
109
|
-
if (f["if-not-exists"] === true || f
|
|
110
|
+
if (f["if-not-exists"] === true || executionMode(f).dryRun) {
|
|
110
111
|
const existing = (await c.dashboards.list(pk)).find((dashboard) => dashboard.name === name);
|
|
111
|
-
if (existing && f["if-not-exists"] === true && f
|
|
112
|
+
if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
|
|
112
113
|
return skipResult("dashboard", existing.id, "exists", { current: existing, });
|
|
113
114
|
}
|
|
114
|
-
if (f
|
|
115
|
+
if (executionMode(f).dryRun) {
|
|
115
116
|
return {
|
|
116
117
|
dryRun: true,
|
|
117
118
|
action: "create",
|
|
@@ -146,7 +147,7 @@ export const dashboardCommands = {
|
|
|
146
147
|
if (name === undefined && listed === undefined && !data) {
|
|
147
148
|
throw new UsageError("--name, --listed, --data, --data-file, or --stdin is required.");
|
|
148
149
|
}
|
|
149
|
-
if (f
|
|
150
|
+
if (executionMode(f).dryRun) {
|
|
150
151
|
const current = await c.dashboards.get(a[0], f["project-key"]);
|
|
151
152
|
const next = deepMerge(current, data ?? {});
|
|
152
153
|
if (name !== undefined)
|
|
@@ -170,11 +171,11 @@ export const dashboardCommands = {
|
|
|
170
171
|
handler: async (c, a, f) => {
|
|
171
172
|
requireArgs(a, 1, "dss dashboard delete <id>");
|
|
172
173
|
const pk = f["project-key"];
|
|
173
|
-
if (f
|
|
174
|
+
if (executionMode(f).dryRun || f["if-exists"] === true) {
|
|
174
175
|
const current = await readIfExists(() => c.dashboards.get(a[0], pk));
|
|
175
176
|
if (!current)
|
|
176
177
|
return skipResult("dashboard", a[0], "missing");
|
|
177
|
-
if (f
|
|
178
|
+
if (executionMode(f).dryRun) {
|
|
178
179
|
return { dryRun: true, action: "delete", resource: "dashboard", id: a[0], current, };
|
|
179
180
|
}
|
|
180
181
|
}
|