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
package/dist/src/cli.js
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
+
import { homedir, } from "node:os";
|
|
4
|
+
import { resolve, } from "node:path";
|
|
3
5
|
import { num, unknownJsonInput, } from "./cli/coerce.js";
|
|
4
|
-
import { commands, } from "./cli/commands/index.js";
|
|
5
|
-
import { AGENT_CONTRACT_USAGE, AUTH_ACTIONS, BATCH_EXAMPLE_PAYLOAD, BATCH_HINT, BATCH_PLAN_EXIT_CODES, BATCH_USAGE, buildAgentContract, buildCommandRegistry, buildMutationPlan, CLEANUP_USAGE, commandActionSummary, COMMANDS_USAGE, inferRequiresProject, isAllowedCleanupAction, supportsCleanupLedger, } from "./cli/contract.js";
|
|
6
|
-
import { runDoctor, runFixtures, } from "./cli/doctor.js";
|
|
7
6
|
import { dataikuEnvironmentEnabled, loadEnvFile, } from "./cli/env.js";
|
|
8
|
-
import { FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
|
|
7
|
+
import { executionMode, FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
|
|
9
8
|
import { cleanupLedgerEntry, } from "./cli/helpers/cleanup.js";
|
|
10
|
-
import { commandFailureExitCode,
|
|
11
|
-
import { currentCommandContext, resolveCredentials,
|
|
9
|
+
import { commandFailureExitCode, CommandResultFailure, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
|
|
10
|
+
import { currentCommandContext, resolveCredentials, resolveLoginCredentials, } from "./cli/runtime.js";
|
|
12
11
|
import { COMMANDS_RUN_HINT, missingActionError, noCommandError, requireArgs, unknownActionError, unknownResourceError, UsageError, } from "./cli/usage.js";
|
|
13
12
|
import { cliVersionResult, } from "./cli/version.js";
|
|
14
|
-
import { DataikuClient, } from "./client.js";
|
|
15
13
|
import { getCredentialsPath, } from "./config.js";
|
|
16
14
|
import { canonicalStatusText, ClientValidationError, DataikuError, dataikuErrorCode, } from "./errors.js";
|
|
17
|
-
import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } from "./skill.js";
|
|
18
15
|
import { appendCleanupLedgerEntry, findCleanupLedgerBindingViolation, preflightCleanupLedgerPath, readCleanupLedger, reserveCleanupLedgerDssUrl, } from "./utils/cleanup-ledger.js";
|
|
19
16
|
import { canonicalDssUrl, } from "./utils/dss-url.js";
|
|
17
|
+
let commands;
|
|
18
|
+
let contract;
|
|
19
|
+
let commandRuntimeLoad;
|
|
20
|
+
// Dispatch and error reporting initialize these bindings before synchronous validation.
|
|
21
|
+
function loadCommandRuntime() {
|
|
22
|
+
return commandRuntimeLoad ??= Promise.all([
|
|
23
|
+
import("./cli/commands/index.js"),
|
|
24
|
+
import("./cli/contract.js"),
|
|
25
|
+
]).then(([commandModule, contractModule,]) => {
|
|
26
|
+
commands = commandModule.commands;
|
|
27
|
+
contract = contractModule;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
20
30
|
// ---------------------------------------------------------------------------
|
|
21
31
|
// Arg parsing
|
|
22
32
|
// ---------------------------------------------------------------------------
|
|
@@ -60,7 +70,7 @@ function findUnboundLifecycleCleanup(entries) {
|
|
|
60
70
|
async function runCleanup(flags) {
|
|
61
71
|
const filePath = flags["file"];
|
|
62
72
|
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
63
|
-
throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
|
|
73
|
+
throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
|
|
64
74
|
}
|
|
65
75
|
let entries;
|
|
66
76
|
try {
|
|
@@ -95,6 +105,7 @@ async function runCleanup(flags) {
|
|
|
95
105
|
}
|
|
96
106
|
const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
|
|
97
107
|
const retryMaxAttempts = num(flags["retries"], "--retries");
|
|
108
|
+
const { DataikuClient, } = await import("./client.js");
|
|
98
109
|
const client = new DataikuClient({
|
|
99
110
|
url,
|
|
100
111
|
apiKey,
|
|
@@ -153,7 +164,12 @@ async function runCleanup(flags) {
|
|
|
153
164
|
try {
|
|
154
165
|
const parsed = parseArgs(entry.cleanup.argv);
|
|
155
166
|
const [resource, action, ...args] = parsed.positional;
|
|
156
|
-
|
|
167
|
+
Object.assign(currentCommandContext, {
|
|
168
|
+
resource,
|
|
169
|
+
action,
|
|
170
|
+
projectKey: parsed.flags["project-key"],
|
|
171
|
+
});
|
|
172
|
+
if (!resource || !action || !contract.isAllowedCleanupAction(resource, action)
|
|
157
173
|
|| !commands[resource]?.[action]) {
|
|
158
174
|
throw new UsageError(`Invalid cleanup argv: ${entry.cleanup.argv.join(" ")}`);
|
|
159
175
|
}
|
|
@@ -173,13 +189,18 @@ async function runCleanup(flags) {
|
|
|
173
189
|
const failure = {
|
|
174
190
|
index,
|
|
175
191
|
cleanup: entry.cleanup,
|
|
176
|
-
error:
|
|
177
|
-
result,
|
|
192
|
+
error: buildErrorReport(new CommandResultFailure(result, 4)),
|
|
178
193
|
};
|
|
179
194
|
failures.push(failure);
|
|
180
195
|
if (flags["continue-on-error"] !== true) {
|
|
181
196
|
return {
|
|
182
|
-
result: {
|
|
197
|
+
result: {
|
|
198
|
+
applied: true,
|
|
199
|
+
steps,
|
|
200
|
+
results: applied,
|
|
201
|
+
failures,
|
|
202
|
+
...summarizeCleanupFailures(failures),
|
|
203
|
+
},
|
|
183
204
|
exitCode: 2,
|
|
184
205
|
};
|
|
185
206
|
}
|
|
@@ -191,29 +212,41 @@ async function runCleanup(flags) {
|
|
|
191
212
|
const failure = {
|
|
192
213
|
index,
|
|
193
214
|
cleanup: entry.cleanup,
|
|
194
|
-
error:
|
|
215
|
+
error: buildErrorReport(error),
|
|
195
216
|
};
|
|
196
217
|
failures.push(failure);
|
|
197
218
|
if (flags["continue-on-error"] !== true) {
|
|
198
219
|
return {
|
|
199
|
-
result: {
|
|
220
|
+
result: {
|
|
221
|
+
applied: true,
|
|
222
|
+
steps,
|
|
223
|
+
results: applied,
|
|
224
|
+
failures,
|
|
225
|
+
...summarizeCleanupFailures(failures),
|
|
226
|
+
},
|
|
200
227
|
exitCode: 2,
|
|
201
228
|
};
|
|
202
229
|
}
|
|
203
230
|
}
|
|
204
231
|
}
|
|
205
232
|
return {
|
|
206
|
-
result: {
|
|
233
|
+
result: {
|
|
234
|
+
applied: true,
|
|
235
|
+
steps,
|
|
236
|
+
results: applied,
|
|
237
|
+
failures,
|
|
238
|
+
...summarizeCleanupFailures(failures),
|
|
239
|
+
},
|
|
207
240
|
exitCode: failures.length > 0 ? 2 : 0,
|
|
208
241
|
};
|
|
209
242
|
}
|
|
210
243
|
function parseBatchSteps(payload) {
|
|
211
244
|
if (!Array.isArray(payload)) {
|
|
212
|
-
throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", BATCH_HINT, { example: BATCH_EXAMPLE_PAYLOAD, });
|
|
245
|
+
throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", contract.BATCH_HINT, { example: contract.BATCH_EXAMPLE_PAYLOAD, });
|
|
213
246
|
}
|
|
214
247
|
return payload.map((step, index) => {
|
|
215
248
|
if (!Array.isArray(step) || !step.every((token) => typeof token === "string")) {
|
|
216
|
-
throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", BATCH_HINT);
|
|
249
|
+
throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", contract.BATCH_HINT);
|
|
217
250
|
}
|
|
218
251
|
return step;
|
|
219
252
|
});
|
|
@@ -244,12 +277,12 @@ const META_COMMAND_RESOURCES = {
|
|
|
244
277
|
fixtures: true,
|
|
245
278
|
batch: true,
|
|
246
279
|
};
|
|
247
|
-
|
|
248
|
-
function cachedCommandRegistry() {
|
|
249
|
-
if (commandRegistryCache
|
|
250
|
-
commandRegistryCache
|
|
280
|
+
const commandRegistryCache = new Map();
|
|
281
|
+
function cachedCommandRegistry(resource) {
|
|
282
|
+
if (!commandRegistryCache.has(resource)) {
|
|
283
|
+
commandRegistryCache.set(resource, contract.buildCommandRegistry(resource)[resource]);
|
|
251
284
|
}
|
|
252
|
-
return commandRegistryCache;
|
|
285
|
+
return commandRegistryCache.get(resource);
|
|
253
286
|
}
|
|
254
287
|
function supportedCommandFlags(entry) {
|
|
255
288
|
const supported = { ...ALWAYS_ALLOWED_COMMAND_FLAGS, };
|
|
@@ -287,7 +320,7 @@ function commandRegistrySelectors(flags) {
|
|
|
287
320
|
return selectors;
|
|
288
321
|
}
|
|
289
322
|
function validateSupportedCommandFlags(resource, action, flags) {
|
|
290
|
-
const entry = cachedCommandRegistry()
|
|
323
|
+
const entry = cachedCommandRegistry(resource)?.[action];
|
|
291
324
|
if (!entry)
|
|
292
325
|
return;
|
|
293
326
|
const supported = supportedCommandFlags(entry);
|
|
@@ -296,7 +329,7 @@ function validateSupportedCommandFlags(resource, action, flags) {
|
|
|
296
329
|
throw new UsageError(`Unknown flag --${flagName} for ${resource} ${action}`, "unknown_flag", scopedCommandDiscoveryHint(resource, action));
|
|
297
330
|
}
|
|
298
331
|
}
|
|
299
|
-
if (flags
|
|
332
|
+
if (executionMode(flags).dryRun && !entry.dryRun) {
|
|
300
333
|
throw new UsageError(`--dry-run is not supported for ${resource} ${action}.`, "unknown_flag", entry.flags.some((flag) => flag.name === "plan")
|
|
301
334
|
? "Remove --dry-run; use --plan to preview the mutation instead."
|
|
302
335
|
: `Remove --dry-run; ${resource} ${action} has no dry-run mode.`);
|
|
@@ -359,7 +392,7 @@ function validateRequiredCommandInputs(resource, action, args, flags, entry) {
|
|
|
359
392
|
}
|
|
360
393
|
}
|
|
361
394
|
function validateRegistryCommandInputs(resource, action, args, flags) {
|
|
362
|
-
const entry = cachedCommandRegistry()
|
|
395
|
+
const entry = cachedCommandRegistry(resource)?.[action];
|
|
363
396
|
if (!entry)
|
|
364
397
|
return;
|
|
365
398
|
validateSupportedCommandFlags(resource, action, flags);
|
|
@@ -396,7 +429,7 @@ async function validateDryRunHandlerPreconditions(handler, args, flags) {
|
|
|
396
429
|
throw error;
|
|
397
430
|
}
|
|
398
431
|
}
|
|
399
|
-
function validateMetaCommandInputs(resource, action, flags) {
|
|
432
|
+
async function validateMetaCommandInputs(resource, action, flags) {
|
|
400
433
|
if (resource === "doctor") {
|
|
401
434
|
if (action !== undefined && action !== "run") {
|
|
402
435
|
throw unknownActionError("doctor", action, ["run",]);
|
|
@@ -404,11 +437,11 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
404
437
|
return "run";
|
|
405
438
|
}
|
|
406
439
|
if (resource === "auth") {
|
|
407
|
-
const validActions = Object.keys(AUTH_ACTIONS);
|
|
440
|
+
const validActions = Object.keys(contract.AUTH_ACTIONS);
|
|
408
441
|
if (!action) {
|
|
409
442
|
throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
|
|
410
443
|
}
|
|
411
|
-
if (!AUTH_ACTIONS[action]) {
|
|
444
|
+
if (!contract.AUTH_ACTIONS[action]) {
|
|
412
445
|
throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
|
|
413
446
|
}
|
|
414
447
|
return action;
|
|
@@ -417,6 +450,7 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
417
450
|
if (action !== undefined && action !== "run") {
|
|
418
451
|
throw unknownActionError("install-skill", action, ["run",]);
|
|
419
452
|
}
|
|
453
|
+
const { AGENTS, } = await import("./skill.js");
|
|
420
454
|
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
421
455
|
if (agentFilter && !AGENTS[agentFilter]) {
|
|
422
456
|
throw new UsageError(`Unknown agent: ${agentFilter}.`, "usage_error", COMMANDS_RUN_HINT, { agent: agentFilter, validAgents: Object.keys(AGENTS), });
|
|
@@ -425,14 +459,14 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
425
459
|
}
|
|
426
460
|
if (resource === "agent") {
|
|
427
461
|
if (!action)
|
|
428
|
-
throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
|
|
462
|
+
throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
|
|
429
463
|
if (action !== "contract")
|
|
430
464
|
throw unknownActionError("agent", action, ["contract",]);
|
|
431
465
|
return action;
|
|
432
466
|
}
|
|
433
467
|
if (resource === "commands") {
|
|
434
468
|
if (!action)
|
|
435
|
-
throw missingActionError("commands", ["run",], COMMANDS_USAGE);
|
|
469
|
+
throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
|
|
436
470
|
if (action !== "run")
|
|
437
471
|
throw unknownActionError("commands", action, ["run",]);
|
|
438
472
|
validateSupportedCommandFlags("commands", "run", flags);
|
|
@@ -449,7 +483,7 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
449
483
|
if (action !== undefined && action !== "run") {
|
|
450
484
|
throw unknownActionError("cleanup", action, ["run",]);
|
|
451
485
|
}
|
|
452
|
-
const entry = cachedCommandRegistry()
|
|
486
|
+
const entry = cachedCommandRegistry("cleanup")?.run;
|
|
453
487
|
if (entry)
|
|
454
488
|
validateRequiredCommandInputs("cleanup", "run", [], flags, entry);
|
|
455
489
|
return "run";
|
|
@@ -464,7 +498,7 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
464
498
|
if (action !== undefined && action !== "run") {
|
|
465
499
|
throw unknownActionError("batch", action, ["run",]);
|
|
466
500
|
}
|
|
467
|
-
const entry = cachedCommandRegistry()
|
|
501
|
+
const entry = cachedCommandRegistry("batch")?.run;
|
|
468
502
|
if (entry)
|
|
469
503
|
validateRequiredCommandInputs("batch", "run", [], flags, entry);
|
|
470
504
|
return "run";
|
|
@@ -477,7 +511,7 @@ async function validateBatchStep(argv) {
|
|
|
477
511
|
const action = positional[1];
|
|
478
512
|
if (!resource)
|
|
479
513
|
throw noCommandError();
|
|
480
|
-
const metaAction = validateMetaCommandInputs(resource, action, flags);
|
|
514
|
+
const metaAction = await validateMetaCommandInputs(resource, action, flags);
|
|
481
515
|
if (metaAction)
|
|
482
516
|
return { positional, flags, resource, action: metaAction, };
|
|
483
517
|
const resourceActions = commands[resource];
|
|
@@ -496,7 +530,7 @@ async function validateBatchStep(argv) {
|
|
|
496
530
|
function batchStepNeedsClient(argv) {
|
|
497
531
|
try {
|
|
498
532
|
const { positional, flags, } = parseArgs(argv);
|
|
499
|
-
if (flags
|
|
533
|
+
if (executionMode(flags).plan)
|
|
500
534
|
return false;
|
|
501
535
|
const resource = positional[0];
|
|
502
536
|
if (!resource)
|
|
@@ -578,7 +612,7 @@ function validateBatchStepMode(argv, index) {
|
|
|
578
612
|
catch {
|
|
579
613
|
return;
|
|
580
614
|
}
|
|
581
|
-
if (flags
|
|
615
|
+
if (!executionMode(flags).plan && !executionMode(flags).dryRun)
|
|
582
616
|
return;
|
|
583
617
|
const resource = positional[0];
|
|
584
618
|
const action = positional[1];
|
|
@@ -592,13 +626,13 @@ function validateBatchStepMode(argv, index) {
|
|
|
592
626
|
const meta = resourceActions[action ?? ""];
|
|
593
627
|
if (!meta)
|
|
594
628
|
return;
|
|
595
|
-
const entry = cachedCommandRegistry()
|
|
629
|
+
const entry = cachedCommandRegistry(resource)?.[action ?? ""];
|
|
596
630
|
if (!entry)
|
|
597
631
|
return;
|
|
598
|
-
if (flags
|
|
632
|
+
if (executionMode(flags).plan && !entry.flags.some((flag) => flag.name === "plan")) {
|
|
599
633
|
throw new UsageError(`Batch step ${index} requests --plan, which is not supported for ${resource} ${action}.`, "unknown_flag", "Remove --plan from the step; only mutating commands support --plan.");
|
|
600
634
|
}
|
|
601
|
-
if (flags
|
|
635
|
+
if (executionMode(flags).dryRun && !entry.dryRun) {
|
|
602
636
|
throw new UsageError(`Batch step ${index} requests --dry-run, which is not supported for ${resource} ${action}.`, "unknown_flag", "Remove --dry-run from the step.");
|
|
603
637
|
}
|
|
604
638
|
}
|
|
@@ -607,7 +641,8 @@ function validateBatchStepModes(steps) {
|
|
|
607
641
|
validateBatchStepMode(steps[index], index);
|
|
608
642
|
}
|
|
609
643
|
}
|
|
610
|
-
function runInstallSkill(flags) {
|
|
644
|
+
async function runInstallSkill(flags) {
|
|
645
|
+
const { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } = await import("./skill.js");
|
|
611
646
|
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
612
647
|
const isGlobal = flags["global"] === true;
|
|
613
648
|
const targetDir = typeof flags["target"] === "string" ? flags["target"] : undefined;
|
|
@@ -633,46 +668,34 @@ function runInstallSkill(flags) {
|
|
|
633
668
|
throw new UsageError("No coding agents detected.", "usage_error", "Use --agent NAME to choose one of the supported agents.", { validAgents: Object.keys(AGENTS), });
|
|
634
669
|
}
|
|
635
670
|
const scope = isGlobal ? "global" : "project";
|
|
636
|
-
const cwd =
|
|
671
|
+
const cwd = isGlobal
|
|
672
|
+
? homedir()
|
|
673
|
+
: targetDir
|
|
674
|
+
? resolve(targetDir)
|
|
675
|
+
: findWorkspaceRoot(process.cwd());
|
|
637
676
|
const installed = planSkillInstalls(targets, { global: isGlobal, cwd, });
|
|
638
|
-
if (flags
|
|
677
|
+
if (executionMode(flags).plan) {
|
|
639
678
|
return planResult("install-skill", "run", {
|
|
640
679
|
identifiers: { scope, target: cwd, },
|
|
641
680
|
payload: { installed, },
|
|
642
681
|
idempotency: "none",
|
|
643
682
|
asyncKind: "none",
|
|
644
683
|
exitCodesOnFailure: { usage: 1, error: 2, transient: 3, },
|
|
645
|
-
plannedAndDryRun: flags
|
|
684
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
646
685
|
});
|
|
647
686
|
}
|
|
648
687
|
return {
|
|
649
688
|
scope,
|
|
650
689
|
target: cwd,
|
|
651
|
-
installed: flags
|
|
690
|
+
installed: executionMode(flags).dryRun
|
|
652
691
|
? installed
|
|
653
692
|
: installSkill(targets, { global: isGlobal, cwd, }),
|
|
654
|
-
...(flags
|
|
693
|
+
...(executionMode(flags).dryRun ? { dryRun: true, } : {}),
|
|
655
694
|
};
|
|
656
695
|
}
|
|
657
696
|
const META_PLAN_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
|
|
658
697
|
function authLoginPlan(flags) {
|
|
659
|
-
const tlsSettings =
|
|
660
|
-
const useEnv = dataikuEnvironmentEnabled();
|
|
661
|
-
const url = typeof flags["url"] === "string"
|
|
662
|
-
? flags["url"]
|
|
663
|
-
: useEnv
|
|
664
|
-
? process.env.DATAIKU_URL ?? ""
|
|
665
|
-
: "";
|
|
666
|
-
const apiKey = typeof flags["api-key"] === "string"
|
|
667
|
-
? flags["api-key"]
|
|
668
|
-
: useEnv
|
|
669
|
-
? process.env.DATAIKU_API_KEY ?? ""
|
|
670
|
-
: "";
|
|
671
|
-
const projectKey = typeof flags["project-key"] === "string"
|
|
672
|
-
? flags["project-key"]
|
|
673
|
-
: useEnv
|
|
674
|
-
? process.env.DATAIKU_PROJECT_KEY
|
|
675
|
-
: undefined;
|
|
698
|
+
const { url, apiKey, projectKey, tlsSettings, } = resolveLoginCredentials(flags);
|
|
676
699
|
if (!url || !apiKey) {
|
|
677
700
|
throw new UsageError("Missing --url and/or --api-key for auth login.", "missing_required_flag", "Pass --url and --api-key, or set DATAIKU_URL and DATAIKU_API_KEY.", { requiredFlags: ["url", "api-key",], env: ["DATAIKU_URL", "DATAIKU_API_KEY",], });
|
|
678
701
|
}
|
|
@@ -700,7 +723,7 @@ function authLoginPlan(flags) {
|
|
|
700
723
|
function cleanupPlan(flags) {
|
|
701
724
|
const filePath = flags["file"];
|
|
702
725
|
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
703
|
-
throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
|
|
726
|
+
throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
|
|
704
727
|
}
|
|
705
728
|
return planResult("cleanup", "run", {
|
|
706
729
|
identifiers: { file: filePath, },
|
|
@@ -711,13 +734,13 @@ function cleanupPlan(flags) {
|
|
|
711
734
|
idempotency: "none",
|
|
712
735
|
asyncKind: "none",
|
|
713
736
|
exitCodesOnFailure: META_PLAN_EXIT_CODES,
|
|
714
|
-
plannedAndDryRun: flags
|
|
737
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
715
738
|
});
|
|
716
739
|
}
|
|
717
740
|
function batchPlan(flags) {
|
|
718
741
|
const payload = unknownJsonInput(flags);
|
|
719
742
|
if (payload === undefined) {
|
|
720
|
-
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
|
|
743
|
+
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
|
|
721
744
|
}
|
|
722
745
|
const steps = parseBatchSteps(payload);
|
|
723
746
|
return planResult("batch", "run", {
|
|
@@ -728,34 +751,37 @@ function batchPlan(flags) {
|
|
|
728
751
|
payload: {
|
|
729
752
|
steps: steps.map((argv) => redactArgv(argv)),
|
|
730
753
|
continueOnError: flags["continue-on-error"] === true,
|
|
731
|
-
dryRun: flags
|
|
754
|
+
dryRun: executionMode(flags).dryRun,
|
|
732
755
|
},
|
|
733
756
|
idempotency: "none",
|
|
734
757
|
asyncKind: "none",
|
|
735
|
-
exitCodesOnFailure: BATCH_PLAN_EXIT_CODES,
|
|
736
|
-
plannedAndDryRun: flags
|
|
758
|
+
exitCodesOnFailure: contract.BATCH_PLAN_EXIT_CODES,
|
|
759
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
737
760
|
});
|
|
738
761
|
}
|
|
739
762
|
async function runMetaCommand(resource, action, flags) {
|
|
763
|
+
if (resource !== "version")
|
|
764
|
+
await loadCommandRuntime();
|
|
740
765
|
if (resource === "doctor") {
|
|
741
766
|
if (action !== undefined && action !== "run") {
|
|
742
767
|
throw unknownActionError("doctor", action, ["run",]);
|
|
743
768
|
}
|
|
744
769
|
currentCommandContext.action = action ?? "run";
|
|
770
|
+
const { runDoctor, } = await import("./cli/doctor.js");
|
|
745
771
|
const { result, exitCode, } = await runDoctor(flags);
|
|
746
772
|
return { action: "run", result, exitCode, };
|
|
747
773
|
}
|
|
748
774
|
if (resource === "auth") {
|
|
749
|
-
const validActions = Object.keys(AUTH_ACTIONS);
|
|
775
|
+
const validActions = Object.keys(contract.AUTH_ACTIONS);
|
|
750
776
|
if (!action) {
|
|
751
777
|
throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
|
|
752
778
|
}
|
|
753
779
|
currentCommandContext.action = action;
|
|
754
|
-
const authMeta = AUTH_ACTIONS[action];
|
|
780
|
+
const authMeta = contract.AUTH_ACTIONS[action];
|
|
755
781
|
if (!authMeta) {
|
|
756
782
|
throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
|
|
757
783
|
}
|
|
758
|
-
if (flags
|
|
784
|
+
if (executionMode(flags).plan) {
|
|
759
785
|
return { action, result: authLoginPlan(flags), exitCode: 0, };
|
|
760
786
|
}
|
|
761
787
|
return { action, result: await authMeta.handler(flags), exitCode: 0, };
|
|
@@ -765,29 +791,29 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
765
791
|
throw unknownActionError("install-skill", action, ["run",]);
|
|
766
792
|
}
|
|
767
793
|
currentCommandContext.action = action ?? "run";
|
|
768
|
-
return { action: "run", result: runInstallSkill(flags), exitCode: 0, };
|
|
794
|
+
return { action: "run", result: await runInstallSkill(flags), exitCode: 0, };
|
|
769
795
|
}
|
|
770
796
|
if (resource === "agent") {
|
|
771
797
|
if (!action)
|
|
772
|
-
throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
|
|
798
|
+
throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
|
|
773
799
|
currentCommandContext.action = action;
|
|
774
800
|
if (action !== "contract")
|
|
775
801
|
throw unknownActionError("agent", action, ["contract",]);
|
|
776
|
-
return { action, result: buildAgentContract(), exitCode: 0, };
|
|
802
|
+
return { action, result: contract.buildAgentContract(), exitCode: 0, };
|
|
777
803
|
}
|
|
778
804
|
if (resource === "commands") {
|
|
779
805
|
if (!action)
|
|
780
|
-
throw missingActionError("commands", ["run",], COMMANDS_USAGE);
|
|
806
|
+
throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
|
|
781
807
|
currentCommandContext.action = action;
|
|
782
808
|
if (action !== "run")
|
|
783
809
|
throw unknownActionError("commands", action, ["run",]);
|
|
784
810
|
validateSupportedCommandFlags("commands", "run", flags);
|
|
785
811
|
const selectors = commandRegistrySelectors(flags);
|
|
786
|
-
const registry = buildCommandRegistry();
|
|
812
|
+
const registry = contract.buildCommandRegistry();
|
|
787
813
|
for (const selector of selectors) {
|
|
788
814
|
const selectorParts = selector.split(".");
|
|
789
815
|
if (selectorParts.some((part) => part.length === 0)) {
|
|
790
|
-
throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", COMMANDS_USAGE, { selector, });
|
|
816
|
+
throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", contract.COMMANDS_USAGE, { selector, });
|
|
791
817
|
}
|
|
792
818
|
const [selectedResource, selectedAction,] = selectorParts;
|
|
793
819
|
const resourceActions = registry[selectedResource];
|
|
@@ -808,7 +834,7 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
808
834
|
}
|
|
809
835
|
return {
|
|
810
836
|
action,
|
|
811
|
-
result: selectors.length === 0 ? commandActionSummary(registry) : selectedRegistry,
|
|
837
|
+
result: selectors.length === 0 ? contract.commandActionSummary(registry) : selectedRegistry,
|
|
812
838
|
exitCode: 0,
|
|
813
839
|
};
|
|
814
840
|
}
|
|
@@ -824,7 +850,7 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
824
850
|
throw unknownActionError("cleanup", action, ["run",]);
|
|
825
851
|
}
|
|
826
852
|
currentCommandContext.action = action ?? "run";
|
|
827
|
-
if (flags
|
|
853
|
+
if (executionMode(flags).plan) {
|
|
828
854
|
return { action: "run", result: cleanupPlan(flags), exitCode: 0, };
|
|
829
855
|
}
|
|
830
856
|
const { result, exitCode, } = await runCleanup(flags);
|
|
@@ -835,6 +861,7 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
835
861
|
throw unknownActionError("fixtures", action, ["run",]);
|
|
836
862
|
}
|
|
837
863
|
currentCommandContext.action = action ?? "run";
|
|
864
|
+
const { runFixtures, } = await import("./cli/doctor.js");
|
|
838
865
|
return { action: "run", result: await runFixtures(flags), exitCode: 0, };
|
|
839
866
|
}
|
|
840
867
|
if (resource === "batch") {
|
|
@@ -842,7 +869,7 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
842
869
|
throw unknownActionError("batch", action, ["run",]);
|
|
843
870
|
}
|
|
844
871
|
currentCommandContext.action = action ?? "run";
|
|
845
|
-
if (flags
|
|
872
|
+
if (executionMode(flags).plan) {
|
|
846
873
|
return { action: "run", result: batchPlan(flags), exitCode: 0, };
|
|
847
874
|
}
|
|
848
875
|
const { result, exitCode, } = await runBatch(flags);
|
|
@@ -856,11 +883,14 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
856
883
|
* share this guard.
|
|
857
884
|
*/
|
|
858
885
|
function assertCleanupLedgerSupported(resource, action, flags) {
|
|
859
|
-
if (typeof flags["record-cleanup"] !== "string" || flags
|
|
886
|
+
if (typeof flags["record-cleanup"] !== "string" || executionMode(flags).dryRun)
|
|
860
887
|
return;
|
|
861
|
-
if (!supportsCleanupLedger(resource, action)) {
|
|
888
|
+
if (!contract.supportsCleanupLedger(resource, action)) {
|
|
862
889
|
throw new UsageError(`--record-cleanup is not supported for ${resource} ${action}.`);
|
|
863
890
|
}
|
|
891
|
+
if (resource === "workspace" && action === "create" && flags["stdin"] === true) {
|
|
892
|
+
throw new UsageError("workspace create cannot combine --record-cleanup with --stdin because the cleanup identifier cannot be replayed from a consumed stream.", "conflicting_input_sources", "Use --data or --data-file so the workspaceKey remains available for the cleanup ledger.");
|
|
893
|
+
}
|
|
864
894
|
}
|
|
865
895
|
/**
|
|
866
896
|
* Preflight a requested cleanup ledger path before any remote mutation so an
|
|
@@ -869,7 +899,7 @@ function assertCleanupLedgerSupported(resource, action, flags) {
|
|
|
869
899
|
* steps both call it, and dry-run/preview runs never touch the ledger.
|
|
870
900
|
*/
|
|
871
901
|
async function preflightCleanupLedgerForFlags(client, flags) {
|
|
872
|
-
if (flags
|
|
902
|
+
if (executionMode(flags).dryRun)
|
|
873
903
|
return;
|
|
874
904
|
const ledgerPath = flags["record-cleanup"];
|
|
875
905
|
if (typeof ledgerPath !== "string")
|
|
@@ -920,19 +950,27 @@ async function preflightCleanupLedgerForFlags(client, flags) {
|
|
|
920
950
|
*/
|
|
921
951
|
async function recordCleanupLedgerEntry(client, resource, action, args, flags, result, projectKey) {
|
|
922
952
|
const ledgerPath = flags["record-cleanup"];
|
|
923
|
-
if (typeof ledgerPath !== "string" || flags
|
|
953
|
+
if (typeof ledgerPath !== "string" || executionMode(flags).dryRun)
|
|
924
954
|
return;
|
|
925
955
|
const entry = cleanupLedgerEntry(resource, action, args, flags, result, projectKey);
|
|
926
|
-
if (entry)
|
|
956
|
+
if (entry) {
|
|
927
957
|
await appendCleanupLedgerEntry(ledgerPath, entry, client.getBaseUrl());
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
const record = result !== null && typeof result === "object" && !Array.isArray(result)
|
|
961
|
+
? result
|
|
962
|
+
: {};
|
|
963
|
+
if (record.skipped !== undefined || record.cleanupEligible === false)
|
|
964
|
+
return;
|
|
965
|
+
throw new ClientValidationError(`Mutation completed but no cleanup entry could be derived for ${resource} ${action}.`, "ambiguous_outcome", "Inspect the created object and remove it manually; do not assume the requested cleanup ledger contains it.", { resource, action, ledgerPath, });
|
|
928
966
|
}
|
|
929
967
|
async function runBatch(flags) {
|
|
930
968
|
const payload = unknownJsonInput(flags);
|
|
931
969
|
if (payload === undefined) {
|
|
932
|
-
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
|
|
970
|
+
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
|
|
933
971
|
}
|
|
934
972
|
const steps = parseBatchSteps(payload);
|
|
935
|
-
if (flags
|
|
973
|
+
if (executionMode(flags).dryRun) {
|
|
936
974
|
const batchProjectKey = typeof flags["project-key"] === "string"
|
|
937
975
|
? flags["project-key"]
|
|
938
976
|
: dataikuEnvironmentEnabled()
|
|
@@ -999,6 +1037,7 @@ async function runBatch(flags) {
|
|
|
999
1037
|
env: ["DATAIKU_API_KEY",],
|
|
1000
1038
|
});
|
|
1001
1039
|
}
|
|
1040
|
+
const { DataikuClient, } = await import("./client.js");
|
|
1002
1041
|
client = new DataikuClient({
|
|
1003
1042
|
url,
|
|
1004
1043
|
apiKey,
|
|
@@ -1061,8 +1100,8 @@ async function runBatch(flags) {
|
|
|
1061
1100
|
if (!meta)
|
|
1062
1101
|
throw unknownActionError(resource, action, Object.keys(resourceActions));
|
|
1063
1102
|
validateSupportedCommandFlags(resource, action, stepFlags);
|
|
1064
|
-
if (stepFlags
|
|
1065
|
-
result = buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
|
|
1103
|
+
if (executionMode(stepFlags).plan) {
|
|
1104
|
+
result = contract.buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
|
|
1066
1105
|
}
|
|
1067
1106
|
else {
|
|
1068
1107
|
if (!client) {
|
|
@@ -1102,16 +1141,46 @@ async function runBatch(flags) {
|
|
|
1102
1141
|
}
|
|
1103
1142
|
}
|
|
1104
1143
|
const ok = firstFailureExit === undefined;
|
|
1144
|
+
const failureSummary = summarizeFailureEnvelopes(results.flatMap((step) => step.error ? [{ index: step.index, error: step.error, },] : []));
|
|
1105
1145
|
return {
|
|
1106
1146
|
result: {
|
|
1107
1147
|
ok,
|
|
1108
1148
|
total: steps.length,
|
|
1109
1149
|
completed: results.filter((step) => step.ok !== null).length,
|
|
1150
|
+
...failureSummary,
|
|
1110
1151
|
steps: results,
|
|
1111
1152
|
},
|
|
1112
1153
|
exitCode: ok ? 0 : firstFailureExit ?? 2,
|
|
1113
1154
|
};
|
|
1114
1155
|
}
|
|
1156
|
+
function summarizeFailureEnvelopes(failures) {
|
|
1157
|
+
const failureCodes = failures.map(({ index, error, }) => ({
|
|
1158
|
+
index,
|
|
1159
|
+
code: error.code,
|
|
1160
|
+
category: error.category,
|
|
1161
|
+
exitCode: error.exitCode,
|
|
1162
|
+
retryable: error.retryable === true,
|
|
1163
|
+
}));
|
|
1164
|
+
return {
|
|
1165
|
+
failed: failures.length,
|
|
1166
|
+
retryable: failures.length > 0 && failureCodes.every((failure) => failure.retryable),
|
|
1167
|
+
hasRetryableFailures: failureCodes.some((failure) => failure.retryable),
|
|
1168
|
+
failureCodes,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
function summarizeCleanupFailures(failures) {
|
|
1172
|
+
return summarizeFailureEnvelopes(failures.flatMap((failure) => {
|
|
1173
|
+
const index = failure.index;
|
|
1174
|
+
const error = failure.error;
|
|
1175
|
+
return typeof index === "number"
|
|
1176
|
+
&& error !== null
|
|
1177
|
+
&& typeof error === "object"
|
|
1178
|
+
&& !Array.isArray(error)
|
|
1179
|
+
&& error.type === "error"
|
|
1180
|
+
? [{ index, error: error, },]
|
|
1181
|
+
: [];
|
|
1182
|
+
}));
|
|
1183
|
+
}
|
|
1115
1184
|
function rawFlagValue(argv, flagName) {
|
|
1116
1185
|
const longFlag = `--${flagName}`;
|
|
1117
1186
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -1129,7 +1198,7 @@ function commandIsProjectScoped(resource, action) {
|
|
|
1129
1198
|
if (!resource)
|
|
1130
1199
|
return false;
|
|
1131
1200
|
const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
|
|
1132
|
-
return inferRequiresProject(resource, action ?? "", usage);
|
|
1201
|
+
return contract.inferRequiresProject(resource, action ?? "", usage);
|
|
1133
1202
|
}
|
|
1134
1203
|
function rawCommandContext() {
|
|
1135
1204
|
const argv = process.argv.slice(2);
|
|
@@ -1206,13 +1275,48 @@ function requestIdFromBody(body) {
|
|
|
1206
1275
|
return undefined;
|
|
1207
1276
|
}
|
|
1208
1277
|
const MISSING_PROJECT_KEY_ERROR_PREFIX = "projectKey is required";
|
|
1278
|
+
function isAmbiguousMutationFailure(err, context) {
|
|
1279
|
+
if (!(err instanceof DataikuError))
|
|
1280
|
+
return false;
|
|
1281
|
+
const method = err.retry?.method.toUpperCase();
|
|
1282
|
+
if (!method || method === "GET" || method === "HEAD")
|
|
1283
|
+
return false;
|
|
1284
|
+
if (err.status !== 0 && err.status < 500)
|
|
1285
|
+
return false;
|
|
1286
|
+
const entry = context.resource && context.action
|
|
1287
|
+
? cachedCommandRegistry(context.resource)?.[context.action]
|
|
1288
|
+
: undefined;
|
|
1289
|
+
return entry?.sideEffect === "write" && entry.idempotency === "none";
|
|
1290
|
+
}
|
|
1291
|
+
const MAX_FAILURE_RESULT_BYTES = 64 * 1024;
|
|
1292
|
+
function boundedFailureResult(result) {
|
|
1293
|
+
let serialized;
|
|
1294
|
+
try {
|
|
1295
|
+
serialized = JSON.stringify(result);
|
|
1296
|
+
}
|
|
1297
|
+
catch {
|
|
1298
|
+
return { result: "[unserializable command result]", resultTruncated: true, };
|
|
1299
|
+
}
|
|
1300
|
+
if (serialized === undefined || Buffer.byteLength(serialized, "utf8") <= MAX_FAILURE_RESULT_BYTES) {
|
|
1301
|
+
return { result, };
|
|
1302
|
+
}
|
|
1303
|
+
return {
|
|
1304
|
+
resultPreview: Buffer.from(serialized, "utf8").subarray(0, MAX_FAILURE_RESULT_BYTES).toString("utf8"),
|
|
1305
|
+
resultBytes: Buffer.byteLength(serialized, "utf8"),
|
|
1306
|
+
resultTruncated: true,
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1209
1309
|
function errorExitCode(err) {
|
|
1210
1310
|
if (err instanceof CommandResultFailure)
|
|
1211
1311
|
return err.exitCode;
|
|
1312
|
+
if (err instanceof ClientValidationError && err.code === "assertion_failed")
|
|
1313
|
+
return 4;
|
|
1212
1314
|
if (err instanceof ClientValidationError && err.code === "ambiguous_outcome")
|
|
1213
1315
|
return 2;
|
|
1214
1316
|
if (err instanceof UsageError || err instanceof ClientValidationError)
|
|
1215
1317
|
return 1;
|
|
1318
|
+
if (isAmbiguousMutationFailure(err, rawCommandContext()))
|
|
1319
|
+
return 2;
|
|
1216
1320
|
if (err instanceof DataikuError)
|
|
1217
1321
|
return err.category === "transient" ? 3 : 2;
|
|
1218
1322
|
if (err instanceof Error && err.message.startsWith(MISSING_PROJECT_KEY_ERROR_PREFIX))
|
|
@@ -1222,7 +1326,8 @@ function errorExitCode(err) {
|
|
|
1222
1326
|
function buildErrorReport(err) {
|
|
1223
1327
|
const context = rawCommandContext();
|
|
1224
1328
|
const exitCode = errorExitCode(err);
|
|
1225
|
-
if (err instanceof ClientValidationError
|
|
1329
|
+
if (err instanceof ClientValidationError
|
|
1330
|
+
&& (err.code === "ambiguous_outcome" || err.code === "assertion_failed")) {
|
|
1226
1331
|
return {
|
|
1227
1332
|
type: "error",
|
|
1228
1333
|
ok: false,
|
|
@@ -1230,6 +1335,7 @@ function buildErrorReport(err) {
|
|
|
1230
1335
|
code: err.code,
|
|
1231
1336
|
category: "dss",
|
|
1232
1337
|
exitCode,
|
|
1338
|
+
retryable: false,
|
|
1233
1339
|
...(err.hint ? { hint: err.hint, } : {}),
|
|
1234
1340
|
...(err.details ? { details: err.details, } : {}),
|
|
1235
1341
|
...context,
|
|
@@ -1259,7 +1365,29 @@ function buildErrorReport(err) {
|
|
|
1259
1365
|
code: err.code,
|
|
1260
1366
|
category: "dss",
|
|
1261
1367
|
exitCode: err.exitCode,
|
|
1262
|
-
details:
|
|
1368
|
+
details: boundedFailureResult(err.result),
|
|
1369
|
+
...context,
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
if (isAmbiguousMutationFailure(err, context)) {
|
|
1373
|
+
const method = err.retry?.method.toUpperCase() ?? "mutation";
|
|
1374
|
+
return {
|
|
1375
|
+
type: "error",
|
|
1376
|
+
ok: false,
|
|
1377
|
+
error: `The ${method} request failed after dispatch; the mutation outcome is unknown.`,
|
|
1378
|
+
code: "ambiguous_outcome",
|
|
1379
|
+
category: "dss",
|
|
1380
|
+
exitCode: 2,
|
|
1381
|
+
hint: "Read the target state and verify whether the mutation took effect before retrying.",
|
|
1382
|
+
status: err.status,
|
|
1383
|
+
retryable: false,
|
|
1384
|
+
requestId: err.requestId ?? requestIdFromBody(err.body),
|
|
1385
|
+
details: {
|
|
1386
|
+
dssCategory: err.category,
|
|
1387
|
+
statusText: canonicalStatusText(err.status),
|
|
1388
|
+
idempotency: "none",
|
|
1389
|
+
...(err.retry ? { retry: err.retry, } : {}),
|
|
1390
|
+
},
|
|
1263
1391
|
...context,
|
|
1264
1392
|
};
|
|
1265
1393
|
}
|
|
@@ -1374,8 +1502,8 @@ async function main() {
|
|
|
1374
1502
|
throw unknownActionError(resource, action, Object.keys(resourceActions));
|
|
1375
1503
|
validateSupportedCommandFlags(resource, action, flags);
|
|
1376
1504
|
const args = positional.slice(2);
|
|
1377
|
-
if (flags
|
|
1378
|
-
const plan = buildMutationPlan(resource, action, actionMeta, args, flags);
|
|
1505
|
+
if (executionMode(flags).plan) {
|
|
1506
|
+
const plan = contract.buildMutationPlan(resource, action, actionMeta, args, flags);
|
|
1379
1507
|
writeCommandResult(plan);
|
|
1380
1508
|
return;
|
|
1381
1509
|
}
|
|
@@ -1387,6 +1515,7 @@ async function main() {
|
|
|
1387
1515
|
writeCommandResult(result);
|
|
1388
1516
|
return;
|
|
1389
1517
|
}
|
|
1518
|
+
actionMeta.validate?.(args, flags);
|
|
1390
1519
|
const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
|
|
1391
1520
|
currentCommandContext.projectKey = projectKey;
|
|
1392
1521
|
if (!url) {
|
|
@@ -1397,6 +1526,7 @@ async function main() {
|
|
|
1397
1526
|
}
|
|
1398
1527
|
const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
|
|
1399
1528
|
const retryMaxAttempts = num(flags["retries"], "--retries");
|
|
1529
|
+
const { DataikuClient, } = await import("./client.js");
|
|
1400
1530
|
const client = new DataikuClient({
|
|
1401
1531
|
url,
|
|
1402
1532
|
apiKey,
|
|
@@ -1416,7 +1546,8 @@ async function main() {
|
|
|
1416
1546
|
throw new CommandResultFailure(result, failureExitCode);
|
|
1417
1547
|
writeCommandResult(result);
|
|
1418
1548
|
}
|
|
1419
|
-
main().catch((err) => {
|
|
1549
|
+
main().catch(async (err) => {
|
|
1550
|
+
await loadCommandRuntime();
|
|
1420
1551
|
writeErrorReport(err);
|
|
1421
1552
|
process.exitCode = errorExitCode(err);
|
|
1422
1553
|
});
|