dataiku-sdk 3.0.0 → 3.1.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 +132 -17
- 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/cli/commands/api-service.js +62 -16
- package/dist/src/cli/commands/bundle.js +49 -15
- package/dist/src/cli/commands/code-env.js +210 -49
- package/dist/src/cli/commands/code.d.ts +19 -0
- package/dist/src/cli/commands/code.js +87 -15
- package/dist/src/cli/commands/dataset.js +8 -3
- package/dist/src/cli/commands/flow-zone.js +33 -5
- package/dist/src/cli/commands/notebook.js +72 -41
- 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 +121 -50
- package/dist/src/cli/commands/project.js +16 -2
- package/dist/src/cli/commands/recipe.js +37 -12
- package/dist/src/cli/commands/sql.d.ts +10 -0
- package/dist/src/cli/commands/sql.js +39 -17
- package/dist/src/cli/commands/webapp.js +67 -13
- package/dist/src/cli/contract.d.ts +15 -0
- package/dist/src/cli/contract.js +870 -107
- package/dist/src/cli/env.d.ts +9 -0
- package/dist/src/cli/env.js +20 -2
- package/dist/src/cli/flags.js +9 -0
- 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 +135 -28
- package/dist/src/client.d.ts +60 -0
- package/dist/src/client.js +237 -42
- package/dist/src/errors.d.ts +8 -2
- package/dist/src/errors.js +40 -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/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 -2
- package/dist/src/resources/projects.js +18 -2
- 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 +22 -2
- 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 +9 -0
- package/dist/src/skill.js +37 -4
- 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/package.json +4 -4
- 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 +74 -47
package/dist/src/cli/contract.js
CHANGED
|
@@ -4,19 +4,31 @@ import { validateCredentials, } from "../auth.js";
|
|
|
4
4
|
import { getCredentialsPath, saveCredentials, } from "../config.js";
|
|
5
5
|
import { DataikuError, } from "../errors.js";
|
|
6
6
|
import { APP_MANIFEST_CONCURRENCY_CONTROL, } from "../resources/applications.js";
|
|
7
|
-
import {
|
|
7
|
+
import { encodeGitReferencePath, validateGitReferencePath, } from "../resources/project-git.js";
|
|
8
|
+
import { encodeLibraryPath, PROJECT_LIBRARY_CONCURRENCY_CONTROL, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../resources/project-library.js";
|
|
9
|
+
import { CodeEnvDetailsSchema, CodeEnvLogSummaryArraySchema, CodeEnvSummaryArraySchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, FlowZoneArraySchema, FlowZoneSchema, JobSummaryArraySchema, JobWaitResultSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, NotebookSessionArraySchema, ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, RecipeSummaryArraySchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlQueryResponseSchema, } from "../schemas.js";
|
|
10
|
+
import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, recipeInputDatasetsFromFlags, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
|
|
11
|
+
import { parseCodeRunIntegerFlag, resolveCodeInputWithSource, } from "./commands/code.js";
|
|
8
12
|
import { commands, } from "./commands/index.js";
|
|
13
|
+
import { projectLibraryPutPayload, } from "./commands/project-library.js";
|
|
14
|
+
import { resolveSqlQueryInvocation, } from "./commands/sql.js";
|
|
9
15
|
import { dataikuEnvironmentEnabled, } from "./env.js";
|
|
10
16
|
import { BOOLEAN_FLAGS, FLAG_ALIASES, KNOWN_LONG_FLAGS, SHORT_FLAGS, } from "./flags.js";
|
|
11
17
|
import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-zone.js";
|
|
12
18
|
import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
|
|
13
19
|
import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
|
|
14
|
-
import {
|
|
20
|
+
import { resolveLoginCredentials, } from "./runtime.js";
|
|
15
21
|
import { requireArgs, UsageError, } from "./usage.js";
|
|
16
22
|
import { AGENT_CONTRACT_SCHEMA_ID, AGENT_CONTRACT_VERSION, cliVersionResult, JSON_SCHEMA_DRAFT, } from "./version.js";
|
|
17
23
|
function codeEnvWait(flags) {
|
|
18
24
|
return flags["no-wait"] !== true;
|
|
19
25
|
}
|
|
26
|
+
function codeEnvLang(value, usage) {
|
|
27
|
+
if (value !== "PYTHON" && value !== "R") {
|
|
28
|
+
throw new UsageError(`Invalid code environment language ${JSON.stringify(value)}. Usage: ${usage}`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
20
32
|
function codeEnvParams(flags) {
|
|
21
33
|
const params = json(flags["params"]) ?? jsonInput(flags) ?? {};
|
|
22
34
|
if (typeof flags["python-interpreter"] === "string") {
|
|
@@ -46,23 +58,7 @@ function codeEnvPackageList(flags) {
|
|
|
46
58
|
export const AUTH_ACTIONS = {
|
|
47
59
|
login: {
|
|
48
60
|
handler: async (flags) => {
|
|
49
|
-
const tlsSettings =
|
|
50
|
-
const useEnv = dataikuEnvironmentEnabled();
|
|
51
|
-
const url = typeof flags["url"] === "string"
|
|
52
|
-
? flags["url"]
|
|
53
|
-
: useEnv
|
|
54
|
-
? process.env.DATAIKU_URL ?? ""
|
|
55
|
-
: "";
|
|
56
|
-
const apiKey = typeof flags["api-key"] === "string"
|
|
57
|
-
? flags["api-key"]
|
|
58
|
-
: useEnv
|
|
59
|
-
? process.env.DATAIKU_API_KEY ?? ""
|
|
60
|
-
: "";
|
|
61
|
-
const projectKey = typeof flags["project-key"] === "string"
|
|
62
|
-
? flags["project-key"]
|
|
63
|
-
: useEnv
|
|
64
|
-
? process.env.DATAIKU_PROJECT_KEY
|
|
65
|
-
: undefined;
|
|
61
|
+
const { url, apiKey, projectKey, tlsSettings, } = resolveLoginCredentials(flags);
|
|
66
62
|
if (!url || !apiKey) {
|
|
67
63
|
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",], });
|
|
68
64
|
}
|
|
@@ -237,13 +233,13 @@ const VERSION_USAGE = "dss version";
|
|
|
237
233
|
const VERSION_DESCRIPTION = "Print the CLI version, checkout/build revisions, load source, runtime, and stale-build status as JSON.";
|
|
238
234
|
const VERSION_EXAMPLES = ["dss version", "dss --version",];
|
|
239
235
|
const INSTALL_SKILL_USAGE = "dss install-skill [--global] [--agent NAME] [--target PATH] [--list-agents] [--dry-run] [--plan]";
|
|
240
|
-
const INSTALL_SKILL_DESCRIPTION = "
|
|
236
|
+
const INSTALL_SKILL_DESCRIPTION = "Report missing/stale/current skill status and atomically install changed dataiku-dss agent skills.";
|
|
241
237
|
const INSTALL_SKILL_EXAMPLES = [
|
|
242
238
|
"dss install-skill --list-agents",
|
|
243
239
|
"dss install-skill --agent omp --dry-run",
|
|
244
240
|
];
|
|
245
241
|
export const CLEANUP_USAGE = "dss cleanup --file PATH [--dry-run|--apply] [--continue-on-error]";
|
|
246
|
-
const CLEANUP_DESCRIPTION = "Replay cleanup
|
|
242
|
+
const CLEANUP_DESCRIPTION = "Replay cleanup entries in reverse order; failures include structured code/category/exitCode/retryability summaries.";
|
|
247
243
|
const CLEANUP_EXAMPLES = [
|
|
248
244
|
"dss cleanup --file cleanup.jsonl",
|
|
249
245
|
"dss cleanup --file cleanup.jsonl --apply",
|
|
@@ -272,6 +268,12 @@ const ALLOWED_CLEANUP_ACTIONS = new Set([
|
|
|
272
268
|
"folder delete-file",
|
|
273
269
|
"project delete",
|
|
274
270
|
"app delete-instance",
|
|
271
|
+
"meaning delete",
|
|
272
|
+
"notebook delete-jupyter",
|
|
273
|
+
"notebook delete-sql",
|
|
274
|
+
"project-library delete",
|
|
275
|
+
"streaming-endpoint delete",
|
|
276
|
+
"workspace delete",
|
|
275
277
|
]);
|
|
276
278
|
export function isAllowedCleanupAction(resource, action) {
|
|
277
279
|
return ALLOWED_CLEANUP_ACTIONS.has(`${resource} ${action}`);
|
|
@@ -407,7 +409,202 @@ function structuredExamples(examples, examplePayload) {
|
|
|
407
409
|
};
|
|
408
410
|
});
|
|
409
411
|
}
|
|
410
|
-
|
|
412
|
+
const PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA = {
|
|
413
|
+
type: "object",
|
|
414
|
+
additionalProperties: false,
|
|
415
|
+
required: ["name",],
|
|
416
|
+
properties: {
|
|
417
|
+
name: { type: "string", },
|
|
418
|
+
path: { type: "string", },
|
|
419
|
+
size: { type: "integer", minimum: 0, },
|
|
420
|
+
mimeType: { type: "string", },
|
|
421
|
+
hasData: { type: "boolean", },
|
|
422
|
+
lastModified: { type: "number", },
|
|
423
|
+
children: { type: "array", items: { type: "object", additionalProperties: true, }, },
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
const NOTEBOOK_SAVE_OUTPUT_SCHEMA = {
|
|
427
|
+
type: "object",
|
|
428
|
+
additionalProperties: false,
|
|
429
|
+
required: ["saved", "resource", "created", "hash",],
|
|
430
|
+
properties: {
|
|
431
|
+
saved: { type: "string", },
|
|
432
|
+
resource: { enum: ["jupyter-notebook", "sql-notebook",], },
|
|
433
|
+
created: { type: "boolean", },
|
|
434
|
+
hash: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
const COMMAND_OUTPUT_SCHEMAS = {
|
|
438
|
+
"code-env.list": CodeEnvSummaryArraySchema,
|
|
439
|
+
"code-env.get": CodeEnvDetailsSchema,
|
|
440
|
+
"code-env.list-logs": CodeEnvLogSummaryArraySchema,
|
|
441
|
+
"code-env.get-log": {
|
|
442
|
+
oneOf: [
|
|
443
|
+
{
|
|
444
|
+
type: "object",
|
|
445
|
+
required: ["log", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
|
|
446
|
+
properties: {
|
|
447
|
+
log: { type: "string", },
|
|
448
|
+
bytes: { type: "integer", minimum: 0, },
|
|
449
|
+
truncated: { type: "boolean", },
|
|
450
|
+
tailed: { type: "boolean", },
|
|
451
|
+
envLang: { enum: ["PYTHON", "R",], },
|
|
452
|
+
envName: { type: "string", },
|
|
453
|
+
logName: { type: "string", },
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
type: "object",
|
|
458
|
+
required: ["path", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
|
|
459
|
+
properties: {
|
|
460
|
+
path: { type: "string", },
|
|
461
|
+
bytes: { type: "integer", minimum: 0, },
|
|
462
|
+
truncated: { type: "boolean", },
|
|
463
|
+
tailed: { type: "boolean", },
|
|
464
|
+
envLang: { enum: ["PYTHON", "R",], },
|
|
465
|
+
envName: { type: "string", },
|
|
466
|
+
logName: { type: "string", },
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
},
|
|
471
|
+
"code-env.version": CodeEnvVersionForProjectSchema,
|
|
472
|
+
"code-env.usages": CodeEnvUsageArraySchema,
|
|
473
|
+
"code.run": {
|
|
474
|
+
type: "object",
|
|
475
|
+
additionalProperties: false,
|
|
476
|
+
required: [
|
|
477
|
+
"outcome",
|
|
478
|
+
"success",
|
|
479
|
+
"runId",
|
|
480
|
+
"elapsedMs",
|
|
481
|
+
"pollCount",
|
|
482
|
+
"output",
|
|
483
|
+
"logTruncated",
|
|
484
|
+
"maxLogBytes",
|
|
485
|
+
"cleanup",
|
|
486
|
+
],
|
|
487
|
+
properties: {
|
|
488
|
+
outcome: { type: "string", },
|
|
489
|
+
success: { type: "boolean", },
|
|
490
|
+
runId: { type: "string", },
|
|
491
|
+
elapsedMs: { type: "number", minimum: 0, },
|
|
492
|
+
pollCount: { type: "integer", minimum: 0, },
|
|
493
|
+
output: { type: "string", },
|
|
494
|
+
log: { type: "string", },
|
|
495
|
+
logTruncated: { type: "boolean", },
|
|
496
|
+
maxLogBytes: { type: "integer", minimum: 0, },
|
|
497
|
+
timedOut: { const: true, },
|
|
498
|
+
timeoutMs: { type: "integer", minimum: 0, },
|
|
499
|
+
cleanup: {
|
|
500
|
+
type: "object",
|
|
501
|
+
additionalProperties: false,
|
|
502
|
+
required: ["status",],
|
|
503
|
+
properties: {
|
|
504
|
+
status: { enum: ["deleted", "kept", "failed",], },
|
|
505
|
+
error: { type: "string", },
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
"notebook.list-jupyter": JupyterNotebookSummaryArraySchema,
|
|
511
|
+
"notebook.get-jupyter": JupyterNotebookContentSchema,
|
|
512
|
+
"notebook.sessions-jupyter": NotebookSessionArraySchema,
|
|
513
|
+
"notebook.list-sql": SqlNotebookSummaryArraySchema,
|
|
514
|
+
"notebook.get-sql": SqlNotebookContentSchema,
|
|
515
|
+
"notebook.history-sql": SqlNotebookHistorySchema,
|
|
516
|
+
"notebook.save-jupyter": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
|
|
517
|
+
"notebook.save-sql": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
|
|
518
|
+
"project-library.list": { type: "array", items: PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA, },
|
|
519
|
+
"project-library.get-bytes": {
|
|
520
|
+
type: "object",
|
|
521
|
+
additionalProperties: false,
|
|
522
|
+
required: ["path", "bytes", "sha256",],
|
|
523
|
+
properties: {
|
|
524
|
+
path: { type: "string", },
|
|
525
|
+
bytes: { type: "integer", minimum: 0, },
|
|
526
|
+
sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
"project-library.put": {
|
|
530
|
+
type: "object",
|
|
531
|
+
additionalProperties: false,
|
|
532
|
+
required: ["updated", "bytes", "sha256",],
|
|
533
|
+
properties: {
|
|
534
|
+
updated: { type: "string", },
|
|
535
|
+
bytes: { type: "integer", minimum: 0, },
|
|
536
|
+
sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
537
|
+
beforeSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
"project-library.diff": {
|
|
541
|
+
type: "object",
|
|
542
|
+
additionalProperties: false,
|
|
543
|
+
required: [
|
|
544
|
+
"path",
|
|
545
|
+
"unchanged",
|
|
546
|
+
"added",
|
|
547
|
+
"removed",
|
|
548
|
+
"diff",
|
|
549
|
+
"diffTruncated",
|
|
550
|
+
"localSha256",
|
|
551
|
+
"localBytes",
|
|
552
|
+
"maxLines",
|
|
553
|
+
],
|
|
554
|
+
properties: {
|
|
555
|
+
path: { type: "string", },
|
|
556
|
+
unchanged: { type: "boolean", },
|
|
557
|
+
added: { type: "integer", minimum: 0, },
|
|
558
|
+
removed: { type: "integer", minimum: 0, },
|
|
559
|
+
diff: { type: "string", },
|
|
560
|
+
diffTruncated: { type: "boolean", },
|
|
561
|
+
binary: { type: "boolean", },
|
|
562
|
+
remoteAbsent: { type: "boolean", },
|
|
563
|
+
remoteSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
564
|
+
remoteBytes: { type: "integer", minimum: 0, },
|
|
565
|
+
localSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
566
|
+
localBytes: { type: "integer", minimum: 0, },
|
|
567
|
+
maxLines: { type: "integer", minimum: 0, },
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
"project.list": ProjectSummaryArraySchema,
|
|
571
|
+
"project.get": ProjectDetailsSchema,
|
|
572
|
+
"project.metadata": ProjectMetadataSchema,
|
|
573
|
+
"dataset.list": DatasetSummaryArraySchema,
|
|
574
|
+
"dataset.get": DatasetDetailsSchema,
|
|
575
|
+
"dataset.schema": DatasetSchemaSchema,
|
|
576
|
+
"recipe.list": RecipeSummaryArraySchema,
|
|
577
|
+
"job.list": JobSummaryArraySchema,
|
|
578
|
+
"job.wait": JobWaitResultSchema,
|
|
579
|
+
"job.monitor": JobWaitResultSchema,
|
|
580
|
+
"scenario.list": ScenarioSummaryArraySchema,
|
|
581
|
+
"scenario.get": ScenarioDetailsSchema,
|
|
582
|
+
"scenario.status": ScenarioStatusSchema,
|
|
583
|
+
"flow-zone.list": FlowZoneArraySchema,
|
|
584
|
+
"flow-zone.get": FlowZoneSchema,
|
|
585
|
+
"sql.query": {
|
|
586
|
+
anyOf: [
|
|
587
|
+
SqlQueryResponseSchema,
|
|
588
|
+
{
|
|
589
|
+
type: "object",
|
|
590
|
+
required: ["queryId", "rowCount", "preview",],
|
|
591
|
+
additionalProperties: true,
|
|
592
|
+
properties: {
|
|
593
|
+
queryId: { type: "string", },
|
|
594
|
+
rowCount: { type: "number", },
|
|
595
|
+
preview: { type: "array", items: true, },
|
|
596
|
+
truncated: { type: "boolean", },
|
|
597
|
+
outputPath: { type: "string", },
|
|
598
|
+
written: { type: "string", },
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
function outputJsonSchema(resource, action, shape) {
|
|
605
|
+
const precise = COMMAND_OUTPUT_SCHEMAS[registryKey(resource, action)];
|
|
606
|
+
if (precise)
|
|
607
|
+
return precise;
|
|
411
608
|
if (shape === "array")
|
|
412
609
|
return { type: "array", items: true, };
|
|
413
610
|
if (shape === "string")
|
|
@@ -417,6 +614,9 @@ function outputJsonSchema(shape) {
|
|
|
417
614
|
function payloadJsonSchema(payloadSchema) {
|
|
418
615
|
if (!payloadSchema)
|
|
419
616
|
return undefined;
|
|
617
|
+
if (payloadSchema.contentType === "text/plain") {
|
|
618
|
+
return { type: "string", contentMediaType: "text/plain", };
|
|
619
|
+
}
|
|
420
620
|
return payloadSchema.jsonShape === "array"
|
|
421
621
|
? { type: "array", items: true, }
|
|
422
622
|
: { type: "object", additionalProperties: true, };
|
|
@@ -564,10 +764,13 @@ export function buildCommandSchemas(resource, action, flags, requiredFlags, requ
|
|
|
564
764
|
return {
|
|
565
765
|
argv: argvJsonSchema(resource, action, flags, usage, requiredFlags, requiredOneOf),
|
|
566
766
|
...(payloadSchema ? { input: payloadJsonSchema(payloadSchema), } : {}),
|
|
567
|
-
output: outputJsonSchema(outputShape),
|
|
767
|
+
output: outputJsonSchema(resource, action, outputShape),
|
|
568
768
|
};
|
|
569
769
|
}
|
|
570
770
|
const EXPLICIT_REGISTRY_OVERRIDES = {
|
|
771
|
+
"code.run": {
|
|
772
|
+
payloadSchema: { stdin: true, contentType: "text/plain", },
|
|
773
|
+
},
|
|
571
774
|
"dashboard.create": {
|
|
572
775
|
examplePayload: { name: "Agent dashboard", pages: [], },
|
|
573
776
|
},
|
|
@@ -620,8 +823,78 @@ function extractUsageFlags(usage) {
|
|
|
620
823
|
}
|
|
621
824
|
return uniqueStrings(flags).filter((flag) => KNOWN_LONG_FLAGS.has(flag));
|
|
622
825
|
}
|
|
623
|
-
function
|
|
624
|
-
|
|
826
|
+
function extractPositionalArguments(usage) {
|
|
827
|
+
const positionals = [];
|
|
828
|
+
const add = (name, required) => {
|
|
829
|
+
const existing = positionals.find((positional) => positional.name === name);
|
|
830
|
+
if (existing)
|
|
831
|
+
existing.required ||= required;
|
|
832
|
+
else
|
|
833
|
+
positionals.push({ name, required, });
|
|
834
|
+
};
|
|
835
|
+
for (const match of usage.matchAll(/<([^>]+)>/g)) {
|
|
836
|
+
const before = usage.slice(0, match.index);
|
|
837
|
+
const openParen = before.lastIndexOf("(");
|
|
838
|
+
const insideParen = openParen > before.lastIndexOf(")");
|
|
839
|
+
const closeParen = insideParen ? usage.indexOf(")", match.index) : -1;
|
|
840
|
+
const group = closeParen >= 0 ? usage.slice(openParen + 1, closeParen) : "";
|
|
841
|
+
const hasFlagAlternative = group.split("|").some((alternative) => alternative.includes("--"));
|
|
842
|
+
const required = before.lastIndexOf("[") <= before.lastIndexOf("]")
|
|
843
|
+
&& !hasFlagAlternative;
|
|
844
|
+
add(match[1], required);
|
|
845
|
+
}
|
|
846
|
+
for (const suffix of usage.split("[").slice(1)) {
|
|
847
|
+
const candidate = suffix.split("]", 1)[0];
|
|
848
|
+
if (candidate && /^[A-Za-z][A-Za-z0-9_-]*$/.test(candidate))
|
|
849
|
+
add(candidate, false);
|
|
850
|
+
}
|
|
851
|
+
for (const group of topLevelParenGroups(usage)) {
|
|
852
|
+
const candidate = group.split("|", 1)[0]?.trim();
|
|
853
|
+
if (candidate && /^[A-Z][A-Z0-9_-]*$/.test(candidate))
|
|
854
|
+
add(candidate, false);
|
|
855
|
+
}
|
|
856
|
+
return positionals;
|
|
857
|
+
}
|
|
858
|
+
const COMMAND_REQUIRED_INPUT_GROUPS = {
|
|
859
|
+
"notebook.unload-jupyter": [
|
|
860
|
+
{
|
|
861
|
+
oneOf: [
|
|
862
|
+
{ positionals: ["name", "sessionId",], },
|
|
863
|
+
{ flags: ["all",], },
|
|
864
|
+
],
|
|
865
|
+
},
|
|
866
|
+
],
|
|
867
|
+
"sql.query": [
|
|
868
|
+
{
|
|
869
|
+
oneOf: [
|
|
870
|
+
{ positionals: ["SQL",], },
|
|
871
|
+
{ flags: ["sql",], },
|
|
872
|
+
{ flags: ["sql-file",], },
|
|
873
|
+
{ flags: ["stdin",], },
|
|
874
|
+
],
|
|
875
|
+
},
|
|
876
|
+
{ oneOf: [{ flags: ["connection",], }, { flags: ["dataset",], },], },
|
|
877
|
+
],
|
|
878
|
+
"flow-zone.move": [
|
|
879
|
+
{
|
|
880
|
+
oneOf: [
|
|
881
|
+
{ positionals: ["id",], },
|
|
882
|
+
{ flags: ["zone",], },
|
|
883
|
+
{ flags: ["zone-id",], },
|
|
884
|
+
],
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
oneOf: [
|
|
888
|
+
{ flags: ["dataset",], },
|
|
889
|
+
{ flags: ["recipe",], },
|
|
890
|
+
{ flags: ["folder",], },
|
|
891
|
+
{ flags: ["object",], },
|
|
892
|
+
],
|
|
893
|
+
},
|
|
894
|
+
],
|
|
895
|
+
};
|
|
896
|
+
function requiredInputGroups(resource, action) {
|
|
897
|
+
return COMMAND_REQUIRED_INPUT_GROUPS[registryKey(resource, action)] ?? [];
|
|
625
898
|
}
|
|
626
899
|
function inferSideEffect(resource, action) {
|
|
627
900
|
if (resource === "auth")
|
|
@@ -633,6 +906,10 @@ function inferSideEffect(resource, action) {
|
|
|
633
906
|
}
|
|
634
907
|
if (resource === "install-skill")
|
|
635
908
|
return "write";
|
|
909
|
+
if (resource === "sql" && action === "query")
|
|
910
|
+
return "write";
|
|
911
|
+
if (resource === "ml-task" && action === "train")
|
|
912
|
+
return "write";
|
|
636
913
|
// Project and dashboard exports stream a rendered/archive artifact and write it
|
|
637
914
|
// locally; the DSS-side resource is untouched.
|
|
638
915
|
if ((resource === "project" || resource === "dashboard") && action === "export")
|
|
@@ -668,6 +945,8 @@ export function inferRequiresProject(resource, action, usage) {
|
|
|
668
945
|
|| resource === "install-skill" || resource === "version") {
|
|
669
946
|
return false;
|
|
670
947
|
}
|
|
948
|
+
if (resource === "sql" && action === "query")
|
|
949
|
+
return false;
|
|
671
950
|
if (PROJECT_SCOPED_RESOURCES.has(resource))
|
|
672
951
|
return true;
|
|
673
952
|
if (resource === "project-git")
|
|
@@ -828,6 +1107,11 @@ const GLOBAL_FLAG_VALUE_HINTS = {
|
|
|
828
1107
|
"ca-cert": { valueType: "PATH", },
|
|
829
1108
|
"project-key": { valueType: "KEY", },
|
|
830
1109
|
"record-cleanup": { valueType: "PATH", },
|
|
1110
|
+
color: { valueType: "HEX", },
|
|
1111
|
+
data: { valueType: "JSON", },
|
|
1112
|
+
local: { valueType: "JSON", },
|
|
1113
|
+
standard: { valueType: "JSON", },
|
|
1114
|
+
until: { valueType: "STATE", },
|
|
831
1115
|
};
|
|
832
1116
|
/** Derive a value placeholder (and enum members) for each value flag from its usage token. */
|
|
833
1117
|
function extractFlagValueHints(usage) {
|
|
@@ -866,6 +1150,7 @@ function inferExitCodes(resource, action, asyncKind) {
|
|
|
866
1150
|
|| `${resource}.${action}` === "dataset.assert-count"
|
|
867
1151
|
|| `${resource}.${action}` === "dataset.assert-schema"
|
|
868
1152
|
|| `${resource}.${action}` === "data-quality.assert-results"
|
|
1153
|
+
|| `${resource}.${action}` === "flow-zone.organize"
|
|
869
1154
|
|| `${resource}.${action}` === "batch.run";
|
|
870
1155
|
return {
|
|
871
1156
|
ok: 0,
|
|
@@ -877,6 +1162,12 @@ function inferExitCodes(resource, action, asyncKind) {
|
|
|
877
1162
|
};
|
|
878
1163
|
}
|
|
879
1164
|
function cleanupCommandFromDeleteUsage(resource, action) {
|
|
1165
|
+
if (`${resource}.${action}` === "notebook.save-jupyter") {
|
|
1166
|
+
return "dss notebook delete-jupyter <name> --if-exists";
|
|
1167
|
+
}
|
|
1168
|
+
if (`${resource}.${action}` === "notebook.save-sql") {
|
|
1169
|
+
return "dss notebook delete-sql <id> --if-exists";
|
|
1170
|
+
}
|
|
880
1171
|
if (`${resource}.${action}` === "project.import") {
|
|
881
1172
|
return "dss project delete <used-project-key> --if-exists --expect-project-incarnation <hash>";
|
|
882
1173
|
}
|
|
@@ -911,6 +1202,7 @@ export function supportsCleanupLedger(resource, action) {
|
|
|
911
1202
|
*/
|
|
912
1203
|
const EXPLICIT_DESTRUCTIVE_KEYS = {
|
|
913
1204
|
"dataset.upload-file": true,
|
|
1205
|
+
"sql.query": true,
|
|
914
1206
|
};
|
|
915
1207
|
function inferDestructiveLevel(resource, sideEffect, action) {
|
|
916
1208
|
if (sideEffect !== "write")
|
|
@@ -930,6 +1222,8 @@ function inferAsyncKind(resource, action) {
|
|
|
930
1222
|
}
|
|
931
1223
|
if (resource === "recipe" && action === "run")
|
|
932
1224
|
return "job";
|
|
1225
|
+
if (resource === "ml-task" && action === "train")
|
|
1226
|
+
return "job";
|
|
933
1227
|
if (resource === "future" && ["get", "peek", "wait", "abort",].includes(action))
|
|
934
1228
|
return "future";
|
|
935
1229
|
if (resource === "scenario" && ["run", "run-and-wait", "status",].includes(action)) {
|
|
@@ -943,6 +1237,11 @@ function inferAsyncKind(resource, action) {
|
|
|
943
1237
|
return "future";
|
|
944
1238
|
if (resource === "code" && action === "run")
|
|
945
1239
|
return "future";
|
|
1240
|
+
if (resource === "webapp" && action === "restart-backend")
|
|
1241
|
+
return "future";
|
|
1242
|
+
if (resource === "code-env"
|
|
1243
|
+
&& ["create", "update-packages", "update-images", "set-jupyter", "delete",].includes(action))
|
|
1244
|
+
return "future";
|
|
946
1245
|
if (resource === "app"
|
|
947
1246
|
&& ["create-instance", "create-successor-instance", "delete-instance",].includes(action)) {
|
|
948
1247
|
return "future";
|
|
@@ -952,6 +1251,8 @@ function inferAsyncKind(resource, action) {
|
|
|
952
1251
|
function inferIdempotency(resource, sideEffect, action, usage) {
|
|
953
1252
|
if (sideEffect === "read")
|
|
954
1253
|
return "safe";
|
|
1254
|
+
if (`${resource}.${action}` === "install-skill.run")
|
|
1255
|
+
return "convergent";
|
|
955
1256
|
if (action.startsWith("create") && usage.includes("--if-not-exists"))
|
|
956
1257
|
return "if-not-exists";
|
|
957
1258
|
if (action.startsWith("delete") && usage.includes("--if-exists"))
|
|
@@ -990,18 +1291,26 @@ export function inferCleanupHint(resource, action) {
|
|
|
990
1291
|
// tag create has a generic `delete` command to close it out.
|
|
991
1292
|
if (resource === "project-git")
|
|
992
1293
|
return undefined;
|
|
1294
|
+
const key = `${resource}.${action}`;
|
|
1295
|
+
if (key === "notebook.save-jupyter") {
|
|
1296
|
+
return "When the save reports created:true, delete with `dss notebook delete-jupyter <name> --if-exists`.";
|
|
1297
|
+
}
|
|
1298
|
+
if (key === "notebook.save-sql") {
|
|
1299
|
+
return "When the save reports created:true, delete with `dss notebook delete-sql <id> --if-exists`.";
|
|
1300
|
+
}
|
|
993
1301
|
if (!(action.startsWith("create") || action === "clone"))
|
|
994
1302
|
return undefined;
|
|
995
|
-
if (LEDGER_ONLY_CLEANUP_KEYS[
|
|
1303
|
+
if (LEDGER_ONLY_CLEANUP_KEYS[key] === true)
|
|
996
1304
|
return LEDGER_ONLY_CLEANUP_HINT;
|
|
997
|
-
}
|
|
998
1305
|
const deleteAction = action === "create-rule"
|
|
999
1306
|
? "delete-rule"
|
|
1000
1307
|
: action === "create-instance" || action === "create-successor-instance"
|
|
1001
1308
|
? "delete-instance"
|
|
1002
1309
|
: "delete";
|
|
1003
1310
|
const deleteUsage = commands[resource]?.[deleteAction]?.usage;
|
|
1004
|
-
|
|
1311
|
+
if (!deleteUsage)
|
|
1312
|
+
return undefined;
|
|
1313
|
+
const ifExists = deleteUsage.includes("--if-exists") ? " --if-exists" : "";
|
|
1005
1314
|
if (resource === "code-env") {
|
|
1006
1315
|
return `Delete with \`dss code-env delete <lang> <name>${ifExists}\`.`;
|
|
1007
1316
|
}
|
|
@@ -1074,7 +1383,9 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
1074
1383
|
...allowEmptyPart,
|
|
1075
1384
|
};
|
|
1076
1385
|
});
|
|
1077
|
-
const
|
|
1386
|
+
const positionalArguments = extractPositionalArguments(meta.usage);
|
|
1387
|
+
const positionals = positionalArguments.map((positional) => positional.name);
|
|
1388
|
+
const inputGroups = requiredInputGroups(resource, action);
|
|
1078
1389
|
const outputShape = inferOutputShape(resource, action);
|
|
1079
1390
|
const producesLocalFile = meta.usage.includes("--output PATH")
|
|
1080
1391
|
|| meta.usage.includes("--output-file PATH");
|
|
@@ -1090,6 +1401,7 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
1090
1401
|
structuredExamples: structuredExamples(meta.examples, examplePayload),
|
|
1091
1402
|
flags: flagMetadata,
|
|
1092
1403
|
positionals,
|
|
1404
|
+
positionalArguments,
|
|
1093
1405
|
sideEffect,
|
|
1094
1406
|
requiresAuth,
|
|
1095
1407
|
requiresProject,
|
|
@@ -1104,6 +1416,7 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
1104
1416
|
requiredFlags: uniqueRequiredFlags,
|
|
1105
1417
|
optionalFlags: uniqueOptionalFlags,
|
|
1106
1418
|
...(requiredOneOf.length > 0 ? { requiredOneOf, } : {}),
|
|
1419
|
+
...(inputGroups.length > 0 ? { requiredInputGroups: inputGroups, } : {}),
|
|
1107
1420
|
...(payloadSchema ? { payloadSchema, } : {}),
|
|
1108
1421
|
schemas: buildCommandSchemas(resource, action, flagMetadata, uniqueRequiredFlags, requiredOneOf, payloadSchema, outputShape, meta.usage),
|
|
1109
1422
|
...(unsafe ? { unsafeOutputs: unsafe, } : {}),
|
|
@@ -1216,6 +1529,7 @@ function commandRegistryEntryJsonSchema() {
|
|
|
1216
1529
|
"usage",
|
|
1217
1530
|
"flags",
|
|
1218
1531
|
"positionals",
|
|
1532
|
+
"positionalArguments",
|
|
1219
1533
|
"sideEffect",
|
|
1220
1534
|
"requiresAuth",
|
|
1221
1535
|
"requiresProject",
|
|
@@ -1235,6 +1549,15 @@ function commandRegistryEntryJsonSchema() {
|
|
|
1235
1549
|
structuredExamples: { type: "array", items: { type: "object", additionalProperties: true, }, },
|
|
1236
1550
|
flags: { type: "array", items: commandFlagJsonSchema(), },
|
|
1237
1551
|
positionals: { type: "array", items: { type: "string", }, },
|
|
1552
|
+
positionalArguments: {
|
|
1553
|
+
type: "array",
|
|
1554
|
+
items: {
|
|
1555
|
+
type: "object",
|
|
1556
|
+
required: ["name", "required",],
|
|
1557
|
+
additionalProperties: false,
|
|
1558
|
+
properties: { name: { type: "string", }, required: { type: "boolean", }, },
|
|
1559
|
+
},
|
|
1560
|
+
},
|
|
1238
1561
|
sideEffect: { enum: ["read", "write", "auth",], },
|
|
1239
1562
|
requiresAuth: { type: "boolean", },
|
|
1240
1563
|
requiresProject: { type: "boolean", },
|
|
@@ -1247,6 +1570,7 @@ function commandRegistryEntryJsonSchema() {
|
|
|
1247
1570
|
idempotency: { enum: ["safe", "convergent", "if-not-exists", "if-exists", "none",], },
|
|
1248
1571
|
dryRun: { type: "boolean", },
|
|
1249
1572
|
requiredFlags: { type: "array", items: { type: "string", }, },
|
|
1573
|
+
requiredInputGroups: { type: "array", items: { type: "object", additionalProperties: true, }, },
|
|
1250
1574
|
optionalFlags: { type: "array", items: { type: "string", }, },
|
|
1251
1575
|
schemas: { type: "object", additionalProperties: true, },
|
|
1252
1576
|
unsafeOutputs: { type: "array", items: { type: "object", additionalProperties: true, }, },
|
|
@@ -1275,8 +1599,13 @@ function errorEnvelopeJsonSchema() {
|
|
|
1275
1599
|
ok: { const: false, },
|
|
1276
1600
|
error: { type: "string", },
|
|
1277
1601
|
code: { type: "string", },
|
|
1278
|
-
category: { enum: ["usage", "dss", "internal",], },
|
|
1602
|
+
category: { enum: ["usage", "permission_or_environment", "dss", "internal",], },
|
|
1279
1603
|
exitCode: { type: "number", },
|
|
1604
|
+
hint: { type: "string", },
|
|
1605
|
+
status: { type: "number", },
|
|
1606
|
+
retryable: { type: "boolean", },
|
|
1607
|
+
requestId: { type: "string", },
|
|
1608
|
+
details: { type: "object", additionalProperties: true, },
|
|
1280
1609
|
resource: { type: "string", },
|
|
1281
1610
|
action: { type: "string", },
|
|
1282
1611
|
projectKey: { type: "string", },
|
|
@@ -1367,6 +1696,8 @@ export function buildAgentContract() {
|
|
|
1367
1696
|
format: "compact-json",
|
|
1368
1697
|
success: "single-json-value",
|
|
1369
1698
|
failure: "structured-error-object",
|
|
1699
|
+
failureResultDetailLimitBytes: 65_536,
|
|
1700
|
+
fieldProjection: "Missing --fields paths stay null on stdout and emit field_projection_missing on stderr.",
|
|
1370
1701
|
richFailureResults: "doctor/batch/cleanup nonzero outcomes are their own compact result on stdout ({ok:false,...}) with the command's exit code; not re-wrapped.",
|
|
1371
1702
|
},
|
|
1372
1703
|
stderr: {
|
|
@@ -1454,7 +1785,9 @@ function requiredPlanJsonInput(flags, usage) {
|
|
|
1454
1785
|
return requiredJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${usage}`);
|
|
1455
1786
|
}
|
|
1456
1787
|
function requiredPlanPositionals(usage) {
|
|
1457
|
-
return
|
|
1788
|
+
return extractPositionalArguments(usage)
|
|
1789
|
+
.filter((positional) => positional.required)
|
|
1790
|
+
.map((positional) => positional.name);
|
|
1458
1791
|
}
|
|
1459
1792
|
function dataQualityEndpoint(projectKey, datasetName, suffix) {
|
|
1460
1793
|
return encodedProjectEndpointForPlan(projectKey, `/datasets/${encodeURIComponent(datasetName)}/data-quality${suffix}`);
|
|
@@ -1482,11 +1815,6 @@ function projectGitEndpoint(projectKey, suffix) {
|
|
|
1482
1815
|
function projectGitFutureEndpoint(jobId) {
|
|
1483
1816
|
return `${PROJECT_GIT_API_ROOT}/futures/${encodeURIComponent(jobId)}`;
|
|
1484
1817
|
}
|
|
1485
|
-
/** Encode a library reference path per segment, mirroring the resource helper. */
|
|
1486
|
-
function encodeGitReferencePath(pathValue) {
|
|
1487
|
-
const normalized = pathValue.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1488
|
-
return normalized.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
1489
|
-
}
|
|
1490
1818
|
/**
|
|
1491
1819
|
* `--plan` bypasses the SDK boundary guard, so HTTP(S) URLs with embedded
|
|
1492
1820
|
* userinfo are rejected here before any plan is printed. SSH/scp-style URLs
|
|
@@ -1566,10 +1894,33 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
1566
1894
|
return encodedProjectEndpointForPlan(projectKey, suffix);
|
|
1567
1895
|
};
|
|
1568
1896
|
const id = args[0];
|
|
1569
|
-
const codeEnvEndpoint = (suffix = "") => `/public/api/admin/code-envs/${encodeURIComponent(args[0])}/${encodeURIComponent(args[1])}${suffix}`;
|
|
1897
|
+
const codeEnvEndpoint = (suffix = "") => `/public/api/admin/code-envs/${encodeURIComponent(codeEnvLang(args[0], entry.usage))}/${encodeURIComponent(args[1])}${suffix}`;
|
|
1570
1898
|
const statisticsWorksheetsEndpoint = (datasetName) => projectEndpoint(`/datasets/${encodeURIComponent(datasetName)}/statistics/worksheets/`);
|
|
1571
1899
|
const statisticsWorksheetEndpoint = (datasetName, worksheetId) => `${statisticsWorksheetsEndpoint(datasetName)}${encodeURIComponent(worksheetId)}`;
|
|
1572
1900
|
switch (`${resource}.${action}`) {
|
|
1901
|
+
case "sql.query": {
|
|
1902
|
+
const payload = resolveSqlQueryInvocation(args, flags, planProjectKeyFromArgs(flags));
|
|
1903
|
+
return {
|
|
1904
|
+
method: "POST",
|
|
1905
|
+
endpoint: "/public/api/sql/queries/",
|
|
1906
|
+
identifiers: {
|
|
1907
|
+
connection: payload.connection,
|
|
1908
|
+
dataset: payload.datasetFullName,
|
|
1909
|
+
},
|
|
1910
|
+
payload,
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
case "ml-task.train":
|
|
1914
|
+
return {
|
|
1915
|
+
method: "POST",
|
|
1916
|
+
endpoint: projectEndpoint(`/models/lab/${encodeURIComponent(args[0])}/${encodeURIComponent(args[1])}/train`),
|
|
1917
|
+
identifiers: { analysisId: args[0], mlTaskId: args[1], },
|
|
1918
|
+
payload: {
|
|
1919
|
+
sessionName: flags["session-name"],
|
|
1920
|
+
runQueue: false,
|
|
1921
|
+
},
|
|
1922
|
+
wait: flags["wait"] === true,
|
|
1923
|
+
};
|
|
1573
1924
|
case "wiki.create": {
|
|
1574
1925
|
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
1575
1926
|
return {
|
|
@@ -1939,17 +2290,72 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
1939
2290
|
}
|
|
1940
2291
|
case "recipe.run":
|
|
1941
2292
|
return {
|
|
2293
|
+
exact: false,
|
|
2294
|
+
reason: "The job endpoint is exact, but DSS reads the recipe and its outputs before constructing the job payload; use recipe run --dry-run for a resolved payload.",
|
|
1942
2295
|
method: "POST",
|
|
1943
2296
|
endpoint: projectEndpoint("/jobs/"),
|
|
1944
2297
|
identifiers: { recipe: id, },
|
|
1945
|
-
payload: {
|
|
1946
|
-
recipe: id,
|
|
1947
|
-
outputResolution: "dynamic",
|
|
1948
|
-
projectKey,
|
|
1949
|
-
partition: flags["partition"],
|
|
1950
|
-
},
|
|
1951
2298
|
wait: recipeRunShouldWait(flags),
|
|
1952
2299
|
};
|
|
2300
|
+
case "code.run": {
|
|
2301
|
+
const { script, source, } = resolveCodeInputWithSource(args, flags);
|
|
2302
|
+
const sourceSha256 = sha256Hex(script);
|
|
2303
|
+
const scenarioBase = projectEndpoint("/scenarios/{generatedScenarioId}");
|
|
2304
|
+
const envName = flags["env"];
|
|
2305
|
+
const keepScenario = flags["keep"] === true;
|
|
2306
|
+
const timeoutMs = parseCodeRunIntegerFlag(flags["timeout"], "--timeout") ?? 120_000;
|
|
2307
|
+
const maxLogBytes = flags["full-log"] === true
|
|
2308
|
+
? 0
|
|
2309
|
+
: parseCodeRunIntegerFlag(flags["max-log-bytes"], "--max-log-bytes") ?? 1_048_576;
|
|
2310
|
+
return {
|
|
2311
|
+
method: "POST",
|
|
2312
|
+
endpoint: projectEndpoint("/scenarios/"),
|
|
2313
|
+
identifiers: {
|
|
2314
|
+
source,
|
|
2315
|
+
sourceSha256,
|
|
2316
|
+
sourceBytes: Buffer.byteLength(script),
|
|
2317
|
+
},
|
|
2318
|
+
requests: [
|
|
2319
|
+
{
|
|
2320
|
+
method: "POST",
|
|
2321
|
+
endpoint: projectEndpoint("/scenarios/"),
|
|
2322
|
+
payload: {
|
|
2323
|
+
id: "{generatedScenarioId}",
|
|
2324
|
+
name: "dss code run ({generatedScenarioId})",
|
|
2325
|
+
projectKey,
|
|
2326
|
+
type: "custom_python",
|
|
2327
|
+
params: {
|
|
2328
|
+
envSelection: envName
|
|
2329
|
+
? { envMode: "EXPLICIT_ENV", envName, }
|
|
2330
|
+
: { envMode: "INHERIT", },
|
|
2331
|
+
},
|
|
2332
|
+
},
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
method: "PUT",
|
|
2336
|
+
endpoint: `${scenarioBase}/payload`,
|
|
2337
|
+
payload: { extension: "py", script: "<omitted>", },
|
|
2338
|
+
redactedFields: ["payload.script",],
|
|
2339
|
+
},
|
|
2340
|
+
{ method: "POST", endpoint: `${scenarioBase}/run/`, payload: {}, },
|
|
2341
|
+
{
|
|
2342
|
+
method: "GET",
|
|
2343
|
+
endpoint: `${scenarioBase}/get-run-for-trigger?triggerId={triggerId}&triggerRunId={triggerRunId}`,
|
|
2344
|
+
repeat: "until scenarioRun.result.outcome or timeout",
|
|
2345
|
+
},
|
|
2346
|
+
{
|
|
2347
|
+
method: "GET",
|
|
2348
|
+
endpoint: `${scenarioBase}/{runId}/log`,
|
|
2349
|
+
boundedBytes: maxLogBytes,
|
|
2350
|
+
},
|
|
2351
|
+
...keepScenario ? [] : [{ method: "DELETE", endpoint: scenarioBase, cleanup: true, },],
|
|
2352
|
+
],
|
|
2353
|
+
wait: {
|
|
2354
|
+
timeoutMs,
|
|
2355
|
+
pollEndpoint: `${scenarioBase}/get-run-for-trigger`,
|
|
2356
|
+
},
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
1953
2359
|
case "recipe.update":
|
|
1954
2360
|
return {
|
|
1955
2361
|
method: "PUT",
|
|
@@ -2710,27 +3116,187 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2710
3116
|
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
2711
3117
|
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2712
3118
|
};
|
|
2713
|
-
case "
|
|
3119
|
+
case "webapp.create":
|
|
3120
|
+
return {
|
|
3121
|
+
method: "POST",
|
|
3122
|
+
endpoint: projectEndpoint("/webapps/"),
|
|
3123
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3124
|
+
};
|
|
3125
|
+
case "webapp.update-settings": {
|
|
3126
|
+
const patch = requiredPlanJsonInput(flags, entry.usage);
|
|
3127
|
+
const expectHash = flags["expect-hash"];
|
|
3128
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3129
|
+
throw new UsageError("Expected webapp hash must be a 64-character SHA-256 hex digest.");
|
|
3130
|
+
}
|
|
3131
|
+
const endpoint = projectEndpoint(`/webapps/${encodeURIComponent(id)}`);
|
|
3132
|
+
return {
|
|
3133
|
+
exact: false,
|
|
3134
|
+
reason: "Apply GETs the current settings, deep-merges the patch, then PUTs the full object.",
|
|
3135
|
+
method: "PUT",
|
|
3136
|
+
endpoint,
|
|
3137
|
+
identifiers: {
|
|
3138
|
+
webappId: id,
|
|
3139
|
+
patchHash: stableHash(patch),
|
|
3140
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3141
|
+
},
|
|
3142
|
+
payload: { patch: "<omitted>", patchHash: stableHash(patch), },
|
|
3143
|
+
requests: [
|
|
3144
|
+
{ method: "GET", endpoint, purpose: "read full settings and verify expectHash", },
|
|
3145
|
+
{
|
|
3146
|
+
method: "PUT",
|
|
3147
|
+
endpoint,
|
|
3148
|
+
condition: "settings read and expected hash matched",
|
|
3149
|
+
payload: "<merged full settings>",
|
|
3150
|
+
},
|
|
3151
|
+
],
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
case "webapp.stop-backend":
|
|
3155
|
+
return {
|
|
3156
|
+
method: "PUT",
|
|
3157
|
+
endpoint: projectEndpoint(`/webapps/${encodeURIComponent(id)}/backend/actions/stop`),
|
|
3158
|
+
identifiers: { webappId: id, },
|
|
3159
|
+
payload: {},
|
|
3160
|
+
};
|
|
3161
|
+
case "webapp.restart-backend": {
|
|
3162
|
+
const endpoint = projectEndpoint(`/webapps/${encodeURIComponent(id)}/backend/actions/restart`);
|
|
2714
3163
|
return {
|
|
2715
3164
|
method: "PUT",
|
|
2716
|
-
endpoint
|
|
3165
|
+
endpoint,
|
|
3166
|
+
identifiers: { webappId: id, },
|
|
3167
|
+
payload: {},
|
|
3168
|
+
wait: {
|
|
3169
|
+
requested: flags["wait"] === true,
|
|
3170
|
+
timeoutMs: num(flags["timeout"], "--timeout"),
|
|
3171
|
+
pollIntervalMs: num(flags["poll-interval"], "--poll-interval"),
|
|
3172
|
+
},
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
case "api-service.create":
|
|
3176
|
+
return {
|
|
3177
|
+
method: "POST",
|
|
3178
|
+
endpoint: projectEndpoint(`/apiservices/${encodeURIComponent(id)}`),
|
|
3179
|
+
identifiers: { serviceId: id, },
|
|
3180
|
+
payload: {},
|
|
3181
|
+
};
|
|
3182
|
+
case "api-service.save-settings": {
|
|
3183
|
+
const settings = requiredPlanJsonInput(flags, entry.usage);
|
|
3184
|
+
const expectHash = flags["expect-hash"];
|
|
3185
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3186
|
+
throw new UsageError("Expected API service settings hash must be a 64-character SHA-256 hex digest.");
|
|
3187
|
+
}
|
|
3188
|
+
const endpoint = projectEndpoint(`/apiservices/${encodeURIComponent(id)}/settings`);
|
|
3189
|
+
const payload = {
|
|
3190
|
+
settings: "<omitted>",
|
|
3191
|
+
settingsHash: stableHash(settings),
|
|
3192
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3193
|
+
};
|
|
3194
|
+
return {
|
|
3195
|
+
method: "PUT",
|
|
3196
|
+
endpoint,
|
|
3197
|
+
identifiers: { serviceId: id, },
|
|
3198
|
+
payload,
|
|
3199
|
+
requests: [
|
|
3200
|
+
...(expectHash
|
|
3201
|
+
? [{ method: "GET", endpoint, purpose: "verify expectHash", },]
|
|
3202
|
+
: []),
|
|
3203
|
+
{
|
|
3204
|
+
method: "PUT",
|
|
3205
|
+
endpoint,
|
|
3206
|
+
condition: expectHash ? "hash matched" : "unconditional",
|
|
3207
|
+
payload,
|
|
3208
|
+
},
|
|
3209
|
+
],
|
|
3210
|
+
};
|
|
3211
|
+
}
|
|
3212
|
+
case "api-service.add-prediction-endpoint": {
|
|
3213
|
+
const endpoint = projectEndpoint(`/apiservices/${encodeURIComponent(id)}/settings`);
|
|
3214
|
+
return {
|
|
3215
|
+
exact: false,
|
|
3216
|
+
reason: "Apply GETs the full settings, appends one endpoint, then PUTs the full object.",
|
|
3217
|
+
method: "PUT",
|
|
3218
|
+
endpoint,
|
|
3219
|
+
identifiers: { serviceId: id, endpointId: args[1], savedModelId: args[2], },
|
|
3220
|
+
payload: { id: args[1], type: "STD_PREDICTION", modelRef: args[2], },
|
|
3221
|
+
requests: [
|
|
3222
|
+
{ method: "GET", endpoint, purpose: "read full settings", },
|
|
3223
|
+
{ method: "PUT", endpoint, payload: "<merged full settings>", },
|
|
3224
|
+
],
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
case "api-service.create-package":
|
|
3228
|
+
case "api-service.delete-package":
|
|
3229
|
+
case "api-service.publish-package": {
|
|
3230
|
+
const serviceId = args[0];
|
|
3231
|
+
const packageId = args[1];
|
|
3232
|
+
const packageEndpoint = projectEndpoint(`/apiservices/${encodeURIComponent(serviceId)}/packages/${encodeURIComponent(packageId)}`);
|
|
3233
|
+
if (action === "delete-package") {
|
|
3234
|
+
return {
|
|
3235
|
+
method: "DELETE",
|
|
3236
|
+
endpoint: packageEndpoint,
|
|
3237
|
+
identifiers: { serviceId, packageId, },
|
|
3238
|
+
};
|
|
3239
|
+
}
|
|
3240
|
+
if (action === "publish-package") {
|
|
3241
|
+
return {
|
|
3242
|
+
method: "POST",
|
|
3243
|
+
endpoint: `${packageEndpoint}/publish${querySuffix({
|
|
3244
|
+
publishedServiceId: flags["published-service-id"],
|
|
3245
|
+
})}`,
|
|
3246
|
+
identifiers: { serviceId, packageId, },
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
return {
|
|
3250
|
+
method: "POST",
|
|
3251
|
+
endpoint: packageEndpoint + querySuffix({
|
|
3252
|
+
releaseNotes: flags["release-notes"],
|
|
3253
|
+
}),
|
|
3254
|
+
identifiers: { serviceId, packageId, },
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
case "bundle.export": {
|
|
3258
|
+
const evaluateProjectStandardsChecks = parseBooleanOption(flags["evaluate-standards-checks"], "--evaluate-standards-checks") ?? true;
|
|
3259
|
+
return {
|
|
3260
|
+
method: "PUT",
|
|
3261
|
+
endpoint: projectEndpoint("/bundles/exported/" + encodeURIComponent(id)) + querySuffix({
|
|
3262
|
+
releaseNotes: flags["release-notes"],
|
|
3263
|
+
evaluateProjectStandardsChecks,
|
|
3264
|
+
}),
|
|
2717
3265
|
identifiers: { bundleId: id, },
|
|
2718
3266
|
payload: {},
|
|
2719
3267
|
};
|
|
3268
|
+
}
|
|
2720
3269
|
case "bundle.publish":
|
|
2721
3270
|
return {
|
|
2722
3271
|
method: "POST",
|
|
2723
|
-
endpoint: projectEndpoint(
|
|
3272
|
+
endpoint: projectEndpoint("/bundles/" + encodeURIComponent(id) + "/publish") + querySuffix({
|
|
3273
|
+
publishedProjectKey: flags["published-project-key"],
|
|
3274
|
+
}),
|
|
2724
3275
|
identifiers: { bundleId: id, },
|
|
2725
3276
|
payload: {},
|
|
2726
3277
|
};
|
|
2727
|
-
case "bundle.activate":
|
|
3278
|
+
case "bundle.activate": {
|
|
3279
|
+
const rawScenarios = flags["scenarios"];
|
|
3280
|
+
let scenariosToEnable;
|
|
3281
|
+
if (rawScenarios !== undefined && rawScenarios !== false) {
|
|
3282
|
+
const parsed = json(rawScenarios);
|
|
3283
|
+
if (typeof parsed !== "object"
|
|
3284
|
+
|| parsed === null
|
|
3285
|
+
|| Array.isArray(parsed)
|
|
3286
|
+
|| !Object.values(parsed).every((value) => typeof value === "boolean")) {
|
|
3287
|
+
throw new UsageError("--scenarios must be a JSON object mapping scenario IDs to true|false.");
|
|
3288
|
+
}
|
|
3289
|
+
scenariosToEnable = parsed;
|
|
3290
|
+
}
|
|
2728
3291
|
return {
|
|
2729
3292
|
method: "POST",
|
|
2730
3293
|
endpoint: projectEndpoint(`/bundles/imported/${encodeURIComponent(id)}/actions/activate`),
|
|
2731
3294
|
identifiers: { bundleId: id, },
|
|
2732
|
-
payload:
|
|
3295
|
+
payload: scenariosToEnable === undefined
|
|
3296
|
+
? {}
|
|
3297
|
+
: { scenariosActiveOnActivation: scenariosToEnable, },
|
|
2733
3298
|
};
|
|
3299
|
+
}
|
|
2734
3300
|
case "bundle.preload":
|
|
2735
3301
|
return {
|
|
2736
3302
|
method: "POST",
|
|
@@ -2738,40 +3304,151 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2738
3304
|
identifiers: { bundleId: id, },
|
|
2739
3305
|
payload: {},
|
|
2740
3306
|
};
|
|
2741
|
-
case "
|
|
3307
|
+
case "project-library.create-file":
|
|
3308
|
+
case "project-library.create-folder": {
|
|
3309
|
+
const libraryPath = validateLibraryPath(id);
|
|
3310
|
+
const kind = action === "create-file" ? "contents" : "folders";
|
|
2742
3311
|
return {
|
|
3312
|
+
exact: false,
|
|
3313
|
+
reason: "Apply performs a live absence check before POST; --if-not-exists may stop after that read, and create never overwrites an existing item.",
|
|
2743
3314
|
method: "POST",
|
|
2744
|
-
endpoint:
|
|
2745
|
-
identifiers: {
|
|
3315
|
+
endpoint: projectEndpoint(`/libraries/${kind}/${encodeLibraryPath(libraryPath)}`),
|
|
3316
|
+
identifiers: { path: libraryPath, },
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
case "project-library.put": {
|
|
3320
|
+
const libraryPath = validateLibraryPath(id);
|
|
3321
|
+
const endpoint = projectEndpoint(`/libraries/contents/${encodeLibraryPath(libraryPath)}`);
|
|
3322
|
+
const payload = projectLibraryPutPayload(flags);
|
|
3323
|
+
return {
|
|
3324
|
+
method: "POST",
|
|
3325
|
+
endpoint,
|
|
3326
|
+
identifiers: {
|
|
3327
|
+
path: libraryPath,
|
|
3328
|
+
concurrencyControl: payload.expectSha256
|
|
3329
|
+
? PROJECT_LIBRARY_CONCURRENCY_CONTROL
|
|
3330
|
+
: "none",
|
|
3331
|
+
},
|
|
3332
|
+
payload,
|
|
3333
|
+
...(payload.expectSha256
|
|
3334
|
+
? {
|
|
3335
|
+
requests: [
|
|
3336
|
+
{
|
|
3337
|
+
method: "GET",
|
|
3338
|
+
endpoint: `${endpoint}?dataEncoding=base64`,
|
|
3339
|
+
purpose: "verify expectSha256",
|
|
3340
|
+
},
|
|
3341
|
+
{ method: "POST", endpoint, condition: "hash matched", payload, },
|
|
3342
|
+
],
|
|
3343
|
+
}
|
|
3344
|
+
: {}),
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
case "project-library.delete": {
|
|
3348
|
+
const libraryPath = validateLibraryPath(id);
|
|
3349
|
+
return {
|
|
3350
|
+
method: "DELETE",
|
|
3351
|
+
endpoint: projectEndpoint(`/libraries/contents/${encodeLibraryPath(libraryPath)}`),
|
|
3352
|
+
identifiers: { path: libraryPath, },
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
case "project-library.rename": {
|
|
3356
|
+
const libraryPath = validateLibraryPath(id);
|
|
3357
|
+
const newName = validateLibraryName(args[1]);
|
|
3358
|
+
return {
|
|
3359
|
+
method: "POST",
|
|
3360
|
+
endpoint: projectEndpoint("/libraries/contents-actions/rename/"),
|
|
3361
|
+
identifiers: { path: libraryPath, newName, },
|
|
3362
|
+
payload: { oldPath: `/${libraryPath}`, newName, },
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
case "project-library.move": {
|
|
3366
|
+
const libraryPath = validateLibraryPath(id);
|
|
3367
|
+
const destinationFolder = validateLibraryDestinationPath(args[1]);
|
|
3368
|
+
return {
|
|
3369
|
+
method: "POST",
|
|
3370
|
+
endpoint: projectEndpoint("/libraries/contents-actions/move"),
|
|
3371
|
+
identifiers: { path: libraryPath, destinationFolder, },
|
|
3372
|
+
payload: { oldPath: `/${libraryPath}`, newPath: destinationFolder, },
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
3375
|
+
case "code-env.create": {
|
|
3376
|
+
const wait = codeEnvWait(flags);
|
|
3377
|
+
return {
|
|
3378
|
+
method: "POST",
|
|
3379
|
+
endpoint: `${codeEnvEndpoint()}?wait=${wait}`,
|
|
3380
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
2746
3381
|
payload: {
|
|
2747
|
-
|
|
2748
|
-
envName: args[1],
|
|
3382
|
+
...codeEnvParams(flags),
|
|
2749
3383
|
deploymentMode: requiredPlanFlag(flags, "deployment-mode", entry.usage),
|
|
2750
|
-
params: codeEnvParams(flags),
|
|
2751
|
-
wait: codeEnvWait(flags),
|
|
2752
3384
|
},
|
|
2753
|
-
wait
|
|
3385
|
+
wait,
|
|
2754
3386
|
};
|
|
2755
|
-
|
|
3387
|
+
}
|
|
3388
|
+
case "code-env.set-definition": {
|
|
3389
|
+
const definition = requiredPlanJsonInput(flags, entry.usage);
|
|
3390
|
+
const expectHash = flags["expect-hash"];
|
|
3391
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3392
|
+
throw new UsageError("--expect-hash must be a 64-character SHA-256 hex digest.");
|
|
3393
|
+
}
|
|
3394
|
+
const endpoint = codeEnvEndpoint();
|
|
3395
|
+
const definitionHash = stableHash(definition);
|
|
3396
|
+
const normalizedExpectHash = expectHash?.toLowerCase();
|
|
2756
3397
|
return {
|
|
3398
|
+
exact: false,
|
|
3399
|
+
reason: "Apply PUTs the supplied definition object unchanged; this plan omits that body and reports only its hash.",
|
|
2757
3400
|
method: "PUT",
|
|
2758
|
-
endpoint
|
|
2759
|
-
identifiers: {
|
|
2760
|
-
|
|
3401
|
+
endpoint,
|
|
3402
|
+
identifiers: {
|
|
3403
|
+
lang: codeEnvLang(args[0], entry.usage),
|
|
3404
|
+
name: args[1],
|
|
3405
|
+
definitionHash,
|
|
3406
|
+
...(normalizedExpectHash ? { expectHash: normalizedExpectHash, } : {}),
|
|
3407
|
+
},
|
|
3408
|
+
payload: "<omitted>",
|
|
3409
|
+
...(normalizedExpectHash
|
|
3410
|
+
? {
|
|
3411
|
+
requests: [
|
|
3412
|
+
{ method: "GET", endpoint, purpose: "verify expectHash", },
|
|
3413
|
+
{
|
|
3414
|
+
method: "PUT",
|
|
3415
|
+
endpoint,
|
|
3416
|
+
condition: "hash matched",
|
|
3417
|
+
payload: "<omitted>",
|
|
3418
|
+
redactedFields: ["payload",],
|
|
3419
|
+
},
|
|
3420
|
+
],
|
|
3421
|
+
}
|
|
3422
|
+
: {}),
|
|
2761
3423
|
};
|
|
3424
|
+
}
|
|
2762
3425
|
case "code-env.set-packages": {
|
|
2763
3426
|
const installCorePackages = parseBooleanOption(flags["install-core-packages"], "--install-core-packages");
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
if (installCorePackages !== undefined) {
|
|
2768
|
-
payload.desc = { installCorePackages, };
|
|
3427
|
+
const expectHash = flags["expect-hash"];
|
|
3428
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3429
|
+
throw new UsageError("--expect-hash must be a 64-character SHA-256 hex digest.");
|
|
2769
3430
|
}
|
|
3431
|
+
const endpoint = codeEnvEndpoint();
|
|
2770
3432
|
return {
|
|
3433
|
+
exact: false,
|
|
3434
|
+
reason: "Apply GETs the full definition, merges only package fields, then PUTs the full result.",
|
|
2771
3435
|
method: "PUT",
|
|
2772
|
-
endpoint
|
|
2773
|
-
identifiers: { lang: args[0], name: args[1], },
|
|
2774
|
-
payload
|
|
3436
|
+
endpoint,
|
|
3437
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3438
|
+
payload: {
|
|
3439
|
+
packages: codeEnvPackageList(flags),
|
|
3440
|
+
...(installCorePackages !== undefined ? { installCorePackages, } : {}),
|
|
3441
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3442
|
+
},
|
|
3443
|
+
requests: [
|
|
3444
|
+
{ method: "GET", endpoint, purpose: "read full definition and verify expectHash", },
|
|
3445
|
+
{
|
|
3446
|
+
method: "PUT",
|
|
3447
|
+
endpoint,
|
|
3448
|
+
condition: "definition read and hash matched",
|
|
3449
|
+
payload: "<merged full definition>",
|
|
3450
|
+
},
|
|
3451
|
+
],
|
|
2775
3452
|
};
|
|
2776
3453
|
}
|
|
2777
3454
|
case "code-env.update-packages": {
|
|
@@ -2780,15 +3457,25 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2780
3457
|
: typeof flags["version"] === "string"
|
|
2781
3458
|
? flags["version"]
|
|
2782
3459
|
: undefined;
|
|
3460
|
+
const wait = codeEnvWait(flags);
|
|
2783
3461
|
return {
|
|
2784
3462
|
method: "POST",
|
|
2785
3463
|
endpoint: `${codeEnvEndpoint("/packages")}${querySuffix({
|
|
2786
3464
|
forceRebuildEnv: flags["force-rebuild"] === true,
|
|
2787
3465
|
versionToUpdate,
|
|
2788
|
-
wait
|
|
3466
|
+
wait,
|
|
2789
3467
|
})}`,
|
|
2790
|
-
identifiers: { lang: args[0], name: args[1], },
|
|
2791
|
-
wait
|
|
3468
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3469
|
+
wait,
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
case "code-env.update-images": {
|
|
3473
|
+
const wait = codeEnvWait(flags);
|
|
3474
|
+
return {
|
|
3475
|
+
method: "POST",
|
|
3476
|
+
endpoint: `${codeEnvEndpoint("/images")}${querySuffix({ envVersion: flags["env-version"], wait, })}`,
|
|
3477
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3478
|
+
wait,
|
|
2792
3479
|
};
|
|
2793
3480
|
}
|
|
2794
3481
|
case "code-env.set-jupyter": {
|
|
@@ -2796,27 +3483,52 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2796
3483
|
if (active === undefined) {
|
|
2797
3484
|
throw new UsageError("--active is required. Usage: dss code-env set-jupyter <lang> <name> --active true|false");
|
|
2798
3485
|
}
|
|
3486
|
+
const wait = codeEnvWait(flags);
|
|
2799
3487
|
return {
|
|
2800
3488
|
method: "POST",
|
|
2801
|
-
endpoint: `${codeEnvEndpoint("/jupyter")}${querySuffix({ active, wait
|
|
2802
|
-
identifiers: { lang: args[0], name: args[1], },
|
|
2803
|
-
wait
|
|
3489
|
+
endpoint: `${codeEnvEndpoint("/jupyter")}${querySuffix({ active, wait, })}`,
|
|
3490
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3491
|
+
wait,
|
|
2804
3492
|
};
|
|
2805
3493
|
}
|
|
2806
|
-
case "code-env.delete":
|
|
3494
|
+
case "code-env.delete": {
|
|
3495
|
+
const wait = codeEnvWait(flags);
|
|
2807
3496
|
return {
|
|
2808
3497
|
method: "DELETE",
|
|
2809
|
-
endpoint:
|
|
2810
|
-
identifiers: { lang: args[0], name: args[1], },
|
|
2811
|
-
wait
|
|
3498
|
+
endpoint: `${codeEnvEndpoint()}?wait=${wait}`,
|
|
3499
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3500
|
+
wait,
|
|
2812
3501
|
};
|
|
2813
|
-
|
|
3502
|
+
}
|
|
3503
|
+
case "notebook.save-jupyter": {
|
|
3504
|
+
const content = requiredPlanJsonInput(flags, entry.usage);
|
|
3505
|
+
const expectHash = flags["expect-hash"];
|
|
3506
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3507
|
+
throw new UsageError("Expected notebook hash must be a 64-character SHA-256 hex digest.");
|
|
3508
|
+
}
|
|
3509
|
+
const endpoint = projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}`);
|
|
2814
3510
|
return {
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
3511
|
+
exact: false,
|
|
3512
|
+
reason: "A fresh GET selects POST when absent or PUT when present; a confirming GET supplies the persisted hash.",
|
|
3513
|
+
endpoint,
|
|
3514
|
+
identifiers: {
|
|
3515
|
+
name: id,
|
|
3516
|
+
contentHash: stableHash(content),
|
|
3517
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3518
|
+
},
|
|
3519
|
+
requests: [
|
|
3520
|
+
{ method: "GET", endpoint, purpose: "resolve create/update and check --expect-hash", },
|
|
3521
|
+
{
|
|
3522
|
+
method: "POST|PUT",
|
|
3523
|
+
endpoint,
|
|
3524
|
+
condition: "POST after not_found; otherwise PUT",
|
|
3525
|
+
payload: "<omitted>",
|
|
3526
|
+
redactedFields: ["payload",],
|
|
3527
|
+
},
|
|
3528
|
+
{ method: "GET", endpoint, purpose: "confirm persisted hash", },
|
|
3529
|
+
],
|
|
2819
3530
|
};
|
|
3531
|
+
}
|
|
2820
3532
|
case "notebook.delete-jupyter":
|
|
2821
3533
|
return {
|
|
2822
3534
|
method: "DELETE",
|
|
@@ -2825,24 +3537,75 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2825
3537
|
};
|
|
2826
3538
|
case "notebook.clear-jupyter-outputs":
|
|
2827
3539
|
return {
|
|
2828
|
-
method: "
|
|
2829
|
-
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}`),
|
|
3540
|
+
method: "DELETE",
|
|
3541
|
+
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}/outputs`),
|
|
2830
3542
|
identifiers: { name: id, },
|
|
2831
|
-
payload: { clearOutputs: true, },
|
|
2832
3543
|
};
|
|
2833
3544
|
case "notebook.unload-jupyter":
|
|
3545
|
+
if (flags["all"] === true) {
|
|
3546
|
+
if (args.length > 0)
|
|
3547
|
+
throw new UsageError(entry.usage);
|
|
3548
|
+
return {
|
|
3549
|
+
exact: false,
|
|
3550
|
+
reason: "DSS has no unload-all endpoint; apply lists active notebooks and sessions, then issues one verified session DELETE per result.",
|
|
3551
|
+
identifiers: { all: true, },
|
|
3552
|
+
requests: [
|
|
3553
|
+
{ method: "GET", endpoint: projectEndpoint("/jupyter-notebooks/?active=true"), },
|
|
3554
|
+
{
|
|
3555
|
+
method: "GET",
|
|
3556
|
+
endpoint: projectEndpoint("/jupyter-notebooks/{name}/sessions"),
|
|
3557
|
+
forEach: "active notebook",
|
|
3558
|
+
},
|
|
3559
|
+
{
|
|
3560
|
+
method: "DELETE",
|
|
3561
|
+
endpoint: projectEndpoint("/jupyter-notebooks/{name}/sessions/{sessionId}"),
|
|
3562
|
+
forEach: "listed session",
|
|
3563
|
+
},
|
|
3564
|
+
],
|
|
3565
|
+
};
|
|
3566
|
+
}
|
|
3567
|
+
requireArgs(args, 2, entry.usage);
|
|
2834
3568
|
return {
|
|
2835
|
-
method: "
|
|
2836
|
-
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(args[0])}/sessions/${encodeURIComponent(args[1])}
|
|
3569
|
+
method: "DELETE",
|
|
3570
|
+
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(args[0])}/sessions/${encodeURIComponent(args[1])}`),
|
|
2837
3571
|
identifiers: { name: args[0], sessionId: args[1], },
|
|
2838
3572
|
};
|
|
2839
|
-
case "notebook.save-sql":
|
|
3573
|
+
case "notebook.save-sql": {
|
|
3574
|
+
const content = requiredPlanJsonInput(flags, entry.usage);
|
|
3575
|
+
const expectHash = flags["expect-hash"];
|
|
3576
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3577
|
+
throw new UsageError("Expected notebook hash must be a 64-character SHA-256 hex digest.");
|
|
3578
|
+
}
|
|
3579
|
+
const endpoint = projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}`);
|
|
2840
3580
|
return {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
3581
|
+
exact: false,
|
|
3582
|
+
reason: "A fresh GET selects collection POST when absent or entity PUT when present; a confirming GET supplies the persisted hash.",
|
|
3583
|
+
endpoint,
|
|
3584
|
+
identifiers: {
|
|
3585
|
+
id,
|
|
3586
|
+
contentHash: stableHash(content),
|
|
3587
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3588
|
+
},
|
|
3589
|
+
requests: [
|
|
3590
|
+
{ method: "GET", endpoint, purpose: "resolve create/update and check --expect-hash", },
|
|
3591
|
+
{
|
|
3592
|
+
method: "POST",
|
|
3593
|
+
endpoint: projectEndpoint("/sql-notebooks/"),
|
|
3594
|
+
condition: "after not_found",
|
|
3595
|
+
payload: "<omitted; includes id and projectKey>",
|
|
3596
|
+
redactedFields: ["payload",],
|
|
3597
|
+
},
|
|
3598
|
+
{
|
|
3599
|
+
method: "PUT",
|
|
3600
|
+
endpoint,
|
|
3601
|
+
condition: "when present",
|
|
3602
|
+
payload: "<omitted>",
|
|
3603
|
+
redactedFields: ["payload",],
|
|
3604
|
+
},
|
|
3605
|
+
{ method: "GET", endpoint, purpose: "confirm persisted hash", },
|
|
3606
|
+
],
|
|
2845
3607
|
};
|
|
3608
|
+
}
|
|
2846
3609
|
case "notebook.delete-sql":
|
|
2847
3610
|
return {
|
|
2848
3611
|
method: "DELETE",
|
|
@@ -2851,10 +3614,13 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2851
3614
|
};
|
|
2852
3615
|
case "notebook.clear-sql-history":
|
|
2853
3616
|
return {
|
|
2854
|
-
method: "
|
|
2855
|
-
endpoint: projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}/history`),
|
|
2856
|
-
identifiers: { id,
|
|
2857
|
-
payload: {
|
|
3617
|
+
method: "POST",
|
|
3618
|
+
endpoint: projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}/history/clear`),
|
|
3619
|
+
identifiers: { id, },
|
|
3620
|
+
payload: {
|
|
3621
|
+
cellId: flags["cell-id"],
|
|
3622
|
+
numRunsToRetain: num(flags["retain"], "--retain"),
|
|
3623
|
+
},
|
|
2858
3624
|
};
|
|
2859
3625
|
case "project.create": {
|
|
2860
3626
|
const settings = jsonInput(flags) ?? null;
|
|
@@ -3225,7 +3991,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
3225
3991
|
};
|
|
3226
3992
|
}
|
|
3227
3993
|
case "project-git.set-library": {
|
|
3228
|
-
const targetPath = id;
|
|
3994
|
+
const targetPath = validateGitReferencePath(id);
|
|
3229
3995
|
return {
|
|
3230
3996
|
method: "PUT",
|
|
3231
3997
|
endpoint: projectGitEndpoint(projectKey, `/lib-git-refs/${encodeGitReferencePath(targetPath)}`),
|
|
@@ -3240,7 +4006,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
3240
4006
|
};
|
|
3241
4007
|
}
|
|
3242
4008
|
case "project-git.remove-library": {
|
|
3243
|
-
const targetPath = id;
|
|
4009
|
+
const targetPath = validateGitReferencePath(id);
|
|
3244
4010
|
return {
|
|
3245
4011
|
method: "DELETE",
|
|
3246
4012
|
endpoint: projectGitEndpoint(projectKey, `/lib-git-refs/${encodeGitReferencePath(targetPath)}${querySuffix({
|
|
@@ -3250,7 +4016,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
3250
4016
|
};
|
|
3251
4017
|
}
|
|
3252
4018
|
case "project-git.reset-library": {
|
|
3253
|
-
const targetPath = id;
|
|
4019
|
+
const targetPath = validateGitReferencePath(id);
|
|
3254
4020
|
return {
|
|
3255
4021
|
method: "POST",
|
|
3256
4022
|
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/action/reset"),
|
|
@@ -3260,7 +4026,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
3260
4026
|
};
|
|
3261
4027
|
}
|
|
3262
4028
|
case "project-git.push-library": {
|
|
3263
|
-
const targetPath = id;
|
|
4029
|
+
const targetPath = validateGitReferencePath(id);
|
|
3264
4030
|
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
3265
4031
|
return {
|
|
3266
4032
|
method: "POST",
|
|
@@ -3294,12 +4060,9 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
3294
4060
|
};
|
|
3295
4061
|
default:
|
|
3296
4062
|
return {
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
? projectEndpoint(`/${resource}s/${id ? encodeURIComponent(id) : ""}`)
|
|
3300
|
-
: undefined,
|
|
4063
|
+
exact: false,
|
|
4064
|
+
reason: `No exact offline request shape is defined for ${resource}.${action}; no endpoint was guessed.`,
|
|
3301
4065
|
identifiers: id ? { id, } : undefined,
|
|
3302
|
-
payload: jsonInput(flags),
|
|
3303
4066
|
};
|
|
3304
4067
|
}
|
|
3305
4068
|
}
|
|
@@ -3333,7 +4096,7 @@ export const BATCH_PLAN_EXIT_CODES = {
|
|
|
3333
4096
|
assertionFailure: 4,
|
|
3334
4097
|
};
|
|
3335
4098
|
export const BATCH_USAGE = "dss batch (--data JSON|--data-file PATH|--stdin) [--continue-on-error] [--dry-run]";
|
|
3336
|
-
const BATCH_DESCRIPTION = "Run
|
|
4099
|
+
const BATCH_DESCRIPTION = "Run dss argv arrays fail-fast by default. The result aggregates failed/retryable/failureCodes; process exit remains the first failed step's exit code.";
|
|
3337
4100
|
export const BATCH_HINT = 'Pass a JSON array of argv arrays, e.g. [["dataset","list"],["recipe","update","r","--data-file","p.json"]].';
|
|
3338
4101
|
export const BATCH_EXAMPLE_PAYLOAD = [
|
|
3339
4102
|
["recipe", "set-payload", "compute_orders", "--file", "code.py", "--no-backup",],
|