dataiku-sdk 3.1.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 +124 -12
- package/dist/build-metadata.json +1 -1
- 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 +5 -3
- package/dist/src/cli/commands/app.js +4 -3
- package/dist/src/cli/commands/bundle.js +2 -1
- package/dist/src/cli/commands/code-env.js +10 -9
- 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 +9 -8
- package/dist/src/cli/commands/doctor.js +4 -2
- package/dist/src/cli/commands/flow-zone.js +9 -8
- 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 +11 -10
- package/dist/src/cli/commands/project-library.js +7 -6
- package/dist/src/cli/commands/project.js +3 -2
- package/dist/src/cli/commands/recipe.js +14 -13
- 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.js +3 -2
- 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 +2 -1
- package/dist/src/cli/commands/wiki.js +7 -6
- package/dist/src/cli/contract.d.ts +1 -1
- package/dist/src/cli/contract.js +100 -82
- package/dist/src/cli/flags.d.ts +4 -0
- package/dist/src/cli/flags.js +20 -1
- package/dist/src/cli.js +92 -68
- package/dist/src/client.d.ts +3 -1
- package/dist/src/client.js +78 -21
- package/dist/src/errors.js +2 -0
- package/dist/src/resources/folders.js +2 -1
- package/dist/src/resources/projects.d.ts +0 -2
- package/dist/src/resources/projects.js +3 -17
- package/dist/src/resources/sql.js +1 -1
- package/dist/src/skill.d.ts +13 -4
- package/dist/src/skill.js +75 -24
- package/dist/src/utils/response-file.d.ts +2 -0
- package/dist/src/utils/response-file.js +34 -0
- package/package.json +6 -4
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +44 -224
- 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
package/README.md
CHANGED
|
@@ -23,12 +23,99 @@ Bun is the only supported runtime and the package manager. Examples below assume
|
|
|
23
23
|
`--no-env-file` disables Bun's automatic preloading only; the CLI still applies its documented `.env` handling unless `DATAIKU_DISABLE_ENV=1` is set.
|
|
24
24
|
`dss version` reports whether the running code came from `source` or `dist`, the runtime (always `bun`), the packaged build revision when available, and `staleBuild` when that revision differs from the checkout. Release builds generate `dist/build-metadata.json`; source snapshots without Git metadata still build but report no build revision.
|
|
25
25
|
|
|
26
|
+
## Self-provisioning live tests
|
|
27
|
+
|
|
28
|
+
From a checkout, `test:live` is a separate Bun script, not a `dss` command. It reads
|
|
29
|
+
the existing `.env` without changing it and needs project-creation access plus a
|
|
30
|
+
writable managed storage connection. No imported tutorials are required.
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
bun run test:live setup
|
|
34
|
+
bun run test:live run --case core.dataset.baseline
|
|
35
|
+
bun run test:live run --case "core.recipe.*"
|
|
36
|
+
bun run test:live run # all core cases + migrated integration suites
|
|
37
|
+
bun run test:live run --profile ml --case "ml.*"
|
|
38
|
+
bun run test:live status
|
|
39
|
+
bun run test:live clean # delete only this lab, including its root project
|
|
40
|
+
# CI: setup + run + cleanup, even when cases fail
|
|
41
|
+
bun run test:live all
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`setup` is idempotent for a ready lab; `run` reuses its datasets, graph and collaboration
|
|
45
|
+
fixtures. Cases use temporary resources or restore baseline values. Cases accept exact
|
|
46
|
+
IDs, comma-separated lists, repeated `--case`, and trailing-prefix `*` matching; quote
|
|
47
|
+
wildcards. Selected runs omit the legacy suites. Unknown, setup-only or inactive-profile
|
|
48
|
+
or empty selections fail before credentials, state creation or provisioning; `--case` is valid only
|
|
49
|
+
for `run`/`all`. `run` refuses
|
|
50
|
+
an incomplete or cleaned lab rather than silently rebuilding it. Clean failed setup
|
|
51
|
+
before provisioning a replacement. Authentication and transport failures retain their
|
|
52
|
+
original errors; they are not evidence that the lab is missing.
|
|
53
|
+
|
|
54
|
+
Core fixtures include deterministic CSVs with nulls, duplicates, Unicode and quoting;
|
|
55
|
+
server-provisioned managed datasets; sync, Prepare, join, fuzzy-join, grouping and Python
|
|
56
|
+
recipes; folders, scenarios, variables, wiki, notebooks, dashboards, insights, libraries,
|
|
57
|
+
metrics and quality rules. Project export/import/duplicate cases use separate owned
|
|
58
|
+
projects. Managed dataset types, paths and recipe output schemas come from DSS rather
|
|
59
|
+
than tutorial-specific connection guesses.
|
|
60
|
+
|
|
61
|
+
Profiles add to core: `ml` trains and deploys a small decision tree and checks clustering;
|
|
62
|
+
`applications` requires `DATAIKU_LIVE_APP_TEMPLATE_ID`; `infrastructure` requires
|
|
63
|
+
`DATAIKU_SQL_CONNECTION` or `DATAIKU_SQL_DATASET_FULL_NAME` for read-only SQL.
|
|
64
|
+
The SQL case permits only the fixed, table-free `SELECT 1 AS one` probe with an
|
|
65
|
+
explicit target and an owned project whose incarnation is checked before execution.
|
|
66
|
+
It does not authorize arbitrary SQL, alternate query inputs or global mutations. Missing
|
|
67
|
+
prerequisites are recorded as **blocked**, never passed. Required blocked cases exit
|
|
68
|
+
nonzero; optional blocked cases remain visible in reports. Global administration,
|
|
69
|
+
external Git mutations and unsupported capabilities are not silently exercised.
|
|
70
|
+
|
|
71
|
+
For an admin-only disposable SQL target (verified on the hosted trial), create a uniquely
|
|
72
|
+
named connection with `POST /public/api/admin/connections/` and body
|
|
73
|
+
`{"name":"<unique-name>","type":"JDBC","params":{"driver":"org.sqlite.JDBC","jdbcurl":"jdbc:sqlite::memory:","properties":[]},"usableBy":"ALLOWED","allowedGroups":[],"allowWrite":false,"allowManagedDatasets":false}`.
|
|
74
|
+
Run `DATAIKU_SQL_CONNECTION=<name> bun run test:live all --profile infrastructure --case infrastructure.sql-select`.
|
|
75
|
+
In a `finally` block, verify that the connection still matches the created definition, then
|
|
76
|
+
remove it with `DELETE /public/api/admin/connections/<name>`. `all` cleans the owned projects,
|
|
77
|
+
not this separately created connection. The in-memory database creates no database file.
|
|
78
|
+
|
|
79
|
+
State is ignored by Git under `.live-tests/<server-hash>/`. Use `--state-dir PATH` for
|
|
80
|
+
another lab or `--manifest PATH` to select an existing manifest for `run`, `clean` or
|
|
81
|
+
`status` (not `setup`/`all`). Keep the manifest and
|
|
82
|
+
`cleanup.jsonl`: cleanup requires exact project identities and creation-incarnation
|
|
83
|
+
hashes, never a prefix sweep. A changed server/project identity or unconfirmed creation
|
|
84
|
+
fails closed; investigate its journal before manual recovery. Locks prevent concurrent
|
|
85
|
+
lab runs; after an uncatchable process kill, confirm its recorded process is gone before
|
|
86
|
+
removing a stale lock. Interrupting a run preserves the lab; `all` attempts cleanup.
|
|
87
|
+
|
|
88
|
+
Each iteration retains logs and `report.json` with cases, timing, executed actions,
|
|
89
|
+
capability reasons, coverage and external-project metadata integrity. Unavailable project
|
|
90
|
+
metadata is reported separately in `integrity.unreadable`, not as confirmed drift;
|
|
91
|
+
integrity remains unverified and the run fails until those reads succeed. `clean` retains
|
|
92
|
+
`cleanup-report.json`, including deletion failures; starting teardown invalidates lab
|
|
93
|
+
readiness even if cleanup only partially succeeds. If cleanup and integrity verification
|
|
94
|
+
both fail, the cleanup error remains primary and the report preserves both results.
|
|
95
|
+
Planned lifecycle commands retain ownership checks; local outputs stay inside the lab
|
|
96
|
+
for both `--output` and `--output-file`. Treat local reports and exported archives as private project
|
|
97
|
+
data. Coverage is an explicit inventory of every registered action, not a claim that
|
|
98
|
+
every action has a live test: unexercised actions remain **uncovered**. Add cases through
|
|
99
|
+
the typed catalogue in `tests/live-cases.ts`, `LiveContext.check` and guarded helpers,
|
|
100
|
+
then update `tests/live-coverage.ts` when the
|
|
101
|
+
registry changes; catalogue drift fails the offline gate. Plan/dry-run calls do not count
|
|
102
|
+
as executed live coverage.
|
|
103
|
+
|
|
104
|
+
Regular `bun run check` and `bun run lint` include the live runner and fixtures.
|
|
105
|
+
For focused verification:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
bun run check:live
|
|
109
|
+
bun test tests/live-context.test.ts tests/live-runner.test.ts tests/live-coverage.test.ts
|
|
110
|
+
```
|
|
111
|
+
|
|
26
112
|
## CLI contract
|
|
27
113
|
|
|
28
114
|
The complete agent-facing contract — stdout JSON discipline, stderr JSONL diagnostics, the error
|
|
29
115
|
envelope, exit codes, `--fields` projection, command discovery, and planning/safety rules — is the
|
|
30
|
-
|
|
31
|
-
|
|
116
|
+
skill bundle rooted at [`skills/dataiku-dss/SKILL.md`](skills/dataiku-dss/SKILL.md). The short
|
|
117
|
+
entrypoint routes agents to focused references; the installed command registry remains the source
|
|
118
|
+
of truth for command syntax and schemas. Human
|
|
32
119
|
essentials: exactly one compact JSON value on stdout per command (void success is `{ok:true}`),
|
|
33
120
|
failures as one structured JSON error object on stdout with a nonzero exit code, stderr reserved
|
|
34
121
|
for JSONL diagnostics only, and exit codes `0` success / `1` usage error / `2` DSS or internal
|
|
@@ -46,8 +133,7 @@ dss commands run --output commands.json
|
|
|
46
133
|
dss agent contract --fields protocol,agentContractVersion,cli,stdio,planning,compatibility
|
|
47
134
|
```
|
|
48
135
|
|
|
49
|
-
`dss commands run` prints the compact resource/action summary; registry entries
|
|
50
|
-
exported) never dump to stdout — they travel only via `--output PATH`. See the canonical skill for
|
|
136
|
+
`dss commands run` prints the compact resource/action summary; registry entries never dump to stdout — they travel only via `--output PATH`. See the canonical skill for
|
|
51
137
|
the full `--fields` projection rules and planning workflow.
|
|
52
138
|
|
|
53
139
|
## Agent skill installation
|
|
@@ -61,7 +147,7 @@ dss install-skill --global --agent omp
|
|
|
61
147
|
|
|
62
148
|
`--list-agents` only reports targetable agents; it does not write files. Auto-detection checks supported agent binaries/config directories (`claude`, `codex`, `cursor`, `pi`, `omp`). Passing `--agent NAME` forces one entry and reports `via:"flag"`.
|
|
63
149
|
|
|
64
|
-
Project installs write `SKILL.md` under the target workspace:
|
|
150
|
+
Project installs write `SKILL.md` and its sibling `references/` directory under the target workspace:
|
|
65
151
|
|
|
66
152
|
- Claude: `.claude/skills/dataiku-dss/SKILL.md`
|
|
67
153
|
- Codex: `.codex/skills/dataiku-dss/SKILL.md`
|
|
@@ -69,16 +155,42 @@ Project installs write `SKILL.md` under the target workspace:
|
|
|
69
155
|
- Pi: `.pi/skills/dataiku-dss/SKILL.md`
|
|
70
156
|
- OMP: `.omp/skills/dataiku-dss/SKILL.md`
|
|
71
157
|
|
|
72
|
-
Global installs write under the agent's home config path, for example OMP: `~/.omp/agent/skills/dataiku-dss/SKILL.md`.
|
|
158
|
+
Global installs write under the agent's home config path, for example OMP: `~/.omp/agent/skills/dataiku-dss/SKILL.md`. The reported `target` is the home directory for global installs and the resolved workspace directory for project installs; `installed[].path` names each entrypoint.
|
|
159
|
+
|
|
160
|
+
Each entry in the `installed` array reports pre-install bundle state:
|
|
161
|
+
|
|
162
|
+
- `status`: `missing` when the entrypoint is absent; `stale` when any bundled file is missing or differs; `current` when every bundled file matches.
|
|
163
|
+
- `changed`: whether any bundled file would (or did) change. Byte-identical files are not rewritten.
|
|
164
|
+
- `expectedSha256` and `actualSha256`: the canonical and existing **entrypoint** hashes. The latter is absent when the entrypoint is missing.
|
|
165
|
+
- `files`: each bundled file's portable `relativePath`, destination `path`, `status`, `changed`, and hashes. Missing or stale references are detected even when `SKILL.md` itself is current.
|
|
166
|
+
|
|
167
|
+
`--dry-run` and `--plan` never write. Real installs replace changed files through same-directory temporary files and rename, writing references before the entrypoint. Atomicity is per file, not a bundle-wide transaction. Unrelated files in the destination are preserved.
|
|
168
|
+
|
|
169
|
+
### Token-efficient skill structure
|
|
170
|
+
|
|
171
|
+
The entrypoint contains bootstrap/discovery, output interpretation, and universal safety rules. Seven focused files under [references/](skills/dataiku-dss/references/) cover authentication, discovery, mutations, application releases, flow maps, coding, and troubleshooting. All are linked directly from `SKILL.md` with task-specific read conditions; agents should not preload the whole directory.
|
|
172
|
+
|
|
173
|
+
The entrypoint measures **750 o200k_base tokens**, down from **851** in the previous revision (12% less) and **4,272** before progressive disclosure. The token-budget test now caps it at **825** tokens. Claude V5 reconstruction counts fell **1,420 → 1,250** and GLM5 **858 → 752**. Safety rules and all seven reference links remain. Reference content consumes context only when read; all five agent installers ship the complete bundle.
|
|
174
|
+
|
|
175
|
+
All seven references are token-budgeted (each measured `o200k_base` baseline plus 5% headroom). Per-file baselines live in [the token-budget tests](tests/cli/agent-token-budget.test.ts); run `bun run test:tokens` to check them.
|
|
176
|
+
|
|
177
|
+
References total **4,179 → 3,545 tokens (15.2% less)**. With the 750-token entrypoint, the whole bundle totals **4,295**; this is a sum of file counts, not a recommendation to preload them. All references shrink across ten encodings; whole-bundle Claude V5 reconstruction **8,200 → 7,362**, GLM5 **4,943 → 4,305**. Runnable examples and safety conditions are retained.
|
|
178
|
+
|
|
179
|
+
This uses progressive disclosure from the [Agent Skills specification](https://agentskills.io/specification) and [Anthropic's authoring guidance](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices): concise metadata/instructions, direct relative reference links, and domain-focused documents. No scripts or assets are needed here; generated command metadata is queried from the CLI rather than duplicated in the skill.
|
|
180
|
+
|
|
181
|
+
`bun run test:tokens` enforces the pinned OpenAI `o200k_base` budgets. To also check ten native encodings (OpenAI, Claude, GLM, Qwen, DeepSeek, and Kimi), point the optional development-only gate at an OMP native module:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
DSS_TOKENIZER_MODULE=/absolute/path/to/oh-my-pi/packages/natives/native/index.js bun run test:tokens
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Without this variable, only the native cross-model case is skipped. A configured missing/incompatible module fails rather than estimating tokens. The cross-model gate permits 10% growth over its recorded text baselines for bootstrap and action discovery; the pinned OpenAI gate also enforces the skill ceiling above. Claude counts use ctok reconstructions, not an official tokenizer, and all counts exclude provider message/tool framing. No native dependency is added to the CLI.
|
|
73
188
|
|
|
74
|
-
|
|
189
|
+
Measured full `dataset.create` discovery versus its `usage,description,flags,examples` projection: OpenAI **1,059 → 356**, Claude V5 reconstruction **1,771 → 640**, GLM5 **1,015 → 347** tokens. Use the projection when those fields suffice; fetch the full action when schema details are needed. This is selective disclosure, not lossless compression.
|
|
75
190
|
|
|
76
|
-
|
|
77
|
-
- `changed`: whether the install writes the file — `true` for `missing` and `stale`, `false` for `current`; installs skip byte-identical files and replace missing/stale files through a same-directory temporary file followed by rename.
|
|
78
|
-
- `expectedSha256`: SHA-256 of the canonical skill bytes this SDK ships.
|
|
79
|
-
- `actualSha256`: SHA-256 of the file currently at the destination; present only when the file exists.
|
|
191
|
+
Boolean flags accept bare flags or explicit `=true`/`=false` values, including aliases. Invalid boolean values fail before dispatch. Direct, batch, and local commands share execution-mode resolution; `--plan --dry-run` returns a plan with `plannedAndDryRun:true` when both modes are supported. Unreadable JSON/text payload files produce `validation_failed` usage errors (exit 1) with the flag, path, and filesystem cause.
|
|
80
192
|
|
|
81
|
-
`--
|
|
193
|
+
For raw streaming downloads, `--request-timeout MS` bounds each pending body read, not the total transfer duration: large progressing transfers remain supported. Buffered text/JSON consumers retain their bounded body-read budget. Request retries do not replay partially consumed streams. Project, bundle, and API-service archives and managed-folder downloads are written to private temporary files beside the destination, then renamed on success. Failures leave no partial output and preserve any existing destination.
|
|
82
194
|
|
|
83
195
|
## Credentials
|
|
84
196
|
|
package/dist/build-metadata.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"format":1,"buildRevision":"
|
|
1
|
+
{"format":1,"buildRevision":"aa9d13ec085be8f8637265cabcc8466df4817c88"}
|
package/dist/src/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DataikuClient, } from "./client.js";
|
|
2
1
|
import { DataikuError, } from "./errors.js";
|
|
3
2
|
export async function validateCredentials(url, apiKey, options = {}) {
|
|
4
3
|
try {
|
|
4
|
+
const { DataikuClient, } = await import("./client.js");
|
|
5
5
|
const client = new DataikuClient({
|
|
6
6
|
url,
|
|
7
7
|
apiKey,
|
package/dist/src/cli/coerce.js
CHANGED
|
@@ -169,6 +169,15 @@ function selectSingleSource(flags, sourceFlags) {
|
|
|
169
169
|
}
|
|
170
170
|
return provided[0];
|
|
171
171
|
}
|
|
172
|
+
function readInputText(path, flag) {
|
|
173
|
+
try {
|
|
174
|
+
return readFileSync(path, "utf-8");
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const cause = error.code;
|
|
178
|
+
throw new UsageError(`Could not read ${flag} file: ${path}`, "validation_failed", "Verify that the path names an existing, readable file.", { flag, path, ...(cause ? { cause, } : {}), });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
172
181
|
function resolveJsonSourceText(flags, kind) {
|
|
173
182
|
const source = selectSingleSource(flags, JSON_SOURCE_FLAGS);
|
|
174
183
|
if (source === undefined)
|
|
@@ -182,7 +191,7 @@ function resolveJsonSourceText(flags, kind) {
|
|
|
182
191
|
if (source === "data-file") {
|
|
183
192
|
const fileValue = flags["data-file"];
|
|
184
193
|
if (typeof fileValue === "string") {
|
|
185
|
-
const jsonText =
|
|
194
|
+
const jsonText = readInputText(fileValue, "--data-file");
|
|
186
195
|
return kind === "object"
|
|
187
196
|
? parseJsonObject(jsonText, fileValue)
|
|
188
197
|
: parseJsonValue(jsonText, fileValue);
|
|
@@ -241,7 +250,7 @@ export function textInput(flags) {
|
|
|
241
250
|
if (source === "file") {
|
|
242
251
|
const fileValue = flags["file"];
|
|
243
252
|
if (typeof fileValue === "string")
|
|
244
|
-
return
|
|
253
|
+
return readInputText(fileValue, "--file");
|
|
245
254
|
}
|
|
246
255
|
const contentValue = flags["content"];
|
|
247
256
|
if (typeof contentValue === "string")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requiredStringFlag, } from "../coerce.js";
|
|
2
|
+
import { executionMode, } from "../flags.js";
|
|
2
3
|
import { readIfExists, skipResult, } from "../output.js";
|
|
3
4
|
import { requireArgs, } from "../usage.js";
|
|
4
5
|
export const analysisCommands = {
|
|
@@ -23,7 +24,7 @@ export const analysisCommands = {
|
|
|
23
24
|
const inputDataset = requiredStringFlag(f, "input-dataset", usage);
|
|
24
25
|
const projectKey = f["project-key"];
|
|
25
26
|
const options = { inputDataset, projectKey, };
|
|
26
|
-
if (f
|
|
27
|
+
if (executionMode(f).dryRun) {
|
|
27
28
|
return {
|
|
28
29
|
dryRun: true,
|
|
29
30
|
action: "create",
|
|
@@ -45,11 +46,11 @@ export const analysisCommands = {
|
|
|
45
46
|
handler: async (c, a, f) => {
|
|
46
47
|
requireArgs(a, 1, "dss analysis delete <analysisId> [--if-exists] [--dry-run] [--project-key KEY]");
|
|
47
48
|
const projectKey = f["project-key"];
|
|
48
|
-
if (f
|
|
49
|
+
if (executionMode(f).dryRun || f["if-exists"] === true) {
|
|
49
50
|
const current = await readIfExists(() => c.analyses.get(a[0], projectKey));
|
|
50
51
|
if (!current)
|
|
51
52
|
return skipResult("analysis", a[0], "missing");
|
|
52
|
-
if (f
|
|
53
|
+
if (executionMode(f).dryRun) {
|
|
53
54
|
return { dryRun: true, action: "delete", resource: "analysis", id: a[0], current, };
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ClientValidationError, } from "../../errors.js";
|
|
2
|
+
import { writeResponseToFile, } from "../../utils/response-file.js";
|
|
2
3
|
import { stableHash, } from "../../utils/stable-hash.js";
|
|
3
4
|
import { requiredJsonInput, } from "../coerce.js";
|
|
5
|
+
import { executionMode, } from "../flags.js";
|
|
4
6
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
5
7
|
function validateExpectHash(value) {
|
|
6
8
|
if (value === undefined || value === false)
|
|
@@ -42,7 +44,7 @@ export const apiServiceCommands = {
|
|
|
42
44
|
const body = requiredJsonInput(f, "--data, --data-file, or --stdin is required (service settings).");
|
|
43
45
|
const projectKey = f["project-key"];
|
|
44
46
|
const expectHash = validateExpectHash(f["expect-hash"]);
|
|
45
|
-
if (f
|
|
47
|
+
if (executionMode(f).dryRun) {
|
|
46
48
|
const current = await c.apiServices.getSettings(a[0], projectKey);
|
|
47
49
|
const currentHash = stableHash(current);
|
|
48
50
|
if (expectHash !== undefined && currentHash !== expectHash.toLowerCase()) {
|
|
@@ -81,7 +83,7 @@ export const apiServiceCommands = {
|
|
|
81
83
|
type: "STD_PREDICTION",
|
|
82
84
|
modelRef: a[2],
|
|
83
85
|
};
|
|
84
|
-
if (f
|
|
86
|
+
if (executionMode(f).dryRun)
|
|
85
87
|
return endpoint;
|
|
86
88
|
return c.apiServices.addPredictionEndpoint(a[0], a[1], a[2], f["project-key"]);
|
|
87
89
|
},
|
|
@@ -143,7 +145,7 @@ export const apiServiceCommands = {
|
|
|
143
145
|
if (!res.body) {
|
|
144
146
|
throw new Error("apiServices.downloadPackageArchive response did not include a body");
|
|
145
147
|
}
|
|
146
|
-
const bytes = await
|
|
148
|
+
const bytes = await writeResponseToFile(out, res);
|
|
147
149
|
return { path: out, bytes, };
|
|
148
150
|
},
|
|
149
151
|
usage: "dss api-service download-package <serviceId> <packageId> --output PATH [--project-key KEY]",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ClientValidationError, DataikuError, } from "../../errors.js";
|
|
2
2
|
import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
|
|
3
3
|
import { jsonInput, num, parseBooleanOption, plainRecord, requiredJsonInput, requiredStringFlag, stringField, } from "../coerce.js";
|
|
4
|
+
import { executionMode, } from "../flags.js";
|
|
4
5
|
import { appPermissionsHash, appPermissionsVerificationError, assertAppPermissionsSnapshotBinding, buildAppPermissionsSnapshot, diffAppPermissions, readAppPermissionsSnapshot, writeAppPermissionsSnapshot, } from "../helpers/app-permissions.js";
|
|
5
6
|
import { createSuccessorInstance, futureTargetIdentity, isDefinitiveRejection, preflightSuccessorInstance, requireAbsentProjectTarget, safeErrorSummary, verifyAppInstance, VISUAL_UI_GATE, } from "../helpers/app-successor.js";
|
|
6
7
|
import { CommandResultFailure, } from "../output.js";
|
|
@@ -385,7 +386,7 @@ export const appCommands = {
|
|
|
385
386
|
to: requiredStringFlag(f, "to", CREATE_SUCCESSOR_USAGE),
|
|
386
387
|
...(f["name"] !== undefined ? { name: f["name"], } : {}),
|
|
387
388
|
copyPermissions: parseBooleanOption(f["copy-permissions"], "--copy-permissions") ?? false,
|
|
388
|
-
dryRun:
|
|
389
|
+
dryRun: executionMode(f).dryRun,
|
|
389
390
|
timeoutMs: num(f["timeout"], "--timeout"),
|
|
390
391
|
pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
|
|
391
392
|
}, CREATE_SUCCESSOR_USAGE);
|
|
@@ -445,7 +446,7 @@ export const appCommands = {
|
|
|
445
446
|
...(version !== undefined ? { version, } : {}),
|
|
446
447
|
...(versionNotes !== undefined ? { versionNotes, } : {}),
|
|
447
448
|
...(f["expect-hash"] !== undefined ? { expectHash: f["expect-hash"], } : {}),
|
|
448
|
-
dryRun:
|
|
449
|
+
dryRun: executionMode(f).dryRun,
|
|
449
450
|
}, f["project-key"]);
|
|
450
451
|
if (result.outcome === "indeterminate") {
|
|
451
452
|
// The PUT may or may not have landed. Fail nonzero without
|
|
@@ -756,7 +757,7 @@ export const appCommands = {
|
|
|
756
757
|
assertAppPermissionsSnapshotBinding(snapshot, projectKey, c.getBaseUrl());
|
|
757
758
|
const currentProjectIncarnationHash = await requireProjectIncarnationHash(c, projectKey);
|
|
758
759
|
assertAppPermissionsSnapshotBinding(snapshot, projectKey, c.getBaseUrl(), currentProjectIncarnationHash);
|
|
759
|
-
const dryRun =
|
|
760
|
+
const dryRun = executionMode(f).dryRun;
|
|
760
761
|
const before = await c.projects.getPermissions(projectKey);
|
|
761
762
|
await assertProjectIncarnationHash(c, projectKey, snapshot.projectIncarnationHash);
|
|
762
763
|
const beforeHash = appPermissionsHash(before);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { writeResponseToFile, } from "../../utils/response-file.js";
|
|
1
2
|
import { json, parseBooleanOption, } from "../coerce.js";
|
|
2
3
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
3
4
|
export const bundleCommands = {
|
|
@@ -48,7 +49,7 @@ export const bundleCommands = {
|
|
|
48
49
|
if (!res.body) {
|
|
49
50
|
throw new Error("bundles.downloadExportedArchive response did not include a body");
|
|
50
51
|
}
|
|
51
|
-
const bytes = await
|
|
52
|
+
const bytes = await writeResponseToFile(out, res);
|
|
52
53
|
return { path: out, bytes, };
|
|
53
54
|
},
|
|
54
55
|
usage: "dss bundle download-exported <bundleId> --output PATH [--project-key KEY]",
|
|
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, } from "node:fs";
|
|
|
2
2
|
import { ClientValidationError, } from "../../errors.js";
|
|
3
3
|
import { stableHash, } from "../../utils/stable-hash.js";
|
|
4
4
|
import { json, jsonInput, num, numFlag, parseBooleanOption, } from "../coerce.js";
|
|
5
|
+
import { executionMode, } from "../flags.js";
|
|
5
6
|
import { enqueueCliWarning, isNotFoundError, readIfExists, skipResult, } from "../output.js";
|
|
6
7
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
7
8
|
const CODE_ENV_LANGS = ["PYTHON", "R",];
|
|
@@ -175,13 +176,13 @@ export const codeEnvCommands = {
|
|
|
175
176
|
}
|
|
176
177
|
const params = codeEnvParams(f);
|
|
177
178
|
const wait = codeEnvWait(f);
|
|
178
|
-
if (f["if-not-exists"] === true || f
|
|
179
|
+
if (f["if-not-exists"] === true || executionMode(f).dryRun) {
|
|
179
180
|
const existing = (await c.codeEnvs.list({ envLang, }))
|
|
180
181
|
.find((env) => env.envName === a[1]);
|
|
181
|
-
if (existing && f["if-not-exists"] === true && f
|
|
182
|
+
if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
|
|
182
183
|
return skipResult("code-env", a[1], "exists", { envLang, current: existing, });
|
|
183
184
|
}
|
|
184
|
-
if (f
|
|
185
|
+
if (executionMode(f).dryRun) {
|
|
185
186
|
return {
|
|
186
187
|
dryRun: true,
|
|
187
188
|
action: "create",
|
|
@@ -224,7 +225,7 @@ export const codeEnvCommands = {
|
|
|
224
225
|
}
|
|
225
226
|
const expectHash = validateExpectHash(f["expect-hash"]);
|
|
226
227
|
const definitionHash = stableHash(definition);
|
|
227
|
-
if (f
|
|
228
|
+
if (executionMode(f).dryRun) {
|
|
228
229
|
const current = await readIfExists(() => c.codeEnvs.getDefinition(envLang, a[1]));
|
|
229
230
|
const currentHash = current ? stableHash(current) : undefined;
|
|
230
231
|
if (expectHash !== undefined && currentHash !== expectHash) {
|
|
@@ -260,7 +261,7 @@ export const codeEnvCommands = {
|
|
|
260
261
|
const packages = codeEnvPackageList(f);
|
|
261
262
|
const installCorePackages = parseBooleanOption(f["install-core-packages"], "--install-core-packages");
|
|
262
263
|
const expectHash = validateExpectHash(f["expect-hash"]);
|
|
263
|
-
if (f
|
|
264
|
+
if (executionMode(f).dryRun) {
|
|
264
265
|
return {
|
|
265
266
|
dryRun: true,
|
|
266
267
|
action: "set-packages",
|
|
@@ -297,7 +298,7 @@ export const codeEnvCommands = {
|
|
|
297
298
|
versionToUpdate,
|
|
298
299
|
wait,
|
|
299
300
|
};
|
|
300
|
-
if (f
|
|
301
|
+
if (executionMode(f).dryRun) {
|
|
301
302
|
return {
|
|
302
303
|
dryRun: true,
|
|
303
304
|
action: "update-packages",
|
|
@@ -320,7 +321,7 @@ export const codeEnvCommands = {
|
|
|
320
321
|
const envLang = requireEnvLang(a[0], usage);
|
|
321
322
|
const wait = codeEnvWait(f);
|
|
322
323
|
const envVersion = typeof f["env-version"] === "string" ? f["env-version"] : undefined;
|
|
323
|
-
if (f
|
|
324
|
+
if (executionMode(f).dryRun) {
|
|
324
325
|
return {
|
|
325
326
|
dryRun: true,
|
|
326
327
|
action: "update-images",
|
|
@@ -350,7 +351,7 @@ export const codeEnvCommands = {
|
|
|
350
351
|
throw new UsageError("--active is required. Usage: dss code-env set-jupyter <lang> <name> --active true|false");
|
|
351
352
|
}
|
|
352
353
|
const wait = codeEnvWait(f);
|
|
353
|
-
if (f
|
|
354
|
+
if (executionMode(f).dryRun) {
|
|
354
355
|
return {
|
|
355
356
|
dryRun: true,
|
|
356
357
|
action: "set-jupyter",
|
|
@@ -373,7 +374,7 @@ export const codeEnvCommands = {
|
|
|
373
374
|
requireArgs(a, 2, usage);
|
|
374
375
|
const envLang = requireEnvLang(a[0], usage);
|
|
375
376
|
const wait = codeEnvWait(f);
|
|
376
|
-
if (f
|
|
377
|
+
if (executionMode(f).dryRun) {
|
|
377
378
|
const current = await readIfExists(() => c.codeEnvs.get(envLang, a[1]));
|
|
378
379
|
if (!current)
|
|
379
380
|
return skipResult("code-env", a[1], "missing", { envLang, });
|
|
@@ -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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { deepMerge, } from "../../utils/deep-merge.js";
|
|
2
2
|
import { num, parseBooleanOption, requiredJsonInput, } from "../coerce.js";
|
|
3
|
+
import { executionMode, } from "../flags.js";
|
|
3
4
|
import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
|
|
4
5
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
5
6
|
/**
|
|
@@ -52,20 +53,20 @@ export const dataQualityCommands = {
|
|
|
52
53
|
: typeof config.displayName === "string"
|
|
53
54
|
? config.displayName
|
|
54
55
|
: undefined;
|
|
55
|
-
if (f["if-not-exists"] === true || f
|
|
56
|
+
if (f["if-not-exists"] === true || executionMode(f).dryRun) {
|
|
56
57
|
const existing = identity
|
|
57
58
|
? (await c.dataQuality.listRules(a[0], pk)).find((rule) => rule.id === identity || rule.displayName === identity)
|
|
58
59
|
: undefined;
|
|
59
|
-
if (existing && f["if-not-exists"] === true && f
|
|
60
|
+
if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
|
|
60
61
|
return skipResult("data-quality", identity ?? existing.id, "exists", {
|
|
61
62
|
dataset: a[0],
|
|
62
63
|
current: existing,
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
|
-
if (f["if-not-exists"] === true && !identity && f
|
|
66
|
+
if (f["if-not-exists"] === true && !identity && !executionMode(f).dryRun) {
|
|
66
67
|
throw new UsageError("--if-not-exists requires rule id or displayName in the rule JSON.");
|
|
67
68
|
}
|
|
68
|
-
if (f
|
|
69
|
+
if (executionMode(f).dryRun) {
|
|
69
70
|
return {
|
|
70
71
|
dryRun: true,
|
|
71
72
|
action: "create-rule",
|
|
@@ -97,7 +98,7 @@ export const dataQualityCommands = {
|
|
|
97
98
|
handler: async (c, a, f) => {
|
|
98
99
|
requireArgs(a, 2, "dss data-quality update-rule <dataset> <rule-id> --data JSON");
|
|
99
100
|
const data = requiredJsonInput(f, "--data, --data-file, or --stdin is required.");
|
|
100
|
-
if (f
|
|
101
|
+
if (executionMode(f).dryRun) {
|
|
101
102
|
const current = await c.dataQuality.getRule(a[0], a[1], f["project-key"]);
|
|
102
103
|
const next = deepMerge(current, data);
|
|
103
104
|
return {
|
|
@@ -125,11 +126,11 @@ export const dataQualityCommands = {
|
|
|
125
126
|
handler: async (c, a, f) => {
|
|
126
127
|
requireArgs(a, 2, "dss data-quality delete-rule <dataset> <rule-id>");
|
|
127
128
|
const pk = f["project-key"];
|
|
128
|
-
if (f
|
|
129
|
+
if (executionMode(f).dryRun || f["if-exists"] === true) {
|
|
129
130
|
const current = await readIfExists(() => c.dataQuality.getRule(a[0], a[1], pk));
|
|
130
131
|
if (!current)
|
|
131
132
|
return skipResult("data-quality", a[1], "missing", { dataset: a[0], });
|
|
132
|
-
if (f
|
|
133
|
+
if (executionMode(f).dryRun) {
|
|
133
134
|
return {
|
|
134
135
|
dryRun: true,
|
|
135
136
|
action: "delete-rule",
|
|
@@ -248,7 +249,7 @@ export const dataQualityCommands = {
|
|
|
248
249
|
projectKey: pk,
|
|
249
250
|
timeoutMs: num(f["timeout"], "--timeout"),
|
|
250
251
|
};
|
|
251
|
-
if (f
|
|
252
|
+
if (executionMode(f).dryRun) {
|
|
252
253
|
const params = new URLSearchParams();
|
|
253
254
|
params.set("partition", options.partition?.trim() ? options.partition : "NP");
|
|
254
255
|
if (options.ruleId !== undefined)
|
|
@@ -2,6 +2,7 @@ import { buildDatasetCloneSettings, } from "../../resources/datasets.js";
|
|
|
2
2
|
import { deepMerge, } from "../../utils/deep-merge.js";
|
|
3
3
|
import { compareStrings, stableHash, } from "../../utils/stable-hash.js";
|
|
4
4
|
import { jsonInput, num, schemaColumnsInput, unknownJsonInput, } from "../coerce.js";
|
|
5
|
+
import { executionMode, } from "../flags.js";
|
|
5
6
|
import { datasetSourceSummary, } from "../helpers/dataset.js";
|
|
6
7
|
import { moveCreatedItemsToZone, resolveFlowZoneIdFromFlags, } from "../helpers/flow-zone.js";
|
|
7
8
|
import { enqueueCliWarning, readIfExists, skipResult, } from "../output.js";
|
|
@@ -195,7 +196,7 @@ export const datasetCommands = {
|
|
|
195
196
|
requireArgs(a, 1, usage);
|
|
196
197
|
const columns = schemaColumnsInput(f, usage);
|
|
197
198
|
const pk = f["project-key"];
|
|
198
|
-
if (f
|
|
199
|
+
if (executionMode(f).dryRun) {
|
|
199
200
|
const current = await c.datasets.schema(a[0], pk);
|
|
200
201
|
return {
|
|
201
202
|
dryRun: true,
|
|
@@ -365,13 +366,13 @@ export const datasetCommands = {
|
|
|
365
366
|
projectKey: pk,
|
|
366
367
|
};
|
|
367
368
|
const zoneId = await resolveFlowZoneIdFromFlags(c, f, pk);
|
|
368
|
-
if (f["if-not-exists"] === true || f
|
|
369
|
+
if (f["if-not-exists"] === true || executionMode(f).dryRun) {
|
|
369
370
|
const list = await c.datasets.list(pk);
|
|
370
371
|
const existing = list.find((d) => d.name === name);
|
|
371
|
-
if (existing && f["if-not-exists"] === true && f
|
|
372
|
+
if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
|
|
372
373
|
return skipResult("dataset", name, "exists", { current: existing, });
|
|
373
374
|
}
|
|
374
|
-
if (f
|
|
375
|
+
if (executionMode(f).dryRun) {
|
|
375
376
|
return {
|
|
376
377
|
dryRun: true,
|
|
377
378
|
action: "create",
|
|
@@ -410,7 +411,7 @@ export const datasetCommands = {
|
|
|
410
411
|
const current = await c.datasets.get(a[0], pk);
|
|
411
412
|
const next = buildDatasetCloneSettings(current, a[1], pk ?? c.resolveProjectKey(pk), opts);
|
|
412
413
|
const zoneId = await resolveFlowZoneIdFromFlags(c, f, pk);
|
|
413
|
-
if (f
|
|
414
|
+
if (executionMode(f).dryRun) {
|
|
414
415
|
return {
|
|
415
416
|
dryRun: true,
|
|
416
417
|
action: "clone",
|
|
@@ -437,11 +438,11 @@ export const datasetCommands = {
|
|
|
437
438
|
handler: async (c, a, f) => {
|
|
438
439
|
requireArgs(a, 1, "dss dataset delete <name>");
|
|
439
440
|
const pk = f["project-key"];
|
|
440
|
-
if (f
|
|
441
|
+
if (executionMode(f).dryRun || f["if-exists"] === true) {
|
|
441
442
|
const current = await readIfExists(() => c.datasets.get(a[0], pk));
|
|
442
443
|
if (!current)
|
|
443
444
|
return skipResult("dataset", a[0], "missing");
|
|
444
|
-
if (f
|
|
445
|
+
if (executionMode(f).dryRun) {
|
|
445
446
|
return { dryRun: true, action: "delete", resource: "dataset", name: a[0], current, };
|
|
446
447
|
}
|
|
447
448
|
}
|
|
@@ -460,7 +461,7 @@ export const datasetCommands = {
|
|
|
460
461
|
throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss dataset update <name> [--data '{...}' | --data-file PATH | --stdin]");
|
|
461
462
|
}
|
|
462
463
|
const pk = f["project-key"];
|
|
463
|
-
if (f
|
|
464
|
+
if (executionMode(f).dryRun) {
|
|
464
465
|
const current = await c.datasets.get(a[0], pk);
|
|
465
466
|
const next = deepMerge(current, data);
|
|
466
467
|
return { dryRun: true, action: "update", resource: "dataset", name: a[0], current, next, };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { runDoctor, } from "../doctor.js";
|
|
2
1
|
export const doctorCommands = {
|
|
3
2
|
run: {
|
|
4
|
-
handler: async (_c, _a, f) =>
|
|
3
|
+
handler: async (_c, _a, f) => {
|
|
4
|
+
const { runDoctor, } = await import("../doctor.js");
|
|
5
|
+
return (await runDoctor(f)).result;
|
|
6
|
+
},
|
|
5
7
|
usage: "dss doctor [--project-key KEY] [--capabilities] [--fast]",
|
|
6
8
|
description: "Run JSON diagnostics for DSS credentials, connectivity, and project access.",
|
|
7
9
|
examples: ["dss doctor", "dss doctor --project-key MYPROJ", "dss doctor --capabilities --fast",],
|