dataiku-sdk 3.1.0 → 3.3.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 +253 -38
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +102 -0
- package/dist/packages/types/src/index.js +76 -0
- 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 +61 -18
- package/dist/src/cli/commands/bundle.js +3 -2
- package/dist/src/cli/commands/code-env.js +23 -16
- package/dist/src/cli/commands/connection.d.ts +9 -0
- package/dist/src/cli/commands/connection.js +267 -1
- package/dist/src/cli/commands/dashboard.js +7 -6
- package/dist/src/cli/commands/data-collection.d.ts +2 -0
- package/dist/src/cli/commands/data-collection.js +164 -0
- package/dist/src/cli/commands/data-quality.js +9 -8
- package/dist/src/cli/commands/dataset.js +170 -40
- 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 +4 -3
- package/dist/src/cli/commands/group.d.ts +2 -0
- package/dist/src/cli/commands/group.js +109 -0
- package/dist/src/cli/commands/index.js +151 -78
- package/dist/src/cli/commands/insight.js +8 -7
- package/dist/src/cli/commands/job.js +4 -3
- package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
- package/dist/src/cli/commands/knowledge-bank.js +80 -0
- package/dist/src/cli/commands/llm.d.ts +2 -0
- package/dist/src/cli/commands/llm.js +93 -0
- package/dist/src/cli/commands/macro.d.ts +2 -0
- package/dist/src/cli/commands/macro.js +221 -0
- package/dist/src/cli/commands/meaning.js +5 -4
- package/dist/src/cli/commands/ml-task.js +21 -8
- package/dist/src/cli/commands/model-evaluation-store.js +4 -3
- package/dist/src/cli/commands/notebook.js +17 -12
- package/dist/src/cli/commands/plugin.d.ts +2 -0
- package/dist/src/cli/commands/plugin.js +799 -0
- package/dist/src/cli/commands/project-folder.d.ts +2 -0
- package/dist/src/cli/commands/project-folder.js +197 -0
- package/dist/src/cli/commands/project-git.js +14 -54
- package/dist/src/cli/commands/project-library.js +7 -6
- package/dist/src/cli/commands/project.js +55 -8
- package/dist/src/cli/commands/recipe.js +50 -14
- package/dist/src/cli/commands/saved-model.d.ts +5 -0
- package/dist/src/cli/commands/saved-model.js +392 -4
- package/dist/src/cli/commands/scenario.js +147 -9
- package/dist/src/cli/commands/sql.js +3 -2
- package/dist/src/cli/commands/statistics.js +7 -6
- package/dist/src/cli/commands/user.d.ts +8 -0
- package/dist/src/cli/commands/user.js +296 -0
- 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 +2 -3
- package/dist/src/cli/contract.js +1167 -163
- package/dist/src/cli/flags.d.ts +4 -0
- package/dist/src/cli/flags.js +50 -2
- package/dist/src/cli/helpers/app-successor.d.ts +19 -4
- package/dist/src/cli/helpers/app-successor.js +90 -30
- package/dist/src/cli/helpers/cleanup.js +3 -1
- package/dist/src/cli/helpers/flow-zone.js +1 -2
- package/dist/src/cli/output.d.ts +1 -1
- package/dist/src/cli/output.js +23 -10
- package/dist/src/cli/usage.d.ts +1 -0
- package/dist/src/cli/usage.js +47 -1
- package/dist/src/cli.js +225 -95
- package/dist/src/client.d.ts +86 -6
- package/dist/src/client.js +263 -50
- package/dist/src/errors.d.ts +3 -0
- package/dist/src/errors.js +4 -0
- package/dist/src/index.d.ts +16 -7
- package/dist/src/index.js +11 -2
- package/dist/src/resources/analyses.js +1 -8
- package/dist/src/resources/applications.d.ts +8 -6
- package/dist/src/resources/applications.js +21 -21
- package/dist/src/resources/base.d.ts +4 -0
- package/dist/src/resources/base.js +15 -0
- package/dist/src/resources/bundles.d.ts +9 -4
- package/dist/src/resources/bundles.js +10 -5
- package/dist/src/resources/connections.d.ts +78 -1
- package/dist/src/resources/connections.js +132 -1
- package/dist/src/resources/data-collections.d.ts +118 -0
- package/dist/src/resources/data-collections.js +49 -0
- package/dist/src/resources/datasets.d.ts +69 -0
- package/dist/src/resources/datasets.js +99 -1
- package/dist/src/resources/folders.js +2 -1
- package/dist/src/resources/futures.d.ts +4 -1
- package/dist/src/resources/futures.js +38 -6
- package/dist/src/resources/groups.d.ts +47 -0
- package/dist/src/resources/groups.js +56 -0
- package/dist/src/resources/jobs.d.ts +8 -13
- package/dist/src/resources/jobs.js +41 -16
- package/dist/src/resources/knowledge-banks.d.ts +58 -0
- package/dist/src/resources/knowledge-banks.js +61 -0
- package/dist/src/resources/llms.d.ts +92 -0
- package/dist/src/resources/llms.js +62 -0
- package/dist/src/resources/macros.d.ts +121 -0
- package/dist/src/resources/macros.js +231 -0
- package/dist/src/resources/ml-tasks.d.ts +37 -2
- package/dist/src/resources/ml-tasks.js +100 -20
- package/dist/src/resources/model-evaluation-stores.js +1 -8
- package/dist/src/resources/notebooks.d.ts +43 -7
- package/dist/src/resources/notebooks.js +53 -13
- package/dist/src/resources/plugins.d.ts +265 -0
- package/dist/src/resources/plugins.js +458 -0
- package/dist/src/resources/project-folders.d.ts +71 -0
- package/dist/src/resources/project-folders.js +57 -0
- package/dist/src/resources/project-git.d.ts +6 -0
- package/dist/src/resources/project-git.js +69 -42
- package/dist/src/resources/project-library.d.ts +0 -1
- package/dist/src/resources/project-library.js +1 -17
- package/dist/src/resources/projects.d.ts +19 -3
- package/dist/src/resources/projects.js +30 -18
- package/dist/src/resources/recipes.d.ts +15 -1
- package/dist/src/resources/recipes.js +73 -6
- package/dist/src/resources/saved-models.d.ts +134 -1
- package/dist/src/resources/saved-models.js +214 -6
- package/dist/src/resources/scenarios.d.ts +105 -0
- package/dist/src/resources/scenarios.js +232 -106
- package/dist/src/resources/sql.js +5 -1
- package/dist/src/resources/users.d.ts +129 -0
- package/dist/src/resources/users.js +128 -0
- package/dist/src/resources/webapps.d.ts +15 -2
- package/dist/src/resources/webapps.js +18 -4
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.d.ts +13 -4
- package/dist/src/skill.js +75 -24
- package/dist/src/utils/deep-merge.d.ts +1 -1
- package/dist/src/utils/polling.d.ts +20 -0
- package/dist/src/utils/polling.js +27 -0
- package/dist/src/utils/response-file.d.ts +2 -0
- package/dist/src/utils/response-file.js +34 -0
- package/dist/src/utils/secret-sanitize.d.ts +54 -0
- package/dist/src/utils/secret-sanitize.js +101 -0
- package/package.json +8 -5
- package/packages/types/dist/index.d.ts +102 -0
- package/packages/types/dist/index.js +76 -0
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +47 -225
- package/skills/dataiku-dss/references/app-releases.md +28 -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,30 @@
|
|
|
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
6
|
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
7
|
import { dataikuEnvironmentEnabled, loadEnvFile, } from "./cli/env.js";
|
|
8
|
-
import { FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
|
|
8
|
+
import { executionMode, FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
|
|
9
9
|
import { cleanupLedgerEntry, } from "./cli/helpers/cleanup.js";
|
|
10
10
|
import { commandFailureExitCode, CommandResultFailure, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
|
|
11
11
|
import { currentCommandContext, resolveCredentials, resolveLoginCredentials, } from "./cli/runtime.js";
|
|
12
|
-
import { COMMANDS_RUN_HINT, missingActionError, noCommandError, requireArgs, unknownActionError, unknownResourceError, UsageError, } from "./cli/usage.js";
|
|
12
|
+
import { COMMANDS_RUN_HINT, inferRequiresProject, missingActionError, noCommandError, requireArgs, RESOURCE_NAMES, unknownActionError, unknownResourceError, UsageError, } from "./cli/usage.js";
|
|
13
13
|
import { cliVersionResult, } from "./cli/version.js";
|
|
14
|
-
import { DataikuClient, } from "./client.js";
|
|
15
14
|
import { getCredentialsPath, } from "./config.js";
|
|
16
15
|
import { canonicalStatusText, ClientValidationError, DataikuError, dataikuErrorCode, } from "./errors.js";
|
|
17
|
-
import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } from "./skill.js";
|
|
18
16
|
import { appendCleanupLedgerEntry, findCleanupLedgerBindingViolation, preflightCleanupLedgerPath, readCleanupLedger, reserveCleanupLedgerDssUrl, } from "./utils/cleanup-ledger.js";
|
|
19
17
|
import { canonicalDssUrl, } from "./utils/dss-url.js";
|
|
18
|
+
import { sanitizeSecrets, } from "./utils/secret-sanitize.js";
|
|
19
|
+
let contract;
|
|
20
|
+
let commandRuntimeLoad;
|
|
21
|
+
// Command definitions are resource-lazy; full contract metadata loads only
|
|
22
|
+
// after a valid action is selected or explicit discovery is requested.
|
|
23
|
+
function loadCommandRuntime() {
|
|
24
|
+
return commandRuntimeLoad ??= import("./cli/contract.js").then((module) => {
|
|
25
|
+
contract = module;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
20
28
|
// ---------------------------------------------------------------------------
|
|
21
29
|
// Arg parsing
|
|
22
30
|
// ---------------------------------------------------------------------------
|
|
@@ -60,7 +68,7 @@ function findUnboundLifecycleCleanup(entries) {
|
|
|
60
68
|
async function runCleanup(flags) {
|
|
61
69
|
const filePath = flags["file"];
|
|
62
70
|
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
63
|
-
throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
|
|
71
|
+
throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
|
|
64
72
|
}
|
|
65
73
|
let entries;
|
|
66
74
|
try {
|
|
@@ -95,6 +103,7 @@ async function runCleanup(flags) {
|
|
|
95
103
|
}
|
|
96
104
|
const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
|
|
97
105
|
const retryMaxAttempts = num(flags["retries"], "--retries");
|
|
106
|
+
const { DataikuClient, } = await import("./client.js");
|
|
98
107
|
const client = new DataikuClient({
|
|
99
108
|
url,
|
|
100
109
|
apiKey,
|
|
@@ -158,7 +167,7 @@ async function runCleanup(flags) {
|
|
|
158
167
|
action,
|
|
159
168
|
projectKey: parsed.flags["project-key"],
|
|
160
169
|
});
|
|
161
|
-
if (!resource || !action || !isAllowedCleanupAction(resource, action)
|
|
170
|
+
if (!resource || !action || !contract.isAllowedCleanupAction(resource, action)
|
|
162
171
|
|| !commands[resource]?.[action]) {
|
|
163
172
|
throw new UsageError(`Invalid cleanup argv: ${entry.cleanup.argv.join(" ")}`);
|
|
164
173
|
}
|
|
@@ -231,11 +240,11 @@ async function runCleanup(flags) {
|
|
|
231
240
|
}
|
|
232
241
|
function parseBatchSteps(payload) {
|
|
233
242
|
if (!Array.isArray(payload)) {
|
|
234
|
-
throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", BATCH_HINT, { example: BATCH_EXAMPLE_PAYLOAD, });
|
|
243
|
+
throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", contract.BATCH_HINT, { example: contract.BATCH_EXAMPLE_PAYLOAD, });
|
|
235
244
|
}
|
|
236
245
|
return payload.map((step, index) => {
|
|
237
246
|
if (!Array.isArray(step) || !step.every((token) => typeof token === "string")) {
|
|
238
|
-
throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", BATCH_HINT);
|
|
247
|
+
throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", contract.BATCH_HINT);
|
|
239
248
|
}
|
|
240
249
|
return step;
|
|
241
250
|
});
|
|
@@ -266,12 +275,14 @@ const META_COMMAND_RESOURCES = {
|
|
|
266
275
|
fixtures: true,
|
|
267
276
|
batch: true,
|
|
268
277
|
};
|
|
269
|
-
|
|
270
|
-
function cachedCommandRegistry() {
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
const commandRegistryCache = new Map();
|
|
279
|
+
function cachedCommandRegistry(resource) {
|
|
280
|
+
if (!contract)
|
|
281
|
+
return undefined;
|
|
282
|
+
if (!commandRegistryCache.has(resource)) {
|
|
283
|
+
commandRegistryCache.set(resource, contract.buildCommandRegistry(resource)[resource]);
|
|
284
|
+
}
|
|
285
|
+
return commandRegistryCache.get(resource);
|
|
275
286
|
}
|
|
276
287
|
function supportedCommandFlags(entry) {
|
|
277
288
|
const supported = { ...ALWAYS_ALLOWED_COMMAND_FLAGS, };
|
|
@@ -309,7 +320,7 @@ function commandRegistrySelectors(flags) {
|
|
|
309
320
|
return selectors;
|
|
310
321
|
}
|
|
311
322
|
function validateSupportedCommandFlags(resource, action, flags) {
|
|
312
|
-
const entry = cachedCommandRegistry()
|
|
323
|
+
const entry = cachedCommandRegistry(resource)?.[action];
|
|
313
324
|
if (!entry)
|
|
314
325
|
return;
|
|
315
326
|
const supported = supportedCommandFlags(entry);
|
|
@@ -318,7 +329,7 @@ function validateSupportedCommandFlags(resource, action, flags) {
|
|
|
318
329
|
throw new UsageError(`Unknown flag --${flagName} for ${resource} ${action}`, "unknown_flag", scopedCommandDiscoveryHint(resource, action));
|
|
319
330
|
}
|
|
320
331
|
}
|
|
321
|
-
if (flags
|
|
332
|
+
if (executionMode(flags).dryRun && !entry.dryRun) {
|
|
322
333
|
throw new UsageError(`--dry-run is not supported for ${resource} ${action}.`, "unknown_flag", entry.flags.some((flag) => flag.name === "plan")
|
|
323
334
|
? "Remove --dry-run; use --plan to preview the mutation instead."
|
|
324
335
|
: `Remove --dry-run; ${resource} ${action} has no dry-run mode.`);
|
|
@@ -381,7 +392,7 @@ function validateRequiredCommandInputs(resource, action, args, flags, entry) {
|
|
|
381
392
|
}
|
|
382
393
|
}
|
|
383
394
|
function validateRegistryCommandInputs(resource, action, args, flags) {
|
|
384
|
-
const entry = cachedCommandRegistry()
|
|
395
|
+
const entry = cachedCommandRegistry(resource)?.[action];
|
|
385
396
|
if (!entry)
|
|
386
397
|
return;
|
|
387
398
|
validateSupportedCommandFlags(resource, action, flags);
|
|
@@ -404,11 +415,21 @@ const batchDryRunClientHandler = {
|
|
|
404
415
|
throw new BatchDryRunValidationComplete();
|
|
405
416
|
},
|
|
406
417
|
};
|
|
407
|
-
const
|
|
418
|
+
const batchDryRunResource = new Proxy(batchDryRunClientTarget, batchDryRunClientHandler);
|
|
419
|
+
let batchDryRunClient;
|
|
408
420
|
async function validateDryRunHandlerPreconditions(handler, args, flags) {
|
|
409
421
|
const readsFromStdin = flags["stdin"] === true || flags["sql"] === "-";
|
|
410
422
|
if (readsFromStdin)
|
|
411
423
|
return;
|
|
424
|
+
if (!batchDryRunClient) {
|
|
425
|
+
const { DataikuClient, } = await import("./client.js");
|
|
426
|
+
batchDryRunClient = new Proxy(new DataikuClient({
|
|
427
|
+
url: "http://dry-run.invalid",
|
|
428
|
+
apiKey: "dry-run-validation",
|
|
429
|
+
}), {
|
|
430
|
+
get: (_target, property) => property === "then" ? undefined : batchDryRunResource,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
412
433
|
try {
|
|
413
434
|
await handler(batchDryRunClient, args, flags);
|
|
414
435
|
}
|
|
@@ -418,7 +439,7 @@ async function validateDryRunHandlerPreconditions(handler, args, flags) {
|
|
|
418
439
|
throw error;
|
|
419
440
|
}
|
|
420
441
|
}
|
|
421
|
-
function validateMetaCommandInputs(resource, action, flags) {
|
|
442
|
+
async function validateMetaCommandInputs(resource, action, flags) {
|
|
422
443
|
if (resource === "doctor") {
|
|
423
444
|
if (action !== undefined && action !== "run") {
|
|
424
445
|
throw unknownActionError("doctor", action, ["run",]);
|
|
@@ -426,11 +447,11 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
426
447
|
return "run";
|
|
427
448
|
}
|
|
428
449
|
if (resource === "auth") {
|
|
429
|
-
const validActions = Object.keys(AUTH_ACTIONS);
|
|
450
|
+
const validActions = Object.keys(contract.AUTH_ACTIONS);
|
|
430
451
|
if (!action) {
|
|
431
452
|
throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
|
|
432
453
|
}
|
|
433
|
-
if (!AUTH_ACTIONS[action]) {
|
|
454
|
+
if (!contract.AUTH_ACTIONS[action]) {
|
|
434
455
|
throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
|
|
435
456
|
}
|
|
436
457
|
return action;
|
|
@@ -439,6 +460,7 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
439
460
|
if (action !== undefined && action !== "run") {
|
|
440
461
|
throw unknownActionError("install-skill", action, ["run",]);
|
|
441
462
|
}
|
|
463
|
+
const { AGENTS, } = await import("./skill.js");
|
|
442
464
|
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
443
465
|
if (agentFilter && !AGENTS[agentFilter]) {
|
|
444
466
|
throw new UsageError(`Unknown agent: ${agentFilter}.`, "usage_error", COMMANDS_RUN_HINT, { agent: agentFilter, validAgents: Object.keys(AGENTS), });
|
|
@@ -447,14 +469,14 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
447
469
|
}
|
|
448
470
|
if (resource === "agent") {
|
|
449
471
|
if (!action)
|
|
450
|
-
throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
|
|
472
|
+
throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
|
|
451
473
|
if (action !== "contract")
|
|
452
474
|
throw unknownActionError("agent", action, ["contract",]);
|
|
453
475
|
return action;
|
|
454
476
|
}
|
|
455
477
|
if (resource === "commands") {
|
|
456
478
|
if (!action)
|
|
457
|
-
throw missingActionError("commands", ["run",], COMMANDS_USAGE);
|
|
479
|
+
throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
|
|
458
480
|
if (action !== "run")
|
|
459
481
|
throw unknownActionError("commands", action, ["run",]);
|
|
460
482
|
validateSupportedCommandFlags("commands", "run", flags);
|
|
@@ -471,7 +493,7 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
471
493
|
if (action !== undefined && action !== "run") {
|
|
472
494
|
throw unknownActionError("cleanup", action, ["run",]);
|
|
473
495
|
}
|
|
474
|
-
const entry = cachedCommandRegistry()
|
|
496
|
+
const entry = cachedCommandRegistry("cleanup")?.run;
|
|
475
497
|
if (entry)
|
|
476
498
|
validateRequiredCommandInputs("cleanup", "run", [], flags, entry);
|
|
477
499
|
return "run";
|
|
@@ -486,7 +508,7 @@ function validateMetaCommandInputs(resource, action, flags) {
|
|
|
486
508
|
if (action !== undefined && action !== "run") {
|
|
487
509
|
throw unknownActionError("batch", action, ["run",]);
|
|
488
510
|
}
|
|
489
|
-
const entry = cachedCommandRegistry()
|
|
511
|
+
const entry = cachedCommandRegistry("batch")?.run;
|
|
490
512
|
if (entry)
|
|
491
513
|
validateRequiredCommandInputs("batch", "run", [], flags, entry);
|
|
492
514
|
return "run";
|
|
@@ -499,7 +521,7 @@ async function validateBatchStep(argv) {
|
|
|
499
521
|
const action = positional[1];
|
|
500
522
|
if (!resource)
|
|
501
523
|
throw noCommandError();
|
|
502
|
-
const metaAction = validateMetaCommandInputs(resource, action, flags);
|
|
524
|
+
const metaAction = await validateMetaCommandInputs(resource, action, flags);
|
|
503
525
|
if (metaAction)
|
|
504
526
|
return { positional, flags, resource, action: metaAction, };
|
|
505
527
|
const resourceActions = commands[resource];
|
|
@@ -518,7 +540,7 @@ async function validateBatchStep(argv) {
|
|
|
518
540
|
function batchStepNeedsClient(argv) {
|
|
519
541
|
try {
|
|
520
542
|
const { positional, flags, } = parseArgs(argv);
|
|
521
|
-
if (flags
|
|
543
|
+
if (executionMode(flags).plan)
|
|
522
544
|
return false;
|
|
523
545
|
const resource = positional[0];
|
|
524
546
|
if (!resource)
|
|
@@ -600,7 +622,7 @@ function validateBatchStepMode(argv, index) {
|
|
|
600
622
|
catch {
|
|
601
623
|
return;
|
|
602
624
|
}
|
|
603
|
-
if (flags
|
|
625
|
+
if (!executionMode(flags).plan && !executionMode(flags).dryRun)
|
|
604
626
|
return;
|
|
605
627
|
const resource = positional[0];
|
|
606
628
|
const action = positional[1];
|
|
@@ -614,13 +636,13 @@ function validateBatchStepMode(argv, index) {
|
|
|
614
636
|
const meta = resourceActions[action ?? ""];
|
|
615
637
|
if (!meta)
|
|
616
638
|
return;
|
|
617
|
-
const entry = cachedCommandRegistry()
|
|
639
|
+
const entry = cachedCommandRegistry(resource)?.[action ?? ""];
|
|
618
640
|
if (!entry)
|
|
619
641
|
return;
|
|
620
|
-
if (flags
|
|
642
|
+
if (executionMode(flags).plan && !entry.flags.some((flag) => flag.name === "plan")) {
|
|
621
643
|
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.");
|
|
622
644
|
}
|
|
623
|
-
if (flags
|
|
645
|
+
if (executionMode(flags).dryRun && !entry.dryRun) {
|
|
624
646
|
throw new UsageError(`Batch step ${index} requests --dry-run, which is not supported for ${resource} ${action}.`, "unknown_flag", "Remove --dry-run from the step.");
|
|
625
647
|
}
|
|
626
648
|
}
|
|
@@ -629,7 +651,8 @@ function validateBatchStepModes(steps) {
|
|
|
629
651
|
validateBatchStepMode(steps[index], index);
|
|
630
652
|
}
|
|
631
653
|
}
|
|
632
|
-
function runInstallSkill(flags) {
|
|
654
|
+
async function runInstallSkill(flags) {
|
|
655
|
+
const { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } = await import("./skill.js");
|
|
633
656
|
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
634
657
|
const isGlobal = flags["global"] === true;
|
|
635
658
|
const targetDir = typeof flags["target"] === "string" ? flags["target"] : undefined;
|
|
@@ -655,25 +678,29 @@ function runInstallSkill(flags) {
|
|
|
655
678
|
throw new UsageError("No coding agents detected.", "usage_error", "Use --agent NAME to choose one of the supported agents.", { validAgents: Object.keys(AGENTS), });
|
|
656
679
|
}
|
|
657
680
|
const scope = isGlobal ? "global" : "project";
|
|
658
|
-
const cwd =
|
|
681
|
+
const cwd = isGlobal
|
|
682
|
+
? homedir()
|
|
683
|
+
: targetDir
|
|
684
|
+
? resolve(targetDir)
|
|
685
|
+
: findWorkspaceRoot(process.cwd());
|
|
659
686
|
const installed = planSkillInstalls(targets, { global: isGlobal, cwd, });
|
|
660
|
-
if (flags
|
|
687
|
+
if (executionMode(flags).plan) {
|
|
661
688
|
return planResult("install-skill", "run", {
|
|
662
689
|
identifiers: { scope, target: cwd, },
|
|
663
690
|
payload: { installed, },
|
|
664
691
|
idempotency: "none",
|
|
665
692
|
asyncKind: "none",
|
|
666
693
|
exitCodesOnFailure: { usage: 1, error: 2, transient: 3, },
|
|
667
|
-
plannedAndDryRun: flags
|
|
694
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
668
695
|
});
|
|
669
696
|
}
|
|
670
697
|
return {
|
|
671
698
|
scope,
|
|
672
699
|
target: cwd,
|
|
673
|
-
installed: flags
|
|
700
|
+
installed: executionMode(flags).dryRun
|
|
674
701
|
? installed
|
|
675
702
|
: installSkill(targets, { global: isGlobal, cwd, }),
|
|
676
|
-
...(flags
|
|
703
|
+
...(executionMode(flags).dryRun ? { dryRun: true, } : {}),
|
|
677
704
|
};
|
|
678
705
|
}
|
|
679
706
|
const META_PLAN_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
|
|
@@ -706,7 +733,7 @@ function authLoginPlan(flags) {
|
|
|
706
733
|
function cleanupPlan(flags) {
|
|
707
734
|
const filePath = flags["file"];
|
|
708
735
|
if (typeof filePath !== "string" || filePath.trim().length === 0) {
|
|
709
|
-
throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
|
|
736
|
+
throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
|
|
710
737
|
}
|
|
711
738
|
return planResult("cleanup", "run", {
|
|
712
739
|
identifiers: { file: filePath, },
|
|
@@ -717,13 +744,13 @@ function cleanupPlan(flags) {
|
|
|
717
744
|
idempotency: "none",
|
|
718
745
|
asyncKind: "none",
|
|
719
746
|
exitCodesOnFailure: META_PLAN_EXIT_CODES,
|
|
720
|
-
plannedAndDryRun: flags
|
|
747
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
721
748
|
});
|
|
722
749
|
}
|
|
723
750
|
function batchPlan(flags) {
|
|
724
751
|
const payload = unknownJsonInput(flags);
|
|
725
752
|
if (payload === undefined) {
|
|
726
|
-
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
|
|
753
|
+
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
|
|
727
754
|
}
|
|
728
755
|
const steps = parseBatchSteps(payload);
|
|
729
756
|
return planResult("batch", "run", {
|
|
@@ -734,79 +761,89 @@ function batchPlan(flags) {
|
|
|
734
761
|
payload: {
|
|
735
762
|
steps: steps.map((argv) => redactArgv(argv)),
|
|
736
763
|
continueOnError: flags["continue-on-error"] === true,
|
|
737
|
-
dryRun: flags
|
|
764
|
+
dryRun: executionMode(flags).dryRun,
|
|
738
765
|
},
|
|
739
766
|
idempotency: "none",
|
|
740
767
|
asyncKind: "none",
|
|
741
|
-
exitCodesOnFailure: BATCH_PLAN_EXIT_CODES,
|
|
742
|
-
plannedAndDryRun: flags
|
|
768
|
+
exitCodesOnFailure: contract.BATCH_PLAN_EXIT_CODES,
|
|
769
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
743
770
|
});
|
|
744
771
|
}
|
|
745
772
|
async function runMetaCommand(resource, action, flags) {
|
|
746
773
|
if (resource === "doctor") {
|
|
774
|
+
await loadCommandRuntime();
|
|
747
775
|
if (action !== undefined && action !== "run") {
|
|
748
776
|
throw unknownActionError("doctor", action, ["run",]);
|
|
749
777
|
}
|
|
750
778
|
currentCommandContext.action = action ?? "run";
|
|
779
|
+
const { runDoctor, } = await import("./cli/doctor.js");
|
|
751
780
|
const { result, exitCode, } = await runDoctor(flags);
|
|
752
781
|
return { action: "run", result, exitCode, };
|
|
753
782
|
}
|
|
754
783
|
if (resource === "auth") {
|
|
755
|
-
|
|
784
|
+
await loadCommandRuntime();
|
|
785
|
+
const validActions = Object.keys(contract.AUTH_ACTIONS);
|
|
756
786
|
if (!action) {
|
|
757
787
|
throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
|
|
758
788
|
}
|
|
759
789
|
currentCommandContext.action = action;
|
|
760
|
-
const authMeta = AUTH_ACTIONS[action];
|
|
790
|
+
const authMeta = contract.AUTH_ACTIONS[action];
|
|
761
791
|
if (!authMeta) {
|
|
762
792
|
throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
|
|
763
793
|
}
|
|
764
|
-
if (flags
|
|
794
|
+
if (executionMode(flags).plan) {
|
|
765
795
|
return { action, result: authLoginPlan(flags), exitCode: 0, };
|
|
766
796
|
}
|
|
767
797
|
return { action, result: await authMeta.handler(flags), exitCode: 0, };
|
|
768
798
|
}
|
|
769
799
|
if (resource === "install-skill") {
|
|
800
|
+
await loadCommandRuntime();
|
|
770
801
|
if (action !== undefined && action !== "run") {
|
|
771
802
|
throw unknownActionError("install-skill", action, ["run",]);
|
|
772
803
|
}
|
|
773
804
|
currentCommandContext.action = action ?? "run";
|
|
774
|
-
return { action: "run", result: runInstallSkill(flags), exitCode: 0, };
|
|
805
|
+
return { action: "run", result: await runInstallSkill(flags), exitCode: 0, };
|
|
775
806
|
}
|
|
776
807
|
if (resource === "agent") {
|
|
808
|
+
await loadCommandRuntime();
|
|
777
809
|
if (!action)
|
|
778
|
-
throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
|
|
810
|
+
throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
|
|
779
811
|
currentCommandContext.action = action;
|
|
780
812
|
if (action !== "contract")
|
|
781
813
|
throw unknownActionError("agent", action, ["contract",]);
|
|
782
|
-
return { action, result: buildAgentContract(), exitCode: 0, };
|
|
814
|
+
return { action, result: contract.buildAgentContract(), exitCode: 0, };
|
|
783
815
|
}
|
|
784
816
|
if (resource === "commands") {
|
|
817
|
+
await loadCommandRuntime();
|
|
785
818
|
if (!action)
|
|
786
|
-
throw missingActionError("commands", ["run",], COMMANDS_USAGE);
|
|
819
|
+
throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
|
|
787
820
|
currentCommandContext.action = action;
|
|
788
821
|
if (action !== "run")
|
|
789
822
|
throw unknownActionError("commands", action, ["run",]);
|
|
790
823
|
validateSupportedCommandFlags("commands", "run", flags);
|
|
791
824
|
const selectors = commandRegistrySelectors(flags);
|
|
792
|
-
const
|
|
825
|
+
const outputPath = flags["output"];
|
|
826
|
+
if (selectors.length === 0 && typeof outputPath !== "string") {
|
|
827
|
+
return { action, result: contract.commandActionSummary(), exitCode: 0, };
|
|
828
|
+
}
|
|
829
|
+
const registry = selectors.length === 0 ? contract.buildCommandRegistry() : {};
|
|
793
830
|
for (const selector of selectors) {
|
|
794
831
|
const selectorParts = selector.split(".");
|
|
795
832
|
if (selectorParts.some((part) => part.length === 0)) {
|
|
796
|
-
throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", COMMANDS_USAGE, { selector, });
|
|
833
|
+
throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", contract.COMMANDS_USAGE, { selector, });
|
|
797
834
|
}
|
|
798
835
|
const [selectedResource, selectedAction,] = selectorParts;
|
|
799
|
-
const resourceActions =
|
|
836
|
+
const resourceActions = cachedCommandRegistry(selectedResource);
|
|
800
837
|
if (!resourceActions)
|
|
801
838
|
throw unknownResourceError(selectedResource);
|
|
839
|
+
registry[selectedResource] = resourceActions;
|
|
802
840
|
if (selectedAction && !resourceActions[selectedAction]) {
|
|
803
841
|
throw unknownActionError(selectedResource, selectedAction, Object.keys(resourceActions));
|
|
804
842
|
}
|
|
805
843
|
}
|
|
806
|
-
const outputPath = flags["output"];
|
|
807
844
|
const selectedRegistry = selectors.length === 0
|
|
808
845
|
? registry
|
|
809
|
-
: projectResultFields(registry, selectors);
|
|
846
|
+
: projectResultFields(registry, selectors, () => Object.keys(contract.commandActionSummary()).sort());
|
|
810
847
|
if (typeof outputPath === "string") {
|
|
811
848
|
const exported = selectedRegistry;
|
|
812
849
|
await fs.writeFile(outputPath, `${JSON.stringify(exported)}\n`, "utf-8");
|
|
@@ -814,7 +851,7 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
814
851
|
}
|
|
815
852
|
return {
|
|
816
853
|
action,
|
|
817
|
-
result:
|
|
854
|
+
result: selectedRegistry,
|
|
818
855
|
exitCode: 0,
|
|
819
856
|
};
|
|
820
857
|
}
|
|
@@ -826,29 +863,33 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
826
863
|
return { action: "run", result: cliVersionResult(), exitCode: 0, };
|
|
827
864
|
}
|
|
828
865
|
if (resource === "cleanup") {
|
|
866
|
+
await loadCommandRuntime();
|
|
829
867
|
if (action !== undefined && action !== "run") {
|
|
830
868
|
throw unknownActionError("cleanup", action, ["run",]);
|
|
831
869
|
}
|
|
832
870
|
currentCommandContext.action = action ?? "run";
|
|
833
|
-
if (flags
|
|
871
|
+
if (executionMode(flags).plan) {
|
|
834
872
|
return { action: "run", result: cleanupPlan(flags), exitCode: 0, };
|
|
835
873
|
}
|
|
836
874
|
const { result, exitCode, } = await runCleanup(flags);
|
|
837
875
|
return { action: "run", result, exitCode, };
|
|
838
876
|
}
|
|
839
877
|
if (resource === "fixtures") {
|
|
878
|
+
await loadCommandRuntime();
|
|
840
879
|
if (action !== undefined && action !== "run") {
|
|
841
880
|
throw unknownActionError("fixtures", action, ["run",]);
|
|
842
881
|
}
|
|
843
882
|
currentCommandContext.action = action ?? "run";
|
|
883
|
+
const { runFixtures, } = await import("./cli/doctor.js");
|
|
844
884
|
return { action: "run", result: await runFixtures(flags), exitCode: 0, };
|
|
845
885
|
}
|
|
846
886
|
if (resource === "batch") {
|
|
887
|
+
await loadCommandRuntime();
|
|
847
888
|
if (action !== undefined && action !== "run") {
|
|
848
889
|
throw unknownActionError("batch", action, ["run",]);
|
|
849
890
|
}
|
|
850
891
|
currentCommandContext.action = action ?? "run";
|
|
851
|
-
if (flags
|
|
892
|
+
if (executionMode(flags).plan) {
|
|
852
893
|
return { action: "run", result: batchPlan(flags), exitCode: 0, };
|
|
853
894
|
}
|
|
854
895
|
const { result, exitCode, } = await runBatch(flags);
|
|
@@ -862,9 +903,9 @@ async function runMetaCommand(resource, action, flags) {
|
|
|
862
903
|
* share this guard.
|
|
863
904
|
*/
|
|
864
905
|
function assertCleanupLedgerSupported(resource, action, flags) {
|
|
865
|
-
if (typeof flags["record-cleanup"] !== "string" || flags
|
|
906
|
+
if (typeof flags["record-cleanup"] !== "string" || executionMode(flags).dryRun)
|
|
866
907
|
return;
|
|
867
|
-
if (!supportsCleanupLedger(resource, action)) {
|
|
908
|
+
if (!contract.supportsCleanupLedger(resource, action)) {
|
|
868
909
|
throw new UsageError(`--record-cleanup is not supported for ${resource} ${action}.`);
|
|
869
910
|
}
|
|
870
911
|
if (resource === "workspace" && action === "create" && flags["stdin"] === true) {
|
|
@@ -878,7 +919,7 @@ function assertCleanupLedgerSupported(resource, action, flags) {
|
|
|
878
919
|
* steps both call it, and dry-run/preview runs never touch the ledger.
|
|
879
920
|
*/
|
|
880
921
|
async function preflightCleanupLedgerForFlags(client, flags) {
|
|
881
|
-
if (flags
|
|
922
|
+
if (executionMode(flags).dryRun)
|
|
882
923
|
return;
|
|
883
924
|
const ledgerPath = flags["record-cleanup"];
|
|
884
925
|
if (typeof ledgerPath !== "string")
|
|
@@ -929,7 +970,7 @@ async function preflightCleanupLedgerForFlags(client, flags) {
|
|
|
929
970
|
*/
|
|
930
971
|
async function recordCleanupLedgerEntry(client, resource, action, args, flags, result, projectKey) {
|
|
931
972
|
const ledgerPath = flags["record-cleanup"];
|
|
932
|
-
if (typeof ledgerPath !== "string" || flags
|
|
973
|
+
if (typeof ledgerPath !== "string" || executionMode(flags).dryRun)
|
|
933
974
|
return;
|
|
934
975
|
const entry = cleanupLedgerEntry(resource, action, args, flags, result, projectKey);
|
|
935
976
|
if (entry) {
|
|
@@ -946,10 +987,10 @@ async function recordCleanupLedgerEntry(client, resource, action, args, flags, r
|
|
|
946
987
|
async function runBatch(flags) {
|
|
947
988
|
const payload = unknownJsonInput(flags);
|
|
948
989
|
if (payload === undefined) {
|
|
949
|
-
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
|
|
990
|
+
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
|
|
950
991
|
}
|
|
951
992
|
const steps = parseBatchSteps(payload);
|
|
952
|
-
if (flags
|
|
993
|
+
if (executionMode(flags).dryRun) {
|
|
953
994
|
const batchProjectKey = typeof flags["project-key"] === "string"
|
|
954
995
|
? flags["project-key"]
|
|
955
996
|
: dataikuEnvironmentEnabled()
|
|
@@ -1016,6 +1057,7 @@ async function runBatch(flags) {
|
|
|
1016
1057
|
env: ["DATAIKU_API_KEY",],
|
|
1017
1058
|
});
|
|
1018
1059
|
}
|
|
1060
|
+
const { DataikuClient, } = await import("./client.js");
|
|
1019
1061
|
client = new DataikuClient({
|
|
1020
1062
|
url,
|
|
1021
1063
|
apiKey,
|
|
@@ -1078,8 +1120,8 @@ async function runBatch(flags) {
|
|
|
1078
1120
|
if (!meta)
|
|
1079
1121
|
throw unknownActionError(resource, action, Object.keys(resourceActions));
|
|
1080
1122
|
validateSupportedCommandFlags(resource, action, stepFlags);
|
|
1081
|
-
if (stepFlags
|
|
1082
|
-
result = buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
|
|
1123
|
+
if (executionMode(stepFlags).plan) {
|
|
1124
|
+
result = contract.buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
|
|
1083
1125
|
}
|
|
1084
1126
|
else {
|
|
1085
1127
|
if (!client) {
|
|
@@ -1159,27 +1201,12 @@ function summarizeCleanupFailures(failures) {
|
|
|
1159
1201
|
: [];
|
|
1160
1202
|
}));
|
|
1161
1203
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
return next && !next.startsWith("-") ? next : undefined;
|
|
1169
|
-
}
|
|
1170
|
-
if (arg.startsWith(`${longFlag}=`))
|
|
1171
|
-
return arg.slice(longFlag.length + 1);
|
|
1172
|
-
}
|
|
1173
|
-
return undefined;
|
|
1174
|
-
}
|
|
1175
|
-
function commandIsProjectScoped(resource, action) {
|
|
1176
|
-
if (!resource)
|
|
1177
|
-
return false;
|
|
1178
|
-
const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
|
|
1179
|
-
return inferRequiresProject(resource, action ?? "", usage);
|
|
1180
|
-
}
|
|
1181
|
-
function rawCommandContext() {
|
|
1182
|
-
const argv = process.argv.slice(2);
|
|
1204
|
+
/**
|
|
1205
|
+
* Positional arguments scanned with the same flag grammar as `parseArgs`, so
|
|
1206
|
+
* error reporting can recover the invoked resource/action without depending
|
|
1207
|
+
* on the command registry being loaded.
|
|
1208
|
+
*/
|
|
1209
|
+
function rawPositionals(argv) {
|
|
1183
1210
|
const positionals = [];
|
|
1184
1211
|
for (let index = 0; index < argv.length; index++) {
|
|
1185
1212
|
const arg = argv[index];
|
|
@@ -1202,6 +1229,30 @@ function rawCommandContext() {
|
|
|
1202
1229
|
}
|
|
1203
1230
|
positionals.push(arg);
|
|
1204
1231
|
}
|
|
1232
|
+
return positionals;
|
|
1233
|
+
}
|
|
1234
|
+
function rawFlagValue(argv, flagName) {
|
|
1235
|
+
const longFlag = `--${flagName}`;
|
|
1236
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1237
|
+
const arg = argv[index];
|
|
1238
|
+
if (arg === longFlag) {
|
|
1239
|
+
const next = argv[index + 1];
|
|
1240
|
+
return next && !next.startsWith("-") ? next : undefined;
|
|
1241
|
+
}
|
|
1242
|
+
if (arg.startsWith(`${longFlag}=`))
|
|
1243
|
+
return arg.slice(longFlag.length + 1);
|
|
1244
|
+
}
|
|
1245
|
+
return undefined;
|
|
1246
|
+
}
|
|
1247
|
+
function commandIsProjectScoped(resource, action) {
|
|
1248
|
+
if (!resource)
|
|
1249
|
+
return false;
|
|
1250
|
+
const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
|
|
1251
|
+
return inferRequiresProject(resource, action ?? "", usage);
|
|
1252
|
+
}
|
|
1253
|
+
function rawCommandContext() {
|
|
1254
|
+
const argv = process.argv.slice(2);
|
|
1255
|
+
const positionals = rawPositionals(argv);
|
|
1205
1256
|
const resource = currentCommandContext.resource ?? positionals[0];
|
|
1206
1257
|
const action = currentCommandContext.action ?? positionals[1];
|
|
1207
1258
|
const explicitProjectKey = rawFlagValue(argv, "project-key") ?? rawFlagValue(argv, "project");
|
|
@@ -1252,6 +1303,67 @@ function requestIdFromBody(body) {
|
|
|
1252
1303
|
}
|
|
1253
1304
|
return undefined;
|
|
1254
1305
|
}
|
|
1306
|
+
/** DSS diagnostic fields, in the resource layer's read order (detailedMessage → message → errorMessage). */
|
|
1307
|
+
const DSS_DIAGNOSTIC_MESSAGE_FIELDS = ["detailedMessage", "message", "errorMessage",];
|
|
1308
|
+
const MAX_DSS_DIAGNOSTIC_LENGTH = 300;
|
|
1309
|
+
/**
|
|
1310
|
+
* Credential material for redacting a server echo in a receipt: resolved
|
|
1311
|
+
* through the same resolver every command uses (flag → env → saved), so the
|
|
1312
|
+
* report path never grows a second credential-precedence convention.
|
|
1313
|
+
*/
|
|
1314
|
+
function cliSecretValues() {
|
|
1315
|
+
try {
|
|
1316
|
+
const { apiKey, } = resolveCredentials(parseArgs(process.argv.slice(2)).flags);
|
|
1317
|
+
return apiKey ? [apiKey,] : [];
|
|
1318
|
+
}
|
|
1319
|
+
catch {
|
|
1320
|
+
return [];
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Bounded, sanitized server diagnostic preserved on an ambiguous mutation
|
|
1325
|
+
* envelope (`dssMessage`/`dssErrorType`, next to the existing `dssCategory`):
|
|
1326
|
+
* the DSS error text and Java error type the response body carried, so the
|
|
1327
|
+
* receipt explains why the server failed while the mutation outcome stays
|
|
1328
|
+
* unknown. Whitespace collapses to one line, credential material is redacted,
|
|
1329
|
+
* long text truncates, and the raw body/stack never pass through.
|
|
1330
|
+
*/
|
|
1331
|
+
function dssDiagnosticDetails(err) {
|
|
1332
|
+
try {
|
|
1333
|
+
const parsed = JSON.parse(err.body);
|
|
1334
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1335
|
+
return {};
|
|
1336
|
+
const record = parsed;
|
|
1337
|
+
let secrets;
|
|
1338
|
+
const diagnostic = (value) => {
|
|
1339
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
1340
|
+
return undefined;
|
|
1341
|
+
secrets ??= cliSecretValues();
|
|
1342
|
+
const sanitized = sanitizeSecrets(value.replace(/\s+/g, " ").trim(), {
|
|
1343
|
+
sensitiveKeys: {},
|
|
1344
|
+
secrets,
|
|
1345
|
+
redactUrlUserinfo: true,
|
|
1346
|
+
});
|
|
1347
|
+
return sanitized.length > MAX_DSS_DIAGNOSTIC_LENGTH
|
|
1348
|
+
? `${sanitized.slice(0, MAX_DSS_DIAGNOSTIC_LENGTH)}…`
|
|
1349
|
+
: sanitized;
|
|
1350
|
+
};
|
|
1351
|
+
let message;
|
|
1352
|
+
for (const field of DSS_DIAGNOSTIC_MESSAGE_FIELDS) {
|
|
1353
|
+
message = diagnostic(record[field]);
|
|
1354
|
+
if (message !== undefined)
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
const errorType = diagnostic(record["errorType"]);
|
|
1358
|
+
return {
|
|
1359
|
+
...(message !== undefined ? { dssMessage: message, } : {}),
|
|
1360
|
+
...(errorType !== undefined ? { dssErrorType: errorType, } : {}),
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
catch {
|
|
1364
|
+
return {};
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1255
1367
|
const MISSING_PROJECT_KEY_ERROR_PREFIX = "projectKey is required";
|
|
1256
1368
|
function isAmbiguousMutationFailure(err, context) {
|
|
1257
1369
|
if (!(err instanceof DataikuError))
|
|
@@ -1262,7 +1374,7 @@ function isAmbiguousMutationFailure(err, context) {
|
|
|
1262
1374
|
if (err.status !== 0 && err.status < 500)
|
|
1263
1375
|
return false;
|
|
1264
1376
|
const entry = context.resource && context.action
|
|
1265
|
-
? cachedCommandRegistry(
|
|
1377
|
+
? cachedCommandRegistry(context.resource)?.[context.action]
|
|
1266
1378
|
: undefined;
|
|
1267
1379
|
return entry?.sideEffect === "write" && entry.idempotency === "none";
|
|
1268
1380
|
}
|
|
@@ -1362,6 +1474,8 @@ function buildErrorReport(err) {
|
|
|
1362
1474
|
requestId: err.requestId ?? requestIdFromBody(err.body),
|
|
1363
1475
|
details: {
|
|
1364
1476
|
dssCategory: err.category,
|
|
1477
|
+
...(err.bodyTruncated ? { bodyTruncated: true, } : {}),
|
|
1478
|
+
...dssDiagnosticDetails(err),
|
|
1365
1479
|
statusText: canonicalStatusText(err.status),
|
|
1366
1480
|
idempotency: "none",
|
|
1367
1481
|
...(err.retry ? { retry: err.retry, } : {}),
|
|
@@ -1395,6 +1509,7 @@ function buildErrorReport(err) {
|
|
|
1395
1509
|
requestId: err.requestId ?? requestIdFromBody(err.body),
|
|
1396
1510
|
details: {
|
|
1397
1511
|
dssCategory: err.category,
|
|
1512
|
+
...(err.bodyTruncated ? { bodyTruncated: true, } : {}),
|
|
1398
1513
|
statusText: canonicalStatusText(err.status),
|
|
1399
1514
|
body: safeBody,
|
|
1400
1515
|
...(err.retry ? { retry: err.retry, } : {}),
|
|
@@ -1429,6 +1544,14 @@ function writeErrorReport(err) {
|
|
|
1429
1544
|
flushCliWarnings();
|
|
1430
1545
|
process.stdout.write(`${JSON.stringify(buildErrorReport(err))}\n`);
|
|
1431
1546
|
}
|
|
1547
|
+
/** Only ambiguous mutation failures require full contract metadata. */
|
|
1548
|
+
function errorReportNeedsCommandRuntime(err) {
|
|
1549
|
+
if (contract || !(err instanceof DataikuError))
|
|
1550
|
+
return false;
|
|
1551
|
+
const method = err.retry?.method.toUpperCase();
|
|
1552
|
+
return !!method && method !== "GET" && method !== "HEAD"
|
|
1553
|
+
&& (err.status === 0 || err.status >= 500);
|
|
1554
|
+
}
|
|
1432
1555
|
// ---------------------------------------------------------------------------
|
|
1433
1556
|
// Main
|
|
1434
1557
|
// ---------------------------------------------------------------------------
|
|
@@ -1467,7 +1590,8 @@ async function main() {
|
|
|
1467
1590
|
process.exitCode = metaCommand.exitCode;
|
|
1468
1591
|
return;
|
|
1469
1592
|
}
|
|
1470
|
-
|
|
1593
|
+
// Resource and action recovery require only this resource's definitions.
|
|
1594
|
+
if (!RESOURCE_NAMES.includes(resource))
|
|
1471
1595
|
throw unknownResourceError(resource);
|
|
1472
1596
|
const resourceActions = commands[resource];
|
|
1473
1597
|
if (positional.length === 1) {
|
|
@@ -1478,10 +1602,14 @@ async function main() {
|
|
|
1478
1602
|
const actionMeta = resourceActions[action];
|
|
1479
1603
|
if (!actionMeta)
|
|
1480
1604
|
throw unknownActionError(resource, action, Object.keys(resourceActions));
|
|
1605
|
+
await loadCommandRuntime();
|
|
1481
1606
|
validateSupportedCommandFlags(resource, action, flags);
|
|
1482
1607
|
const args = positional.slice(2);
|
|
1483
|
-
|
|
1484
|
-
|
|
1608
|
+
// Command-local validation runs for live, --plan, and --dry-run alike, so a
|
|
1609
|
+
// bad identifier is a usage error before any plan is built or request made.
|
|
1610
|
+
actionMeta.validate?.(args, flags);
|
|
1611
|
+
if (executionMode(flags).plan) {
|
|
1612
|
+
const plan = contract.buildMutationPlan(resource, action, actionMeta, args, flags);
|
|
1485
1613
|
writeCommandResult(plan);
|
|
1486
1614
|
return;
|
|
1487
1615
|
}
|
|
@@ -1493,7 +1621,6 @@ async function main() {
|
|
|
1493
1621
|
writeCommandResult(result);
|
|
1494
1622
|
return;
|
|
1495
1623
|
}
|
|
1496
|
-
actionMeta.validate?.(args, flags);
|
|
1497
1624
|
const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
|
|
1498
1625
|
currentCommandContext.projectKey = projectKey;
|
|
1499
1626
|
if (!url) {
|
|
@@ -1504,6 +1631,7 @@ async function main() {
|
|
|
1504
1631
|
}
|
|
1505
1632
|
const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
|
|
1506
1633
|
const retryMaxAttempts = num(flags["retries"], "--retries");
|
|
1634
|
+
const { DataikuClient, } = await import("./client.js");
|
|
1507
1635
|
const client = new DataikuClient({
|
|
1508
1636
|
url,
|
|
1509
1637
|
apiKey,
|
|
@@ -1523,7 +1651,9 @@ async function main() {
|
|
|
1523
1651
|
throw new CommandResultFailure(result, failureExitCode);
|
|
1524
1652
|
writeCommandResult(result);
|
|
1525
1653
|
}
|
|
1526
|
-
main().catch((err) => {
|
|
1654
|
+
main().catch(async (err) => {
|
|
1655
|
+
if (errorReportNeedsCommandRuntime(err))
|
|
1656
|
+
await loadCommandRuntime();
|
|
1527
1657
|
writeErrorReport(err);
|
|
1528
1658
|
process.exitCode = errorExitCode(err);
|
|
1529
1659
|
});
|