dataiku-sdk 3.9.0 → 4.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.
- package/README.md +3 -3
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +4 -0
- package/dist/packages/types/src/index.js +4 -0
- package/dist/src/cli/coerce.d.ts +6 -0
- package/dist/src/cli/coerce.js +9 -0
- package/dist/src/cli/commands/app.js +2 -2
- package/dist/src/cli/commands/code-env.js +5 -6
- package/dist/src/cli/commands/code.js +16 -2
- package/dist/src/cli/commands/dashboard.js +1 -2
- package/dist/src/cli/commands/flow-zone.js +1 -1
- package/dist/src/cli/commands/job.js +67 -16
- package/dist/src/cli/commands/plugin.js +7 -7
- package/dist/src/cli/commands/project-library.js +2 -2
- package/dist/src/cli/commands/project.js +21 -4
- package/dist/src/cli/commands/recipe.js +11 -19
- package/dist/src/cli/commands/scenario.js +2 -4
- package/dist/src/cli/contract.js +27 -5
- package/dist/src/cli/env.js +2 -3
- package/dist/src/cli/helpers/app-permissions.d.ts +1 -1
- package/dist/src/cli/helpers/app-permissions.js +2 -2
- package/dist/src/cli/helpers/cleanup.d.ts +2 -0
- package/dist/src/cli/helpers/cleanup.js +122 -8
- package/dist/src/cli/helpers/flow-zone.d.ts +1 -1
- package/dist/src/cli/helpers/flow-zone.js +3 -4
- package/dist/src/cli/list-output.d.ts +5 -0
- package/dist/src/cli/list-output.js +11 -2
- package/dist/src/cli/list-shapes.d.ts +6 -0
- package/dist/src/cli/list-shapes.js +7 -1
- package/dist/src/cli/output-schemas.d.ts +1 -1
- package/dist/src/cli/output-schemas.js +1 -1
- package/dist/src/cli/output.js +7 -0
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +4 -5
- package/dist/src/cli.js +66 -17
- package/dist/src/client.d.ts +14 -2
- package/dist/src/client.js +18 -10
- package/dist/src/generated/action-output-schemas.json +18 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/resources/datasets.d.ts +11 -1
- package/dist/src/resources/datasets.js +91 -30
- package/dist/src/resources/jobs.d.ts +35 -5
- package/dist/src/resources/jobs.js +68 -41
- package/dist/src/resources/projects.js +2 -2
- package/dist/src/resources/recipes.d.ts +12 -1
- package/dist/src/resources/recipes.js +39 -12
- package/dist/src/resources/scenarios.d.ts +8 -1
- package/dist/src/resources/scenarios.js +51 -12
- package/dist/src/resources/wiki.js +3 -7
- package/dist/src/skill.js +1 -2
- package/dist/src/utils/project-archive.d.ts +25 -0
- package/dist/src/utils/project-archive.js +216 -5
- package/dist/src/utils/response-file.js +15 -8
- package/package.json +11 -12
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Malformed user/group listings and scenario run histories fail explicitly instead
|
|
|
16
16
|
|
|
17
17
|
## Platform support
|
|
18
18
|
|
|
19
|
-
The published `dss` CLI requires Bun >= 1.4.
|
|
19
|
+
The published `dss` CLI requires Bun >= 1.4.3 and supports Linux, macOS, and Windows. CI runs Bun 1.4.3 on all three operating systems. The runtime dependency is pure JavaScript, so the same package also runs on every Bun-supported x64 and ARM64 system.
|
|
20
20
|
|
|
21
21
|
Run directly with Bun:
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ Or install the npm binary:
|
|
|
29
29
|
```text
|
|
30
30
|
npm install --global dataiku-sdk
|
|
31
31
|
```
|
|
32
|
-
The installed `dss` bin bootstraps Bun with automatic `.env` preloading disabled, so Bun >= 1.4.
|
|
32
|
+
The installed `dss` bin bootstraps Bun with automatic `.env` preloading disabled, so Bun >= 1.4.3 must be on `PATH`; the CLI's own `.env` handling stays authoritative. `bunx --bun` forces Bun's Node-compatibility mode, which provides the same no-preload guarantee across operating systems.
|
|
33
33
|
|
|
34
34
|
Bun is the only supported runtime and the package manager. Examples below assume an installed `dss` binary. From a checkout, use `bun --no-env-file src/cli.ts ...` or the packaged launcher `bun --no-env-file ./bin/dss.js ...`. From another working directory, pass the checkout's absolute `bin/dss.js` path to Bun.
|
|
35
35
|
`--no-env-file` disables Bun's automatic preloading only; the CLI still applies its documented `.env` handling unless `DATAIKU_DISABLE_ENV=1` is set.
|
|
@@ -181,7 +181,7 @@ then update `tests/live-coverage.ts` when the
|
|
|
181
181
|
registry changes; catalogue drift fails the offline gate. Plan/dry-run calls do not count
|
|
182
182
|
as executed live coverage.
|
|
183
183
|
|
|
184
|
-
Regular `bun run
|
|
184
|
+
Regular `bun run verify` and `bun run lint` include the live runner and fixtures.
|
|
185
185
|
Lint includes project-owned rules in `tools/oxlint/index.ts`: accumulator-copy, widen-then-assert, and chained-assertion errors, alongside the native accumulating-spread check.
|
|
186
186
|
For focused verification:
|
|
187
187
|
|
package/dist/build-metadata.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"format":1,"buildRevision":"
|
|
1
|
+
{"format":1,"buildRevision":"ab176cb1de853bfbdc58627b38a131ce1fc1681d"}
|
|
@@ -284,6 +284,10 @@ export declare const JobWaitResultSchema: import("@sinclair/typebox").TObject<{
|
|
|
284
284
|
pollCount: import("@sinclair/typebox").TNumber;
|
|
285
285
|
success: import("@sinclair/typebox").TBoolean;
|
|
286
286
|
timedOut: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
287
|
+
/** Present on a wait timeout: the wait never cancels the job, so this is `false`. */
|
|
288
|
+
cancelled: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
289
|
+
/** CLI only, on a wait timeout: the command that resumes waiting for the still-running job. */
|
|
290
|
+
resume: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
287
291
|
progress: import("@sinclair/typebox").TObject<{
|
|
288
292
|
done: import("@sinclair/typebox").TNumber;
|
|
289
293
|
failed: import("@sinclair/typebox").TNumber;
|
|
@@ -308,6 +308,10 @@ export const JobWaitResultSchema = Type.Object({
|
|
|
308
308
|
pollCount: Type.Number(),
|
|
309
309
|
success: Type.Boolean(),
|
|
310
310
|
timedOut: Type.Optional(Type.Boolean()),
|
|
311
|
+
/** Present on a wait timeout: the wait never cancels the job, so this is `false`. */
|
|
312
|
+
cancelled: Type.Optional(Type.Boolean()),
|
|
313
|
+
/** CLI only, on a wait timeout: the command that resumes waiting for the still-running job. */
|
|
314
|
+
resume: Type.Optional(Type.String()),
|
|
311
315
|
progress: Type.Object({
|
|
312
316
|
done: Type.Number(),
|
|
313
317
|
failed: Type.Number(),
|
package/dist/src/cli/coerce.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export declare function parseJsonObject(text: string, source: string): Record<st
|
|
|
21
21
|
export declare function json(v: string | boolean | undefined, source?: string): Record<string, unknown> | undefined;
|
|
22
22
|
export declare function readStdinText(): string;
|
|
23
23
|
export declare function readInputText(path: string, flag: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Async UTF-8 read of a user-supplied path (BOM kept, like `readFileSync`).
|
|
26
|
+
* Stays on node:fs: Bun 1.4.3 `Bun.file(path)` reads of a missing path ended the
|
|
27
|
+
* process on Windows CI instead of rejecting with ENOENT.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readTextFile(path: string): Promise<string>;
|
|
24
30
|
export declare function seenJsonInputCredentials(): readonly string[];
|
|
25
31
|
export declare function jsonInput(flags: Record<string, string | boolean>): Record<string, unknown> | undefined;
|
|
26
32
|
export declare function unknownJsonInput(flags: Record<string, string | boolean>): unknown;
|
package/dist/src/cli/coerce.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync, } from "node:fs";
|
|
2
|
+
import { readFile, } from "node:fs/promises";
|
|
2
3
|
import { credentialValues, } from "../utils/secret-sanitize.js";
|
|
3
4
|
import { UsageError, } from "./usage.js";
|
|
4
5
|
function safeValuePreview(value) {
|
|
@@ -174,6 +175,14 @@ export function readInputText(path, flag) {
|
|
|
174
175
|
throw new UsageError(`Could not read ${flag} file: ${path}`, "validation_failed", "Verify that the path names an existing, readable file.", { flag, path, ...(cause ? { cause, } : {}), });
|
|
175
176
|
}
|
|
176
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Async UTF-8 read of a user-supplied path (BOM kept, like `readFileSync`).
|
|
180
|
+
* Stays on node:fs: Bun 1.4.3 `Bun.file(path)` reads of a missing path ended the
|
|
181
|
+
* process on Windows CI instead of rejecting with ENOENT.
|
|
182
|
+
*/
|
|
183
|
+
export async function readTextFile(path) {
|
|
184
|
+
return readFile(path, "utf-8");
|
|
185
|
+
}
|
|
177
186
|
/**
|
|
178
187
|
* Credential values seen in this process's JSON inputs. DSS error text can
|
|
179
188
|
* echo the request payload, so error reports redact these.
|
|
@@ -754,7 +754,7 @@ export const appCommands = withUsage("app", {
|
|
|
754
754
|
const usage = commandUsage("app", "permissions-diff");
|
|
755
755
|
requireNoArgs(_a, usage);
|
|
756
756
|
const file = requiredStringFlag(f, "file", usage);
|
|
757
|
-
const snapshot = readAppPermissionsSnapshot(file);
|
|
757
|
+
const snapshot = await readAppPermissionsSnapshot(file);
|
|
758
758
|
const projectKey = c.resolveProjectKey(f["project-key"]);
|
|
759
759
|
assertAppPermissionsSnapshotBinding(snapshot, projectKey, c.getBaseUrl());
|
|
760
760
|
const currentProjectIncarnationHash = await requireProjectIncarnationHash(c, projectKey);
|
|
@@ -779,7 +779,7 @@ export const appCommands = withUsage("app", {
|
|
|
779
779
|
const usage = commandUsage("app", "permissions-restore");
|
|
780
780
|
requireNoArgs(_a, usage);
|
|
781
781
|
const file = requiredStringFlag(f, "file", usage);
|
|
782
|
-
const snapshot = readAppPermissionsSnapshot(file);
|
|
782
|
+
const snapshot = await readAppPermissionsSnapshot(file);
|
|
783
783
|
const projectKey = c.resolveProjectKey(f["project-key"]);
|
|
784
784
|
assertAppPermissionsSnapshotBinding(snapshot, projectKey, c.getBaseUrl());
|
|
785
785
|
const currentProjectIncarnationHash = await requireProjectIncarnationHash(c, projectKey);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, } from "node:fs";
|
|
2
1
|
import { ClientValidationError, } from "../../errors.js";
|
|
3
2
|
import { SHA256_HEX_PATTERN, stableHash, } from "../../utils/stable-hash.js";
|
|
4
|
-
import { json, jsonInput, num, numFlag, parseBooleanOption, } from "../coerce.js";
|
|
3
|
+
import { json, jsonInput, num, numFlag, parseBooleanOption, readTextFile, } from "../coerce.js";
|
|
5
4
|
import { executionMode, } from "../flags.js";
|
|
6
5
|
import { enqueueCliWarning, isNotFoundError, readIfExists, skipResult, } from "../output.js";
|
|
7
6
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
@@ -49,9 +48,9 @@ function splitPackageSpec(raw) {
|
|
|
49
48
|
* (set-packages replaces specPackageList wholesale); only a call with no
|
|
50
49
|
* package source flag at all is a usage error.
|
|
51
50
|
*/
|
|
52
|
-
function codeEnvPackageList(flags) {
|
|
51
|
+
async function codeEnvPackageList(flags) {
|
|
53
52
|
const file = flags["file"];
|
|
54
|
-
const packages = typeof file === "string" ? splitPackageSpec(
|
|
53
|
+
const packages = typeof file === "string" ? splitPackageSpec(await readTextFile(file)) : [];
|
|
55
54
|
if (typeof flags["packages"] === "string") {
|
|
56
55
|
packages.push(...splitPackageSpec(flags["packages"]));
|
|
57
56
|
}
|
|
@@ -122,7 +121,7 @@ export const codeEnvCommands = withUsage("code-env", {
|
|
|
122
121
|
});
|
|
123
122
|
const output = f["output"];
|
|
124
123
|
if (typeof output === "string" && output.trim().length > 0) {
|
|
125
|
-
|
|
124
|
+
await Bun.write(output, result.log);
|
|
126
125
|
if (result.truncated || result.tailed) {
|
|
127
126
|
const limits = [
|
|
128
127
|
result.tailed ? `tail limited to ${maxLines ?? 500} lines` : undefined,
|
|
@@ -257,7 +256,7 @@ export const codeEnvCommands = withUsage("code-env", {
|
|
|
257
256
|
const usage = commandUsage("code-env", "set-packages");
|
|
258
257
|
requireArgs(a, 2, usage);
|
|
259
258
|
const envLang = requireEnvLang(a[0], usage);
|
|
260
|
-
const packages = codeEnvPackageList(f);
|
|
259
|
+
const packages = await codeEnvPackageList(f);
|
|
261
260
|
const installCorePackages = parseBooleanOption(f["install-core-packages"], "--install-core-packages");
|
|
262
261
|
const expectHash = validateExpectHash(f["expect-hash"]);
|
|
263
262
|
if (executionMode(f).dryRun) {
|
|
@@ -115,19 +115,33 @@ export const codeCommands = withUsage("code", {
|
|
|
115
115
|
maxLogBytes: run.maxLogBytes,
|
|
116
116
|
cleanup: run.cleanup,
|
|
117
117
|
...(run.timedOut === true ? { timedOut: true, timeoutMs: run.timeoutMs, } : {}),
|
|
118
|
+
...(run.outputTruncated === true ? { outputTruncated: true, } : {}),
|
|
119
|
+
...(run.outputOmittedBytes !== undefined
|
|
120
|
+
? { outputOmittedBytes: run.outputOmittedBytes, }
|
|
121
|
+
: {}),
|
|
118
122
|
};
|
|
119
123
|
if (f["full-log"] === true || run.output === undefined) {
|
|
120
124
|
result.log = run.log;
|
|
121
125
|
}
|
|
126
|
+
if (run.outputTruncated === true) {
|
|
127
|
+
enqueueCliWarning({
|
|
128
|
+
code: "code_run_output_truncated",
|
|
129
|
+
hint: run.outputOmittedBytes !== undefined
|
|
130
|
+
? `Script output exceeded the capture budget; the first ${run.outputOmittedBytes} bytes were dropped and \`output\` holds the last part. Raise --max-log-bytes (the budget is a quarter of it) or print less.`
|
|
131
|
+
: "The run log exceeded --max-log-bytes, so the start of the script output was cut and `output` holds the last part. Raise --max-log-bytes or print less.",
|
|
132
|
+
...(run.outputOmittedBytes !== undefined ? { omittedBytes: run.outputOmittedBytes, } : {}),
|
|
133
|
+
maxLogBytes: run.maxLogBytes,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
122
136
|
if (run.cleanup.status === "failed") {
|
|
123
137
|
reportCleanupFailure({ scenarioId: run.scenarioId, error: run.cleanup.error ?? "unknown", });
|
|
124
138
|
}
|
|
125
139
|
return result;
|
|
126
140
|
},
|
|
127
|
-
description: "Run Python through a throwaway custom-python scenario and return captured output plus outcome. --timeout caps waiting; --max-log-bytes caps
|
|
141
|
+
description: "Run Python through a throwaway custom-python scenario and return captured output plus outcome. --timeout caps waiting; --max-log-bytes caps the log tail read (script output keeps its last quarter; a code_run_output_truncated warning reports cuts); --full-log returns the capped raw log. Exits 4 unless outcome is SUCCESS.",
|
|
128
142
|
examples: [
|
|
129
143
|
"dss code run --file inspect.py",
|
|
130
|
-
"dss code run --file inspect.py --env
|
|
144
|
+
"dss code run --file inspect.py --env py311_pandas",
|
|
131
145
|
"cat snippet.py | dss code run --stdin",
|
|
132
146
|
"dss code run --file inspect.py --full-log",
|
|
133
147
|
],
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
1
|
import { ClientValidationError, } from "../../errors.js";
|
|
3
2
|
import { deepMerge, } from "../../utils/deep-merge.js";
|
|
4
3
|
import { jsonInput, parseBooleanOption, } from "../coerce.js";
|
|
@@ -73,7 +72,7 @@ export const dashboardCommands = withUsage("dashboard", {
|
|
|
73
72
|
if (!isPdf) {
|
|
74
73
|
throw new ClientValidationError("DSS dashboard export did not return a PDF.", "validation_failed", "Confirm DSS graphics export is configured and retry the export.");
|
|
75
74
|
}
|
|
76
|
-
|
|
75
|
+
await Bun.write(outputPath, bytes);
|
|
77
76
|
return {
|
|
78
77
|
path: outputPath,
|
|
79
78
|
bytes: bytes.length,
|
|
@@ -275,7 +275,7 @@ export const flowZoneCommands = withUsage("flow-zone", {
|
|
|
275
275
|
handler: async (c, _a, f) => {
|
|
276
276
|
const usage = commandUsage("flow-zone", "organize");
|
|
277
277
|
const pk = f["project-key"];
|
|
278
|
-
const parsed = readFlowZoneOrganizePlan(f, usage);
|
|
278
|
+
const parsed = await readFlowZoneOrganizePlan(f, usage);
|
|
279
279
|
const sync = f["sync"] === true;
|
|
280
280
|
const validateObjects = f["validate-objects"] === true;
|
|
281
281
|
const recreateOnMove = f["recreate-on-move"] === true;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ClientValidationError, DataikuError, } from "../../errors.js";
|
|
1
|
+
import { resolve, } from "node:path";
|
|
2
|
+
import { ClientValidationError, DataikuError, dataikuErrorCode, } from "../../errors.js";
|
|
4
3
|
import { asRecord, } from "../../utils/records.js";
|
|
5
4
|
import { jobBuildTargetTypeFromFlags, jobLogFilterFromFlag, maxLogLinesFromFlags, num, } from "../coerce.js";
|
|
6
5
|
import { executionMode, } from "../flags.js";
|
|
6
|
+
import { compactValue, } from "../list-output.js";
|
|
7
|
+
import { COMPACT_LIST_FIELDS, } from "../list-shapes.js";
|
|
7
8
|
import { encodedProjectEndpoint, enqueueCliWarning, } from "../output.js";
|
|
8
9
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
9
10
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
@@ -33,6 +34,24 @@ function tailJobLogForStdout(log, limit) {
|
|
|
33
34
|
const tail = lines.slice(-shown).join("\n");
|
|
34
35
|
return hasTrailingLineBreak ? `${tail}\n` : tail;
|
|
35
36
|
}
|
|
37
|
+
const RESPONSE_TOO_LARGE_STATUS_TEXT = "Response Too Large";
|
|
38
|
+
/** POSIX-shell quoting for a resume command argument. */
|
|
39
|
+
function shellArg(value) {
|
|
40
|
+
return /^[\w.:@%+=,/-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
41
|
+
}
|
|
42
|
+
/** Command that resumes waiting for a job whose wait timed out. */
|
|
43
|
+
function jobWaitResumeCommand(jobId, projectKey) {
|
|
44
|
+
return `dss job wait ${shellArg(jobId)}${projectKey ? ` --project-key ${shellArg(projectKey)}` : ""}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A wait that timed out only stopped polling: the job is still running in DSS.
|
|
48
|
+
* Attach the command that resumes waiting; other results pass through.
|
|
49
|
+
*/
|
|
50
|
+
function withWaitResume(result, projectKey) {
|
|
51
|
+
if (result.timedOut !== true)
|
|
52
|
+
return result;
|
|
53
|
+
return { ...result, resume: jobWaitResumeCommand(result.jobId, projectKey), };
|
|
54
|
+
}
|
|
36
55
|
/**
|
|
37
56
|
* With an empty output schema DSS ends a dataset build DONE after writing no
|
|
38
57
|
* columns and no rows, without an error. Flag that after a successful build so
|
|
@@ -120,8 +139,9 @@ function jobSummaryType(job) {
|
|
|
120
139
|
?? "unknown";
|
|
121
140
|
}
|
|
122
141
|
function jobSummaryState(job) {
|
|
142
|
+
const listState = compactValue(job, COMPACT_LIST_FIELDS["job"]["state"]);
|
|
123
143
|
return stringPath(job, ["baseStatus", "state",])
|
|
124
|
-
??
|
|
144
|
+
?? (typeof listState === "string" ? listState : undefined)
|
|
125
145
|
?? "unknown";
|
|
126
146
|
}
|
|
127
147
|
function filteredJobList(jobs, flags) {
|
|
@@ -202,7 +222,19 @@ function jobDurationMs(details) {
|
|
|
202
222
|
}
|
|
203
223
|
async function jobInspectionSummary(client, jobId, flags) {
|
|
204
224
|
const projectKey = flags["project-key"];
|
|
205
|
-
|
|
225
|
+
// A job-detail body over the buffered response cap must not sink the whole
|
|
226
|
+
// summary: keep the log part and report the details failure structurally.
|
|
227
|
+
let details = {};
|
|
228
|
+
let detailsError;
|
|
229
|
+
try {
|
|
230
|
+
details = await client.jobs.get(jobId, projectKey);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (!(error instanceof DataikuError && error.statusText === RESPONSE_TOO_LARGE_STATUS_TEXT)) {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
detailsError = { code: dataikuErrorCode(error.category), message: error.message, };
|
|
237
|
+
}
|
|
206
238
|
let log;
|
|
207
239
|
let logError;
|
|
208
240
|
try {
|
|
@@ -233,6 +265,7 @@ async function jobInspectionSummary(client, jobId, flags) {
|
|
|
233
265
|
jobId: jobSummaryId(details, jobId),
|
|
234
266
|
state: jobSummaryState(details),
|
|
235
267
|
type: jobSummaryType(details),
|
|
268
|
+
...(detailsError ? { detailsError, } : {}),
|
|
236
269
|
...(durationMs !== undefined ? { durationMs, } : {}),
|
|
237
270
|
...(outputs !== undefined ? { outputs, } : {}),
|
|
238
271
|
warnings: jobWarningSummary(details, log),
|
|
@@ -253,7 +286,7 @@ async function jobInspectionSummary(client, jobId, flags) {
|
|
|
253
286
|
export const jobCommands = withUsage("job", {
|
|
254
287
|
list: {
|
|
255
288
|
handler: async (c, _a, f) => filteredJobList(await c.jobs.list(f["project-key"]), f),
|
|
256
|
-
description: "List recent jobs, optionally filtered for automation.",
|
|
289
|
+
description: "List recent jobs, optionally filtered for automation. In-progress jobs, which DSS lists without a state, show RUNNING; `job get` has the exact phase.",
|
|
257
290
|
examples: ["dss job list --state DONE --latest", "dss job list --contains WLM225S --limit 10",],
|
|
258
291
|
},
|
|
259
292
|
get: {
|
|
@@ -282,18 +315,34 @@ export const jobCommands = withUsage("job", {
|
|
|
282
315
|
?? f["output-file"];
|
|
283
316
|
const requestedLines = maxLogLinesFromFlags(f);
|
|
284
317
|
// Stdout is tailed here (not in the SDK) so the total can be reported.
|
|
285
|
-
const
|
|
318
|
+
const details = await c.jobs.logDetails(a[0], {
|
|
286
319
|
activity: f["activity"],
|
|
287
320
|
logId: f["log-id"],
|
|
288
321
|
logFilter,
|
|
289
322
|
maxLogLines: outputFile ? requestedLines : -1,
|
|
290
323
|
projectKey: f["project-key"],
|
|
291
324
|
});
|
|
325
|
+
const log = details.text;
|
|
326
|
+
if (details.truncated) {
|
|
327
|
+
enqueueCliWarning({
|
|
328
|
+
code: "log_truncated",
|
|
329
|
+
reason: "byte_cap",
|
|
330
|
+
earlyLinesDropped: true,
|
|
331
|
+
maxLogBytes: details.maxLogBytes,
|
|
332
|
+
hint: "Only the most recent part of the log was retrieved; early lines (and any earlier errors) were dropped. Use `dss job get` for job-level and per-activity errors.",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (logFilter === "errors" && log.length === 0) {
|
|
336
|
+
enqueueCliWarning({
|
|
337
|
+
code: "no_error_lines",
|
|
338
|
+
scannedLines: details.scannedLines,
|
|
339
|
+
hint: `No error lines matched in ${details.scannedLines} scanned log lines. Run \`dss job get ${a[0]}\` for the job-level and per-activity errors, or \`dss job log ${a[0]}\` without --errors-only for the full log.`,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
292
342
|
if (!outputFile)
|
|
293
343
|
return tailJobLogForStdout(log, requestedLines ?? DEFAULT_STDOUT_LOG_LINES);
|
|
294
344
|
const outputPath = resolve(outputFile);
|
|
295
|
-
await
|
|
296
|
-
await writeFile(outputPath, log.endsWith("\n") ? log : `${log}\n`, "utf-8");
|
|
345
|
+
await Bun.write(outputPath, log.endsWith("\n") ? log : `${log}\n`);
|
|
297
346
|
return outputPath;
|
|
298
347
|
},
|
|
299
348
|
description: "Get public API job log output (last 100 lines by default; --max-lines N, 0 = all); find why a job failed. Use --errors-only for error/traceback lines and --output PATH to write it to a file (stdout returns the path). --log-id is accepted for UI parity; API keys cannot read browser-only cat-activity-log files.",
|
|
@@ -349,7 +398,7 @@ export const jobCommands = withUsage("job", {
|
|
|
349
398
|
};
|
|
350
399
|
}
|
|
351
400
|
if (f["wait"] === true) {
|
|
352
|
-
const result = await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
|
|
401
|
+
const result = withWaitResume(await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, }), pk);
|
|
353
402
|
await warnIfBuiltDatasetHasNoColumns(c, a[0], options.targetType, result.success, pk);
|
|
354
403
|
return result;
|
|
355
404
|
}
|
|
@@ -389,7 +438,7 @@ export const jobCommands = withUsage("job", {
|
|
|
389
438
|
method: "POST",
|
|
390
439
|
};
|
|
391
440
|
}
|
|
392
|
-
const result = await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
|
|
441
|
+
const result = withWaitResume(await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, }), pk);
|
|
393
442
|
await warnIfBuiltDatasetHasNoColumns(c, a[0], options.targetType, result.success, pk);
|
|
394
443
|
return result;
|
|
395
444
|
},
|
|
@@ -402,17 +451,19 @@ export const jobCommands = withUsage("job", {
|
|
|
402
451
|
],
|
|
403
452
|
},
|
|
404
453
|
wait: {
|
|
405
|
-
handler: (c, a, f) => {
|
|
454
|
+
handler: async (c, a, f) => {
|
|
406
455
|
requireArgs(a, 1, commandUsage("job", "wait"));
|
|
407
|
-
|
|
456
|
+
const projectKey = f["project-key"];
|
|
457
|
+
const result = await c.jobs.wait(a[0], {
|
|
408
458
|
includeLogs: f["include-logs"] === true,
|
|
409
459
|
logFilter: jobLogFilterFromFlag(f["log-filter"]),
|
|
410
460
|
maxLogLines: maxLogLinesFromFlags(f),
|
|
411
461
|
pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
|
|
412
462
|
timeoutMs: num(f["timeout"], "--timeout"),
|
|
413
463
|
summary: f["summary"] === true,
|
|
414
|
-
projectKey
|
|
464
|
+
projectKey,
|
|
415
465
|
});
|
|
466
|
+
return withWaitResume(result, projectKey);
|
|
416
467
|
},
|
|
417
468
|
description: "Wait for an existing job to complete.",
|
|
418
469
|
examples: [
|
|
@@ -432,7 +483,7 @@ export const jobCommands = withUsage("job", {
|
|
|
432
483
|
summary: f["summary"] !== false,
|
|
433
484
|
projectKey: f["project-key"],
|
|
434
485
|
};
|
|
435
|
-
const jobs = await Promise.all(a.map((jobId) => c.jobs.wait(jobId, options)));
|
|
486
|
+
const jobs = await Promise.all(a.map(async (jobId) => withWaitResume(await c.jobs.wait(jobId, options), options.projectKey)));
|
|
436
487
|
return aggregateJobWaitResults(jobs, String(f["until"] ?? "all-done"));
|
|
437
488
|
},
|
|
438
489
|
description: "Monitor one or more existing jobs and summarize progress counters from logs.",
|
|
@@ -450,7 +501,7 @@ export const jobCommands = withUsage("job", {
|
|
|
450
501
|
summary: true,
|
|
451
502
|
projectKey: f["project-key"],
|
|
452
503
|
};
|
|
453
|
-
const jobs = await Promise.all(a.map((jobId) => c.jobs.wait(jobId, options)));
|
|
504
|
+
const jobs = await Promise.all(a.map(async (jobId) => withWaitResume(await c.jobs.wait(jobId, options), options.projectKey)));
|
|
454
505
|
return aggregateJobWaitResults(jobs, String(f["until"] ?? "all-done"));
|
|
455
506
|
},
|
|
456
507
|
description: "Watch one or more existing jobs with progress extraction enabled.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { readFile, } from "node:fs/promises";
|
|
2
2
|
import { validatedPluginGitUrl, validatePluginDestinationPath, validatePluginId, validatePluginName, validatePluginPath, } from "../../resources/plugins.js";
|
|
3
3
|
import { writeResponseToFile, } from "../../utils/response-file.js";
|
|
4
|
-
import { num, readStdinText, sha256Hex, } from "../coerce.js";
|
|
4
|
+
import { num, readStdinText, readTextFile, sha256Hex, } from "../coerce.js";
|
|
5
5
|
import { executionMode, } from "../flags.js";
|
|
6
6
|
import { planResult, } from "../output.js";
|
|
7
7
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
@@ -281,7 +281,7 @@ export const pluginCommands = withUsage("plugin", {
|
|
|
281
281
|
requireArgs(a, 1, SETTINGS_SET_USAGE);
|
|
282
282
|
const pluginId = validatePluginId(a[0]);
|
|
283
283
|
const mode = executionMode(f);
|
|
284
|
-
const settings = readSettingsInput(f);
|
|
284
|
+
const settings = await readSettingsInput(f);
|
|
285
285
|
if (mode.dryRun) {
|
|
286
286
|
return pluginPlan("settings-set", {
|
|
287
287
|
method: "POST",
|
|
@@ -330,7 +330,7 @@ export const pluginCommands = withUsage("plugin", {
|
|
|
330
330
|
},
|
|
331
331
|
description: "Create the code env of a plugin. Returns the DSS future (jobId); --wait polls it to completion.",
|
|
332
332
|
examples: [
|
|
333
|
-
"dss plugin code-env-create my-plugin --python-interpreter
|
|
333
|
+
"dss plugin code-env-create my-plugin --python-interpreter PYTHON311",
|
|
334
334
|
"dss plugin code-env-create my-plugin --conda --wait --timeout 300000",
|
|
335
335
|
],
|
|
336
336
|
},
|
|
@@ -629,7 +629,7 @@ export const pluginCommands = withUsage("plugin", {
|
|
|
629
629
|
});
|
|
630
630
|
}
|
|
631
631
|
const content = typeof f["file"] === "string"
|
|
632
|
-
?
|
|
632
|
+
? await readFile(f["file"])
|
|
633
633
|
: typeof f["content"] === "string"
|
|
634
634
|
? f["content"]
|
|
635
635
|
: await readStdinText();
|
|
@@ -738,10 +738,10 @@ function normalizeCreationMode(value) {
|
|
|
738
738
|
}
|
|
739
739
|
throw new UsageError("--creation-mode must be one of EMPTY, GIT_CLONE, GIT_EXPORT.", "invalid_enum");
|
|
740
740
|
}
|
|
741
|
-
function readSettingsInput(flags) {
|
|
741
|
+
async function readSettingsInput(flags) {
|
|
742
742
|
let text;
|
|
743
743
|
if (typeof flags["file"] === "string") {
|
|
744
|
-
text =
|
|
744
|
+
text = await readTextFile(flags["file"]);
|
|
745
745
|
}
|
|
746
746
|
else if (typeof flags["content"] === "string") {
|
|
747
747
|
text = flags["content"];
|
|
@@ -159,7 +159,7 @@ export const projectLibraryCommands = withUsage("project-library", {
|
|
|
159
159
|
const content = typeof f["content"] === "string"
|
|
160
160
|
? f["content"]
|
|
161
161
|
: typeof f["file"] === "string"
|
|
162
|
-
? fs.
|
|
162
|
+
? await fs.promises.readFile(f["file"])
|
|
163
163
|
: await readStdinText();
|
|
164
164
|
const result = await c.projectLibrary.addOrUpdateFile(a[0], content, pk, expectSha256 !== undefined ? { expectSha256, } : undefined);
|
|
165
165
|
return {
|
|
@@ -184,7 +184,7 @@ export const projectLibraryCommands = withUsage("project-library", {
|
|
|
184
184
|
const local = typeof f["content"] === "string"
|
|
185
185
|
? f["content"]
|
|
186
186
|
: typeof f["file"] === "string"
|
|
187
|
-
? fs.
|
|
187
|
+
? await fs.promises.readFile(f["file"])
|
|
188
188
|
: await readStdinText();
|
|
189
189
|
return c.projectLibrary.diffFile(a[0], local, pk, maxLines !== undefined ? { maxLines, } : {});
|
|
190
190
|
},
|
|
@@ -4,11 +4,13 @@ import { inspectProjectArchive, } from "../../utils/project-archive.js";
|
|
|
4
4
|
import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
|
|
5
5
|
import { jsonInput, num, requiredJsonInput, requiredStringFlag, } from "../coerce.js";
|
|
6
6
|
import { executionMode, } from "../flags.js";
|
|
7
|
-
import { CommandResultFailure, } from "../output.js";
|
|
7
|
+
import { CommandResultFailure, enqueueCliWarning, } from "../output.js";
|
|
8
8
|
import { compactProjectMapOutput, } from "../project-map-output.js";
|
|
9
9
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
10
10
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
11
11
|
const INSPECT_ARCHIVE_USAGE = commandUsage("project", "inspect-archive");
|
|
12
|
+
/** Maximum credential inventory entries echoed in the export warning. */
|
|
13
|
+
const EXPORT_CREDENTIAL_INVENTORY_LIMIT = 100;
|
|
12
14
|
function flowRenderFormat(value) {
|
|
13
15
|
if (value === undefined)
|
|
14
16
|
return undefined;
|
|
@@ -162,9 +164,24 @@ export const projectCommands = withUsage("project", {
|
|
|
162
164
|
if (!out)
|
|
163
165
|
throw new UsageError("--output PATH is required.", "missing_required_flag");
|
|
164
166
|
await c.projects.exportArchive(a[0], out, jsonInput(f));
|
|
165
|
-
|
|
167
|
+
const inspection = await inspectProjectArchive(out);
|
|
168
|
+
const findings = inspection.credentialFindings;
|
|
169
|
+
if (findings.length > 0) {
|
|
170
|
+
enqueueCliWarning({
|
|
171
|
+
code: "export_contains_credentials",
|
|
172
|
+
message: `The exported archive '${out}' contains ${findings.length} credential-bearing structure(s) `
|
|
173
|
+
+ "(member paths and key names only; values are never printed). "
|
|
174
|
+
+ "DSS exports credentials, API keys and secret-named variables verbatim.",
|
|
175
|
+
hint: "Sanitize the archive before sharing or uploading it. Overwriting an object in versioned storage does not erase its retained versions: "
|
|
176
|
+
+ "once an unsanitized archive is uploaded, rotate the exposed credentials.",
|
|
177
|
+
total: findings.length,
|
|
178
|
+
shown: Math.min(findings.length, EXPORT_CREDENTIAL_INVENTORY_LIMIT),
|
|
179
|
+
findings: findings.slice(0, EXPORT_CREDENTIAL_INVENTORY_LIMIT),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return { path: out, credentialFindings: findings.length, };
|
|
166
183
|
},
|
|
167
|
-
description: "Export a project to a local archive (.zip).",
|
|
184
|
+
description: "Export a project to a local archive (.zip). The written archive is inspected locally; credential-bearing structures (apikeys.json, secret-named variables, apiKey/token/password fields) raise an export_contains_credentials warning listing member paths and key names only, and the result reports credentialFindings. The archive is not redacted.",
|
|
168
185
|
examples: ["dss project export MY_PROJ --output ./my_proj.zip",],
|
|
169
186
|
},
|
|
170
187
|
import: {
|
|
@@ -202,7 +219,7 @@ export const projectCommands = withUsage("project", {
|
|
|
202
219
|
"inspect-archive": {
|
|
203
220
|
handler: async (_c, a) => inspectArchiveCommand(a),
|
|
204
221
|
localHandler: inspectArchiveCommand,
|
|
205
|
-
description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues, and the source project key. Never contacts DSS.",
|
|
222
|
+
description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues (including credential_structure and residue_scan_skipped warnings), a credentialFindings inventory of credential-bearing member paths and key names (values are never reported), and the source project key. Never contacts DSS.",
|
|
206
223
|
examples: ["dss project inspect-archive ./my_proj.zip",],
|
|
207
224
|
},
|
|
208
225
|
"permissions-set": {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { readFileSync, } from "node:fs";
|
|
2
|
-
import { writeFile, } from "node:fs/promises";
|
|
3
1
|
import { join, } from "node:path";
|
|
4
2
|
import { ClientValidationError, DataikuError, } from "../../errors.js";
|
|
5
3
|
import { recipeCreateWarnings, recipeOutputSchemaIsComputable, schemaUpdateComputableId, schemaUpdateIsPending, } from "../../resources/recipe-create.js";
|
|
6
|
-
import {
|
|
7
|
-
import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
|
|
4
|
+
import { buildRecipeUpdate, } from "../../resources/recipes.js";
|
|
5
|
+
import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, readTextFile, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
|
|
8
6
|
import { executionMode, } from "../flags.js";
|
|
9
7
|
import { moveCreatedItemsToZone, resolveFlowZoneIdFromFlags, } from "../helpers/flow-zone.js";
|
|
10
8
|
import { ensureRecipeBackupDir, readRecipeBackup, recipeBackupDocument, recipeBackupPath, recipeCodeEnv, recipeGraph, recipeInputItemRef, recipeRoleInputItems, recipeRunShouldWait, writeRecipeBackup, } from "../helpers/recipe.js";
|
|
@@ -12,9 +10,9 @@ import { encodedProjectEndpoint, enqueueCliWarning, readIfExists, skipResult, }
|
|
|
12
10
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
13
11
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
14
12
|
/** Read a local recipe file before any DSS request, mapping read failures to usage validation. */
|
|
15
|
-
function readRecipeFile(filePath, flagName) {
|
|
13
|
+
async function readRecipeFile(filePath, flagName) {
|
|
16
14
|
try {
|
|
17
|
-
return
|
|
15
|
+
return await readTextFile(filePath);
|
|
18
16
|
}
|
|
19
17
|
catch (error) {
|
|
20
18
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -507,7 +505,7 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
507
505
|
throw new UsageError(`--file is required. Usage: ${commandUsage("recipe", "diff")}`);
|
|
508
506
|
}
|
|
509
507
|
// Read and validate the local file before any DSS request.
|
|
510
|
-
const localContent = readRecipeFile(filePath, "--file");
|
|
508
|
+
const localContent = await readRecipeFile(filePath, "--file");
|
|
511
509
|
const result = await c.recipes.get(a[0], {
|
|
512
510
|
includePayload: true,
|
|
513
511
|
projectKey: f["project-key"],
|
|
@@ -530,23 +528,17 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
530
528
|
const pk = f["project-key"];
|
|
531
529
|
if (executionMode(f).dryRun) {
|
|
532
530
|
const current = await c.recipes.get(a[0], { projectKey: pk, includePayload: true, });
|
|
533
|
-
const
|
|
534
|
-
const next = {
|
|
535
|
-
...current,
|
|
536
|
-
...data,
|
|
537
|
-
recipe: deepMerge(currentRecipe, (data.recipe && typeof data.recipe === "object" && !Array.isArray(data.recipe))
|
|
538
|
-
? data.recipe
|
|
539
|
-
: {}),
|
|
540
|
-
};
|
|
531
|
+
const next = buildRecipeUpdate(current, data);
|
|
541
532
|
return { dryRun: true, action: "update", resource: "recipe", name: a[0], current, next, };
|
|
542
533
|
}
|
|
543
534
|
await c.recipes.update(a[0], data, pk);
|
|
544
535
|
return { updated: a[0], resource: "recipe", };
|
|
545
536
|
},
|
|
546
|
-
description: "Update recipe settings via JSON merge. Recipe definition fields must be nested under a top-level recipe key.",
|
|
537
|
+
description: "Update recipe settings via JSON merge. Recipe definition fields must be nested under a top-level recipe key. recipe.params.envSelection, when present, replaces the existing envSelection object (include envMode; no stale envName is kept) and is only accepted under recipe.params.",
|
|
547
538
|
examples: [
|
|
548
539
|
"dss recipe update compute_orders --data-file settings.json --dry-run",
|
|
549
|
-
'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"
|
|
540
|
+
'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"py311_env"}}}}\'',
|
|
541
|
+
'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"INHERIT"}}}}\'',
|
|
550
542
|
"cat settings.json | dss recipe update compute_orders --stdin",
|
|
551
543
|
],
|
|
552
544
|
},
|
|
@@ -636,7 +628,7 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
636
628
|
projectKey: f["project-key"],
|
|
637
629
|
});
|
|
638
630
|
if (typeof f["output"] === "string") {
|
|
639
|
-
await
|
|
631
|
+
await Bun.write(f["output"], payload);
|
|
640
632
|
return f["output"];
|
|
641
633
|
}
|
|
642
634
|
return payload;
|
|
@@ -663,7 +655,7 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
663
655
|
const filePath = f["file"];
|
|
664
656
|
if (!filePath)
|
|
665
657
|
throw new UsageError("--file is required.");
|
|
666
|
-
const content = readRecipeFile(filePath, "--file");
|
|
658
|
+
const content = await readRecipeFile(filePath, "--file");
|
|
667
659
|
const pk = f["project-key"];
|
|
668
660
|
const shouldBackup = f["no-backup"] !== true;
|
|
669
661
|
const backupDir = shouldBackup
|
|
@@ -233,11 +233,9 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
233
233
|
const outputFile = f["output"]
|
|
234
234
|
?? f["output-file"];
|
|
235
235
|
if (outputFile) {
|
|
236
|
-
const {
|
|
237
|
-
const { dirname, resolve, } = await import("node:path");
|
|
236
|
+
const { resolve, } = await import("node:path");
|
|
238
237
|
const outputPath = resolve(outputFile);
|
|
239
|
-
await
|
|
240
|
-
await writeFile(outputPath, result.text, "utf-8");
|
|
238
|
+
await Bun.write(outputPath, result.text);
|
|
241
239
|
return { path: outputPath, truncated: result.truncated, };
|
|
242
240
|
}
|
|
243
241
|
return result.text;
|