dataiku-sdk 3.6.0 → 3.7.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 +14 -6
- package/bin/bun-version.js +14 -0
- package/bin/dss.js +23 -12
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +74 -1
- package/dist/packages/types/src/index.js +53 -1
- package/dist/src/cli/coerce.d.ts +1 -0
- package/dist/src/cli/coerce.js +19 -15
- package/dist/src/cli/command-syntax.json +3521 -416
- package/dist/src/cli/commands/analysis.js +19 -1
- package/dist/src/cli/commands/bundle.js +22 -0
- package/dist/src/cli/commands/connection.js +26 -2
- package/dist/src/cli/commands/data-quality.js +33 -1
- package/dist/src/cli/commands/dataset.js +35 -4
- package/dist/src/cli/commands/discussion.js +15 -0
- package/dist/src/cli/commands/doctor.js +1 -1
- package/dist/src/cli/commands/flow.d.ts +5 -0
- package/dist/src/cli/commands/flow.js +57 -0
- package/dist/src/cli/commands/future.js +8 -0
- package/dist/src/cli/commands/index.js +6 -0
- package/dist/src/cli/commands/job.js +29 -6
- package/dist/src/cli/commands/metrics.js +21 -1
- package/dist/src/cli/commands/ml-task.d.ts +5 -0
- package/dist/src/cli/commands/ml-task.js +86 -16
- package/dist/src/cli/commands/mlflow.d.ts +11 -0
- package/dist/src/cli/commands/mlflow.js +121 -0
- package/dist/src/cli/commands/plugin.js +5 -11
- package/dist/src/cli/commands/project-deployer.js +70 -0
- package/dist/src/cli/commands/project-library.js +2 -5
- package/dist/src/cli/commands/project.js +8 -0
- package/dist/src/cli/commands/recipe.d.ts +9 -0
- package/dist/src/cli/commands/recipe.js +93 -7
- package/dist/src/cli/commands/saved-model.d.ts +1 -1
- package/dist/src/cli/commands/saved-model.js +9 -76
- package/dist/src/cli/commands/streaming-endpoint.js +30 -1
- package/dist/src/cli/commands/webapp.js +8 -0
- package/dist/src/cli/commands/wiki.js +22 -0
- package/dist/src/cli/commands/workspace.js +8 -0
- package/dist/src/cli/contract.d.ts +16 -15
- package/dist/src/cli/contract.js +139 -3404
- package/dist/src/cli/doctor.d.ts +13 -0
- package/dist/src/cli/doctor.js +41 -1
- package/dist/src/cli/flags.d.ts +7 -1
- package/dist/src/cli/flags.js +78 -6
- package/dist/src/cli/helpers/app-successor.js +1 -2
- package/dist/src/cli/helpers/documentation.d.ts +21 -0
- package/dist/src/cli/helpers/documentation.js +53 -0
- package/dist/src/cli/helpers/trained-model.d.ts +26 -0
- package/dist/src/cli/helpers/trained-model.js +144 -0
- package/dist/src/cli/list-output.d.ts +7 -0
- package/dist/src/cli/list-output.js +77 -0
- package/dist/src/cli/list-shapes.d.ts +21 -0
- package/dist/src/cli/list-shapes.js +111 -0
- package/dist/src/cli/output-schemas.d.ts +0 -14
- package/dist/src/cli/output-schemas.js +4 -7
- package/dist/src/cli/output.d.ts +0 -1
- package/dist/src/cli/output.js +27 -19
- package/dist/src/cli/plans.d.ts +14 -0
- package/dist/src/cli/plans.js +3684 -0
- package/dist/src/cli/usage.d.ts +7 -0
- package/dist/src/cli/usage.js +20 -1
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +2 -2
- package/dist/src/cli.js +127 -113
- package/dist/src/client.d.ts +52 -43
- package/dist/src/client.js +267 -96
- package/dist/src/errors.d.ts +11 -1
- package/dist/src/errors.js +31 -47
- package/dist/src/generated/action-output-schemas.json +345 -89
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.js +3 -0
- package/dist/src/resources/analyses.d.ts +8 -0
- package/dist/src/resources/analyses.js +13 -1
- package/dist/src/resources/bundles.d.ts +34 -0
- package/dist/src/resources/bundles.js +67 -0
- package/dist/src/resources/connections.d.ts +9 -0
- package/dist/src/resources/connections.js +15 -0
- package/dist/src/resources/data-quality.d.ts +20 -0
- package/dist/src/resources/data-quality.js +30 -0
- package/dist/src/resources/dataset-create.d.ts +19 -0
- package/dist/src/resources/dataset-create.js +66 -0
- package/dist/src/resources/datasets.d.ts +25 -15
- package/dist/src/resources/datasets.js +59 -111
- package/dist/src/resources/discussions.d.ts +5 -0
- package/dist/src/resources/discussions.js +7 -0
- package/dist/src/resources/documentation.d.ts +25 -0
- package/dist/src/resources/documentation.js +16 -0
- package/dist/src/resources/flow.d.ts +48 -0
- package/dist/src/resources/flow.js +65 -0
- package/dist/src/resources/futures.d.ts +23 -0
- package/dist/src/resources/futures.js +32 -0
- package/dist/src/resources/jobs.js +102 -2
- package/dist/src/resources/metrics.d.ts +10 -0
- package/dist/src/resources/metrics.js +10 -0
- package/dist/src/resources/ml-tasks.d.ts +65 -2
- package/dist/src/resources/ml-tasks.js +102 -28
- package/dist/src/resources/mlflow-extension.d.ts +73 -0
- package/dist/src/resources/mlflow-extension.js +105 -0
- package/dist/src/resources/projects.d.ts +6 -0
- package/dist/src/resources/projects.js +8 -0
- package/dist/src/resources/recipe-create.d.ts +65 -0
- package/dist/src/resources/recipe-create.js +338 -0
- package/dist/src/resources/recipes.d.ts +28 -29
- package/dist/src/resources/recipes.js +126 -245
- package/dist/src/resources/saved-models.d.ts +10 -6
- package/dist/src/resources/saved-models.js +16 -14
- package/dist/src/resources/scenarios.js +8 -14
- package/dist/src/resources/streaming-endpoints.d.ts +12 -0
- package/dist/src/resources/streaming-endpoints.js +15 -0
- package/dist/src/resources/trained-model.d.ts +60 -0
- package/dist/src/resources/trained-model.js +98 -0
- package/dist/src/resources/webapps.d.ts +8 -0
- package/dist/src/resources/webapps.js +10 -0
- package/dist/src/resources/wiki.d.ts +5 -0
- package/dist/src/resources/wiki.js +7 -0
- package/dist/src/resources/workspaces.d.ts +2 -0
- package/dist/src/resources/workspaces.js +4 -0
- package/dist/src/utils/response-file.js +20 -9
- package/dist/src/utils/secret-sanitize.d.ts +7 -0
- package/dist/src/utils/secret-sanitize.js +46 -0
- package/package.json +25 -26
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +1 -1
- package/skills/dataiku-dss/references/authentication.md +3 -1
- package/skills/dataiku-dss/references/coding.md +1 -0
- package/skills/dataiku-dss/references/discovery.md +4 -6
- package/skills/dataiku-dss/references/troubleshooting.md +3 -2
package/dist/src/cli/contract.js
CHANGED
|
@@ -1,74 +1,17 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { validateCredentials, } from "../auth.js";
|
|
4
2
|
import { getCredentialsPath, saveCredentials, } from "../config.js";
|
|
5
3
|
import { DataikuError, } from "../errors.js";
|
|
6
4
|
import actionOutputSchemasMetadata from "../generated/action-output-schemas.json" with { type: "json"
|
|
7
5
|
};
|
|
8
|
-
import { APP_MANIFEST_CONCURRENCY_CONTROL, } from "../resources/applications.js";
|
|
9
|
-
import { buildDatasetCreateBody, } from "../resources/datasets.js";
|
|
10
|
-
import { validatePluginDestinationPath, validatePluginPath, } from "../resources/plugins.js";
|
|
11
|
-
import { encodeLibraryPath, PROJECT_LIBRARY_CONCURRENCY_CONTROL, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../resources/project-library.js";
|
|
12
|
-
import { buildRecipeCreateRequest, } from "../resources/recipes.js";
|
|
13
|
-
import { encodeGitReferencePath, validateGitReferencePath, } from "../utils/git-reference.js";
|
|
14
|
-
import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
|
|
15
|
-
import { parseCodeRunIntegerFlag, resolveCodeInputWithSource, } from "./commands/code.js";
|
|
16
6
|
import { commands, } from "./commands/index.js";
|
|
17
|
-
import {
|
|
18
|
-
import { recipeCreateOptionsFromFlags, } from "./commands/recipe.js";
|
|
19
|
-
import { resolveSqlQueryInvocation, } from "./commands/sql.js";
|
|
20
|
-
import { ambientProjectKey, } from "./env.js";
|
|
21
|
-
import { BOOLEAN_FLAGS, executionMode, FLAG_ALIASES, SHORT_FLAGS, } from "./flags.js";
|
|
22
|
-
import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-zone.js";
|
|
23
|
-
import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
|
|
24
|
-
import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
|
|
7
|
+
import { BOOLEAN_FLAGS, FLAG_ALIASES, SHORT_FLAGS, } from "./flags.js";
|
|
25
8
|
import { resolveLoginCredentials, } from "./runtime.js";
|
|
26
|
-
import { commandSyntax, commandSyntaxTree, commandUsage, renderNodes, syntaxCommandWords, syntaxHasFlag, } from "./syntax.js";
|
|
27
|
-
import { inferRequiresProject,
|
|
9
|
+
import { commandSyntax, commandSyntaxKeys, commandSyntaxTree, commandUsage, renderNodes, syntaxCommandWords, syntaxHasFlag, } from "./syntax.js";
|
|
10
|
+
import { inferRequiresProject, UsageError, } from "./usage.js";
|
|
28
11
|
import { AGENT_CONTRACT_SCHEMA_ID, AGENT_CONTRACT_VERSION, cliVersionResult, JSON_SCHEMA_DRAFT, } from "./version.js";
|
|
29
|
-
function
|
|
30
|
-
return flags["no-wait"] !== true;
|
|
31
|
-
}
|
|
32
|
-
function codeEnvLang(value, usage) {
|
|
33
|
-
if (value !== "PYTHON" && value !== "R") {
|
|
34
|
-
throw new UsageError(`Invalid code environment language ${JSON.stringify(value)}. Usage: ${usage}`);
|
|
35
|
-
}
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
|
-
function codeEnvParams(flags) {
|
|
39
|
-
const params = json(flags["params"]) ?? jsonInput(flags) ?? {};
|
|
40
|
-
if (typeof flags["python-interpreter"] === "string") {
|
|
41
|
-
params.pythonInterpreter = flags["python-interpreter"];
|
|
42
|
-
}
|
|
43
|
-
return params;
|
|
44
|
-
}
|
|
45
|
-
function splitPackageSpec(raw) {
|
|
12
|
+
export function splitPackageSpec(raw) {
|
|
46
13
|
return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
47
14
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Resolve requested package specs from --file/--packages/--package. An
|
|
50
|
-
* explicit source that resolves to zero specs is a legitimate clear
|
|
51
|
-
* (set-packages replaces specPackageList wholesale); only a call with no
|
|
52
|
-
* package source flag at all is a usage error.
|
|
53
|
-
*/
|
|
54
|
-
function codeEnvPackageList(flags) {
|
|
55
|
-
const file = flags["file"];
|
|
56
|
-
const packages = typeof file === "string"
|
|
57
|
-
? splitPackageSpec(fs.readFileSync(file, "utf-8"))
|
|
58
|
-
: [];
|
|
59
|
-
if (typeof flags["packages"] === "string") {
|
|
60
|
-
packages.push(...splitPackageSpec(flags["packages"]));
|
|
61
|
-
}
|
|
62
|
-
if (typeof flags["package"] === "string") {
|
|
63
|
-
packages.push(...splitPackageSpec(flags["package"]));
|
|
64
|
-
}
|
|
65
|
-
if (typeof file !== "string"
|
|
66
|
-
&& typeof flags["packages"] !== "string"
|
|
67
|
-
&& typeof flags["package"] !== "string") {
|
|
68
|
-
throw new UsageError("--packages, --package, or --file is required. Use newline-separated package specs for version constraints; an explicitly empty value (or empty file) clears the requested package list.");
|
|
69
|
-
}
|
|
70
|
-
return packages;
|
|
71
|
-
}
|
|
72
15
|
export const AUTH_ACTIONS = {
|
|
73
16
|
login: {
|
|
74
17
|
handler: async (flags) => {
|
|
@@ -242,10 +185,12 @@ const AUTHENTICATED_AGENT_FLAGS = [
|
|
|
242
185
|
"insecure",
|
|
243
186
|
"ca-cert",
|
|
244
187
|
];
|
|
188
|
+
const GLOBAL_FLAG_NAMES = Object.fromEntries([...GLOBAL_AGENT_FLAGS, ...AUTHENTICATED_AGENT_FLAGS,].map((flag) => [flag, true,]));
|
|
245
189
|
export const COMMANDS_USAGE = commandUsage("commands", "run");
|
|
246
|
-
const COMMANDS_DESCRIPTION = "Print a compact resource/action summary by default;
|
|
190
|
+
const COMMANDS_DESCRIPTION = "Print a compact resource/action summary by default; --contains TEXT finds commands by concept (top 5 of id/usage/description matches), --fields gives scoped command metadata, and --output PATH exports the full registry without sending it through stdout.";
|
|
247
191
|
const COMMANDS_EXAMPLES = [
|
|
248
192
|
"dss commands run",
|
|
193
|
+
'dss commands run --contains "propagate schema"',
|
|
249
194
|
"dss commands run --fields dataset",
|
|
250
195
|
"dss commands run --fields dataset.create",
|
|
251
196
|
"dss commands run --fields dataset.create.usage,dataset.create.description,dataset.create.flags,dataset.create.examples",
|
|
@@ -426,16 +371,18 @@ function canonicalizeArgv(argv) {
|
|
|
426
371
|
}
|
|
427
372
|
return canonical;
|
|
428
373
|
}
|
|
374
|
+
/** Machine forms of `examples`: canonical argv arrays, plus the example payload for --data examples. */
|
|
429
375
|
function structuredExamples(examples, examplePayload) {
|
|
430
|
-
return (examples ?? []).
|
|
376
|
+
return (examples ?? []).flatMap((shell) => {
|
|
431
377
|
const argv = exampleArgv(shell);
|
|
432
|
-
return
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
378
|
+
return argv
|
|
379
|
+
? [{
|
|
380
|
+
argv,
|
|
381
|
+
...(examplePayload !== undefined && shell.includes("--data")
|
|
382
|
+
? { payload: examplePayload, }
|
|
383
|
+
: {}),
|
|
384
|
+
},]
|
|
385
|
+
: [];
|
|
439
386
|
});
|
|
440
387
|
}
|
|
441
388
|
/**
|
|
@@ -528,8 +475,7 @@ function argvJsonSchema(resource, action, flags, usage, requiredFlags, requiredO
|
|
|
528
475
|
for (const flag of flags) {
|
|
529
476
|
aliases[flag.name] = flag.aliases ?? [];
|
|
530
477
|
}
|
|
531
|
-
const booleanNames = flags.
|
|
532
|
-
.flatMap((flag) => [flag.name, ...(flag.aliases ?? []),]);
|
|
478
|
+
const booleanNames = flags.flatMap((flag) => flag.kind === "boolean" ? [flag.name, ...(flag.aliases ?? []),] : []);
|
|
533
479
|
const valueFlags = flags.filter((flag) => flag.kind === "value");
|
|
534
480
|
const requiredPositionals = commandSyntax(resource, action).positionalArguments
|
|
535
481
|
.filter((positional) => positional.required).map((positional) => positional.name);
|
|
@@ -731,6 +677,26 @@ function inferSideEffect(resource, action) {
|
|
|
731
677
|
// Saved-model evaluation triggers scoring work on DSS side (cost-bearing).
|
|
732
678
|
if (resource === "saved-model" && action === "evaluate-version")
|
|
733
679
|
return "write";
|
|
680
|
+
// Flow-wide schema propagation rewrites downstream schemas and may rebuild
|
|
681
|
+
// datasets; `propagate` matches no mutating verb pattern.
|
|
682
|
+
if (resource === "flow" && action === "propagate-schema")
|
|
683
|
+
return "write";
|
|
684
|
+
// Hive syncs rewrite the Hive table or the dataset; running checks records outcomes.
|
|
685
|
+
if (resource === "dataset" && action === "sync-hive-metastore")
|
|
686
|
+
return "write";
|
|
687
|
+
if (resource === "metrics" && action === "dataset-run-checks")
|
|
688
|
+
return "write";
|
|
689
|
+
if (resource === "webapp" && action === "trust")
|
|
690
|
+
return "write";
|
|
691
|
+
if (resource === "project" && action === "push-to-git-remote")
|
|
692
|
+
return "write";
|
|
693
|
+
if (resource === "mlflow" && (action === "garbage-collect" || action === "clean-db")) {
|
|
694
|
+
return "write";
|
|
695
|
+
}
|
|
696
|
+
// Re-guessing rewrites the ML task settings.
|
|
697
|
+
if (resource === "ml-task" && (action === "reguess" || action === "reguess-forecasting")) {
|
|
698
|
+
return "write";
|
|
699
|
+
}
|
|
734
700
|
// LLM Mesh completions/embeddings invoke the LLM provider (cost-bearing,
|
|
735
701
|
// arbitrary-prompt execution); the verb shapes match no mutating pattern
|
|
736
702
|
// and would fall through to read, wrongly rejecting --plan. Knowledge-bank
|
|
@@ -924,6 +890,9 @@ const EXPLICIT_DESTRUCTIVE_KEYS = {
|
|
|
924
890
|
"macro.run-and-wait": true,
|
|
925
891
|
// Saved-model version deletion removes model versions permanently.
|
|
926
892
|
"saved-model.delete-versions": true,
|
|
893
|
+
// MLflow cleanup permanently erases experiment-tracking data.
|
|
894
|
+
"mlflow.garbage-collect": true,
|
|
895
|
+
"mlflow.clean-db": true,
|
|
927
896
|
};
|
|
928
897
|
function inferDestructiveLevel(resource, sideEffect, action) {
|
|
929
898
|
if (sideEffect !== "write")
|
|
@@ -967,6 +936,14 @@ function inferAsyncKind(resource, action) {
|
|
|
967
936
|
return "future";
|
|
968
937
|
if (resource === "webapp" && action === "restart-backend")
|
|
969
938
|
return "future";
|
|
939
|
+
if (resource === "flow" && (action === "propagate-schema" || action === "generate-documentation")) {
|
|
940
|
+
return "future";
|
|
941
|
+
}
|
|
942
|
+
// Trained-model diagnostics and documentation start DSS futures.
|
|
943
|
+
if ((resource === "ml-task" || resource === "saved-model")
|
|
944
|
+
&& (action === "compute-diagnostics" || action === "generate-documentation")) {
|
|
945
|
+
return "future";
|
|
946
|
+
}
|
|
970
947
|
if (resource === "code-env"
|
|
971
948
|
&& ["create", "update-packages", "update-images", "set-jupyter", "delete",].includes(action))
|
|
972
949
|
return "future";
|
|
@@ -1048,7 +1025,7 @@ export function inferCleanupHint(resource, action) {
|
|
|
1048
1025
|
}
|
|
1049
1026
|
return `For disposable creates: \`dss ${resource} delete <id>${ifExists}\`.`;
|
|
1050
1027
|
}
|
|
1051
|
-
function buildRegistryEntry(resource, action, meta) {
|
|
1028
|
+
export function buildRegistryEntry(resource, action, meta) {
|
|
1052
1029
|
const requiresAuth = meta.localHandler === undefined && inferRequiresAuth(resource);
|
|
1053
1030
|
const requiresProject = inferRequiresProject(resource, action);
|
|
1054
1031
|
const sideEffect = inferSideEffect(resource, action);
|
|
@@ -1113,12 +1090,16 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
1113
1090
|
};
|
|
1114
1091
|
});
|
|
1115
1092
|
const positionalArguments = syntax.positionalArguments;
|
|
1116
|
-
|
|
1093
|
+
// Implicit global flags are listed once in the agent contract
|
|
1094
|
+
// (`commands.globalFlags`); entries keep the flags their usage names. The
|
|
1095
|
+
// argv schema still accepts both.
|
|
1096
|
+
const implicitGlobalFlags = new Set(flags.filter((name) => GLOBAL_FLAG_NAMES[name] === true && !usageFlags.includes(name)));
|
|
1097
|
+
const commandFlagMetadata = flagMetadata.filter((flag) => !implicitGlobalFlags.has(flag.name));
|
|
1117
1098
|
const inputGroups = requiredInputGroups(resource, action);
|
|
1118
1099
|
const outputShape = inferOutputShape(resource, action);
|
|
1119
1100
|
const producesLocalFile = syntax.producesLocalFile;
|
|
1120
1101
|
const uniqueRequiredFlags = uniqueStrings(requiredFlags);
|
|
1121
|
-
const uniqueOptionalFlags = uniqueStrings(optionalFlags);
|
|
1102
|
+
const uniqueOptionalFlags = uniqueStrings(optionalFlags).filter((flag) => !implicitGlobalFlags.has(flag));
|
|
1122
1103
|
const unsafe = unsafeOutputs(resource, action, producesLocalFile);
|
|
1123
1104
|
let schemas;
|
|
1124
1105
|
return {
|
|
@@ -1128,8 +1109,7 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
1128
1109
|
description: meta.description,
|
|
1129
1110
|
examples: meta.examples,
|
|
1130
1111
|
structuredExamples: structuredExamples(meta.examples, examplePayload),
|
|
1131
|
-
flags:
|
|
1132
|
-
positionals,
|
|
1112
|
+
flags: commandFlagMetadata,
|
|
1133
1113
|
positionalArguments,
|
|
1134
1114
|
sideEffect,
|
|
1135
1115
|
requiresAuth,
|
|
@@ -1163,7 +1143,8 @@ function commandDefinitions(resourceFilter) {
|
|
|
1163
1143
|
for (const resource of Object.keys(commands)) {
|
|
1164
1144
|
if (resourceFilter !== undefined && resource !== resourceFilter)
|
|
1165
1145
|
continue;
|
|
1166
|
-
|
|
1146
|
+
// Forward the lazy lookup: enumerating resources must not load command modules.
|
|
1147
|
+
Object.defineProperty(registry, resource, { enumerable: true, get: () => commands[resource], });
|
|
1167
1148
|
}
|
|
1168
1149
|
if (resourceFilter === undefined || resourceFilter === "commands") {
|
|
1169
1150
|
registry.commands = {
|
|
@@ -1266,6 +1247,58 @@ export function buildCommandRegistry(resourceFilter) {
|
|
|
1266
1247
|
}
|
|
1267
1248
|
return registry;
|
|
1268
1249
|
}
|
|
1250
|
+
/** Query words that match nearly every description and carry no intent. */
|
|
1251
|
+
const SEARCH_STOPWORDS = {
|
|
1252
|
+
the: true,
|
|
1253
|
+
and: true,
|
|
1254
|
+
for: true,
|
|
1255
|
+
how: true,
|
|
1256
|
+
what: true,
|
|
1257
|
+
why: true,
|
|
1258
|
+
did: true,
|
|
1259
|
+
does: true,
|
|
1260
|
+
who: true,
|
|
1261
|
+
can: true,
|
|
1262
|
+
with: true,
|
|
1263
|
+
from: true,
|
|
1264
|
+
into: true,
|
|
1265
|
+
this: true,
|
|
1266
|
+
that: true,
|
|
1267
|
+
dss: true,
|
|
1268
|
+
};
|
|
1269
|
+
/**
|
|
1270
|
+
* Find actions by concept: every query word of three or more letters is
|
|
1271
|
+
* matched (case-insensitively, trailing plural `s` ignored) against the action
|
|
1272
|
+
* id, usage, and description. When some actions match every word, only those
|
|
1273
|
+
* are returned; otherwise actions matching more words rank first. Matches in
|
|
1274
|
+
* the id break ties. Reads command definitions only, never builds entries.
|
|
1275
|
+
*/
|
|
1276
|
+
export function searchCommands(query) {
|
|
1277
|
+
const words = [...new Set(query.toLowerCase().split(/[^a-z0-9]+/)),]
|
|
1278
|
+
.filter((word) => word.length >= 3 && SEARCH_STOPWORDS[word] !== true)
|
|
1279
|
+
.map((word) => word.length > 4 && word.endsWith("s") ? word.slice(0, -1) : word);
|
|
1280
|
+
if (words.length === 0)
|
|
1281
|
+
return [];
|
|
1282
|
+
const scored = [];
|
|
1283
|
+
for (const [resource, actions,] of Object.entries(commandDefinitions())) {
|
|
1284
|
+
for (const [action, meta,] of Object.entries(actions)) {
|
|
1285
|
+
const id = `${resource}.${action}`;
|
|
1286
|
+
const usage = meta.usage ?? commandUsage(resource, action);
|
|
1287
|
+
const description = meta.description ?? "";
|
|
1288
|
+
const text = `${id} ${usage} ${description}`.toLowerCase();
|
|
1289
|
+
const matched = words.filter((word) => text.includes(word));
|
|
1290
|
+
if (matched.length === 0)
|
|
1291
|
+
continue;
|
|
1292
|
+
const inId = matched.filter((word) => id.includes(word)).length;
|
|
1293
|
+
scored.push({ id, usage, description, matched: matched.length, inId, });
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const best = Math.max(0, ...scored.map((match) => match.matched));
|
|
1297
|
+
return scored
|
|
1298
|
+
.filter((match) => best < words.length || match.matched === words.length)
|
|
1299
|
+
.sort((a, b) => b.matched - a.matched || b.inId - a.inId || a.id.localeCompare(b.id))
|
|
1300
|
+
.map(({ id, usage, description, }) => ({ id, usage, description, }));
|
|
1301
|
+
}
|
|
1269
1302
|
function commandFlagJsonSchema() {
|
|
1270
1303
|
return {
|
|
1271
1304
|
type: "object",
|
|
@@ -1289,7 +1322,6 @@ function commandRegistryEntryJsonSchema() {
|
|
|
1289
1322
|
"action",
|
|
1290
1323
|
"usage",
|
|
1291
1324
|
"flags",
|
|
1292
|
-
"positionals",
|
|
1293
1325
|
"positionalArguments",
|
|
1294
1326
|
"sideEffect",
|
|
1295
1327
|
"requiresAuth",
|
|
@@ -1307,9 +1339,16 @@ function commandRegistryEntryJsonSchema() {
|
|
|
1307
1339
|
usage: { type: "string", },
|
|
1308
1340
|
description: { type: "string", },
|
|
1309
1341
|
examples: { type: "array", items: { type: "string", }, },
|
|
1310
|
-
structuredExamples: {
|
|
1342
|
+
structuredExamples: {
|
|
1343
|
+
type: "array",
|
|
1344
|
+
items: {
|
|
1345
|
+
type: "object",
|
|
1346
|
+
required: ["argv",],
|
|
1347
|
+
additionalProperties: false,
|
|
1348
|
+
properties: { argv: { type: "array", items: { type: "string", }, }, payload: {}, },
|
|
1349
|
+
},
|
|
1350
|
+
},
|
|
1311
1351
|
flags: { type: "array", items: commandFlagJsonSchema(), },
|
|
1312
|
-
positionals: { type: "array", items: { type: "string", }, },
|
|
1313
1352
|
positionalArguments: {
|
|
1314
1353
|
type: "array",
|
|
1315
1354
|
items: {
|
|
@@ -1420,9 +1459,17 @@ export function agentContractJsonSchema() {
|
|
|
1420
1459
|
};
|
|
1421
1460
|
}
|
|
1422
1461
|
export function commandActionSummary() {
|
|
1462
|
+
// command-syntax.json lists exactly the registered commands (pinned by
|
|
1463
|
+
// tests/cli/command-syntax.test.ts), so action names come from it and the
|
|
1464
|
+
// summary loads no command module.
|
|
1465
|
+
const actionsByResource = {};
|
|
1466
|
+
for (const key of commandSyntaxKeys()) {
|
|
1467
|
+
const dot = key.indexOf(".");
|
|
1468
|
+
(actionsByResource[key.slice(0, dot)] ??= []).push(key.slice(dot + 1));
|
|
1469
|
+
}
|
|
1423
1470
|
const summary = {};
|
|
1424
|
-
for (const
|
|
1425
|
-
summary[resource] =
|
|
1471
|
+
for (const resource of Object.keys(commandDefinitions())) {
|
|
1472
|
+
summary[resource] = (actionsByResource[resource] ?? []).sort();
|
|
1426
1473
|
}
|
|
1427
1474
|
return summary;
|
|
1428
1475
|
}
|
|
@@ -1439,11 +1486,18 @@ export function buildAgentContract() {
|
|
|
1439
1486
|
fullRegistryExportCommand: "dss commands run --output PATH",
|
|
1440
1487
|
scopedDiscoveryCommand: "dss commands run --fields RESOURCE[.ACTION[.FIELD...]]",
|
|
1441
1488
|
actionIndexCommand: "dss agent contract --fields commands.actions",
|
|
1489
|
+
searchCommand: "dss commands run --contains TEXT",
|
|
1442
1490
|
scopedDiscoveryExamples: [
|
|
1443
1491
|
"dss commands run --fields dataset",
|
|
1444
1492
|
"dss commands run --fields dataset.create",
|
|
1445
1493
|
],
|
|
1446
|
-
scopedDiscoveryHint: "Default: resource/action summary. --fields RESOURCE: all resource entries; RESOURCE.ACTION: one entry keyed by that path; append .FIELD for nested metadata. Comma-separate paths. --output PATH exports the full registry.",
|
|
1494
|
+
scopedDiscoveryHint: "Default: resource/action summary. --contains TEXT: top 5 actions by concept. --fields RESOURCE: all resource entries; RESOURCE.ACTION: one entry keyed by that path; append .FIELD for nested metadata. Comma-separate paths. --output PATH exports the full registry.",
|
|
1495
|
+
// Accepted by every command and absent from per-entry `flags`; the
|
|
1496
|
+
// connection flags matter only for commands that call DSS.
|
|
1497
|
+
globalFlags: [...GLOBAL_AGENT_FLAGS, ...AUTHENTICATED_AGENT_FLAGS,].map((name) => {
|
|
1498
|
+
const hint = GLOBAL_FLAG_VALUE_HINTS[name];
|
|
1499
|
+
return { name, kind: flagKind(name), ...(hint ? { valueType: hint.valueType, } : {}), };
|
|
1500
|
+
}),
|
|
1447
1501
|
actions: commandActionSummary(),
|
|
1448
1502
|
};
|
|
1449
1503
|
},
|
|
@@ -1464,6 +1518,7 @@ export function buildAgentContract() {
|
|
|
1464
1518
|
failure: "structured-error-object",
|
|
1465
1519
|
failureResultDetailLimitBytes: 65_536,
|
|
1466
1520
|
fieldProjection: "Missing --fields paths: null on stdout; field_projection_missing on stderr.",
|
|
1521
|
+
lists: "*.list: compact items; --full/--fields use DSS objects; --contains/--limit filter (list_truncated warning).",
|
|
1467
1522
|
richFailureResults: "doctor/batch/cleanup failures: own compact {ok:false,...} result on stdout and command exit code; no wrapper.",
|
|
1468
1523
|
},
|
|
1469
1524
|
stderr: {
|
|
@@ -1485,3326 +1540,6 @@ export function buildAgentContract() {
|
|
|
1485
1540
|
},
|
|
1486
1541
|
};
|
|
1487
1542
|
}
|
|
1488
|
-
function exitCodesOnFailure(entry) {
|
|
1489
|
-
return {
|
|
1490
|
-
usage: entry.exitCodes.usage,
|
|
1491
|
-
error: entry.exitCodes.error,
|
|
1492
|
-
transient: entry.exitCodes.transient,
|
|
1493
|
-
...(entry.exitCodes.longRunningFailure !== undefined
|
|
1494
|
-
? { longRunningFailure: entry.exitCodes.longRunningFailure, }
|
|
1495
|
-
: {}),
|
|
1496
|
-
...(entry.exitCodes.assertionFailure !== undefined
|
|
1497
|
-
? { assertionFailure: entry.exitCodes.assertionFailure, }
|
|
1498
|
-
: {}),
|
|
1499
|
-
};
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Plan-local project key resolution. `--plan` is a purely local preview, so it
|
|
1503
|
-
* resolves the target project from the explicit `--project-key` flag and the
|
|
1504
|
-
* documented `DATAIKU_PROJECT_KEY` environment variable only. The saved
|
|
1505
|
-
* credentials file is never opened and no API key is ever resolved, so planning
|
|
1506
|
-
* can neither depend on nor leak stored secrets.
|
|
1507
|
-
*/
|
|
1508
|
-
function planProjectKeyFromArgs(flags) {
|
|
1509
|
-
const fromFlag = flags["project-key"];
|
|
1510
|
-
if (typeof fromFlag === "string" && fromFlag.trim().length > 0)
|
|
1511
|
-
return fromFlag.trim();
|
|
1512
|
-
const fromEnv = ambientProjectKey();
|
|
1513
|
-
return fromEnv !== undefined && fromEnv.trim().length > 0 ? fromEnv.trim() : undefined;
|
|
1514
|
-
}
|
|
1515
|
-
/**
|
|
1516
|
-
* Commands whose plan must pin the target with an explicit `--project-key`
|
|
1517
|
-
* instead of falling back to ambient `DATAIKU_PROJECT_KEY`, so a cleanup plan
|
|
1518
|
-
* can never silently retarget a project the caller did not name.
|
|
1519
|
-
*/
|
|
1520
|
-
const EXPLICIT_PLAN_PROJECT_KEY = { "app.delete-instance": true, };
|
|
1521
|
-
/** Plan-local project key, or the canonical `--project-key` usage error. */
|
|
1522
|
-
function requiredPlanProjectKey(flags, usage) {
|
|
1523
|
-
return planProjectKeyFromArgs(flags) ?? requiredPlanFlag(flags, "project-key", usage);
|
|
1524
|
-
}
|
|
1525
|
-
function projectKeyForPlan(entry, flags) {
|
|
1526
|
-
if (!entry.requiresProject)
|
|
1527
|
-
return undefined;
|
|
1528
|
-
if (EXPLICIT_PLAN_PROJECT_KEY[`${entry.resource}.${entry.action}`] === true) {
|
|
1529
|
-
return requiredPlanFlag(flags, "project-key", entry.usage);
|
|
1530
|
-
}
|
|
1531
|
-
const projectKey = planProjectKeyFromArgs(flags);
|
|
1532
|
-
if (projectKey)
|
|
1533
|
-
return projectKey;
|
|
1534
|
-
throw new UsageError(`Missing project key. Pass --project-key or set DATAIKU_PROJECT_KEY before planning ${entry.resource} ${entry.action}.`);
|
|
1535
|
-
}
|
|
1536
|
-
function requiredPlanFlag(flags, name, usage) {
|
|
1537
|
-
const value = flags[name];
|
|
1538
|
-
// Return trimmed like the runtime's requiredStringFlag so plans advertise
|
|
1539
|
-
// exactly the identifier the command will act on.
|
|
1540
|
-
if (typeof value === "string" && value.trim().length > 0)
|
|
1541
|
-
return value.trim();
|
|
1542
|
-
throw new UsageError(`--${name} is required. Usage: ${usage}`);
|
|
1543
|
-
}
|
|
1544
|
-
/**
|
|
1545
|
-
* Optional caller-chosen identifier. Absent selects the command's generated
|
|
1546
|
-
* mode; a present-but-empty value is an input error, never a silent omission.
|
|
1547
|
-
*/
|
|
1548
|
-
function optionalPlanFlag(flags, name, usage) {
|
|
1549
|
-
const value = flags[name];
|
|
1550
|
-
if (value === undefined)
|
|
1551
|
-
return undefined;
|
|
1552
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1553
|
-
throw new UsageError(`--${name} must not be empty when supplied; omit --${name} entirely to generate the successor key during apply. Usage: ${usage}`, "validation_failed");
|
|
1554
|
-
}
|
|
1555
|
-
return value.trim();
|
|
1556
|
-
}
|
|
1557
|
-
function optionalJsonFlag(flags, name) {
|
|
1558
|
-
const value = flags[name];
|
|
1559
|
-
return typeof value === "string" ? parseJsonObject(value, `--${name}`) : undefined;
|
|
1560
|
-
}
|
|
1561
|
-
function requiredPlanJsonInput(flags, usage) {
|
|
1562
|
-
return requiredJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${usage}`);
|
|
1563
|
-
}
|
|
1564
|
-
function dataQualityEndpoint(projectKey, datasetName, suffix) {
|
|
1565
|
-
return encodedProjectEndpointForPlan(projectKey, `/datasets/${encodeURIComponent(datasetName)}/data-quality${suffix}`);
|
|
1566
|
-
}
|
|
1567
|
-
function querySuffix(params) {
|
|
1568
|
-
const search = new URLSearchParams();
|
|
1569
|
-
for (const [key, value,] of Object.entries(params)) {
|
|
1570
|
-
if (value !== undefined)
|
|
1571
|
-
search.set(key, String(value));
|
|
1572
|
-
}
|
|
1573
|
-
const raw = search.toString();
|
|
1574
|
-
return raw ? `?${raw}` : "";
|
|
1575
|
-
}
|
|
1576
|
-
function projectFolderEndpoint(folderId) {
|
|
1577
|
-
return `/public/api/project-folders/${encodeURIComponent(folderId)}`;
|
|
1578
|
-
}
|
|
1579
|
-
/**
|
|
1580
|
-
* Git and future API mount. The official Python client mounts these routes on
|
|
1581
|
-
* `/dip/publicapi`, which is also what the project-git resource uses; do not
|
|
1582
|
-
* normalize them to the repo-wide `/public/api`.
|
|
1583
|
-
*/
|
|
1584
|
-
const PROJECT_GIT_API_ROOT = "/dip/publicapi";
|
|
1585
|
-
function projectGitEndpoint(projectKey, suffix) {
|
|
1586
|
-
if (!projectKey)
|
|
1587
|
-
throw new UsageError("--project-key is required for project-git mutations.");
|
|
1588
|
-
return `${PROJECT_GIT_API_ROOT}/projects/${encodeURIComponent(projectKey)}/git${suffix}`;
|
|
1589
|
-
}
|
|
1590
|
-
function projectGitFutureEndpoint(jobId) {
|
|
1591
|
-
return `${PROJECT_GIT_API_ROOT}/futures/${encodeURIComponent(jobId)}`;
|
|
1592
|
-
}
|
|
1593
|
-
/**
|
|
1594
|
-
* `--plan` bypasses the SDK boundary guard, so HTTP(S) URLs with embedded
|
|
1595
|
-
* userinfo are rejected here before any plan is printed. SSH/scp-style URLs
|
|
1596
|
-
* (`git@host:org/repo.git`, `ssh://...`) remain valid.
|
|
1597
|
-
*/
|
|
1598
|
-
function validatedPlanRepositoryUrl(url, flag, usage) {
|
|
1599
|
-
const candidate = url.trim();
|
|
1600
|
-
if (/[\u0000-\u001f\u007f]/u.test(candidate)) {
|
|
1601
|
-
throw new UsageError(`--${flag} must not contain control characters. Usage: ${usage}`);
|
|
1602
|
-
}
|
|
1603
|
-
if (/^https?:/i.test(candidate)) {
|
|
1604
|
-
if (candidate.includes("\\")) {
|
|
1605
|
-
throw new UsageError(`--${flag} must be a valid HTTP(S) URL. Usage: ${usage}`);
|
|
1606
|
-
}
|
|
1607
|
-
if (!/^https?:\/\//i.test(candidate)) {
|
|
1608
|
-
throw new UsageError(`--${flag} must be a valid HTTP(S) URL. Usage: ${usage}`);
|
|
1609
|
-
}
|
|
1610
|
-
let parsed;
|
|
1611
|
-
try {
|
|
1612
|
-
parsed = new URL(candidate);
|
|
1613
|
-
}
|
|
1614
|
-
catch {
|
|
1615
|
-
throw new UsageError(`--${flag} must be a valid HTTP(S) URL. Usage: ${usage}`);
|
|
1616
|
-
}
|
|
1617
|
-
if (parsed.username !== "" || parsed.password !== "") {
|
|
1618
|
-
throw new UsageError(`--${flag} must not contain embedded credentials (userinfo). Usage: ${usage}`);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
return candidate;
|
|
1622
|
-
}
|
|
1623
|
-
function pluginRootEndpoint(pluginId, suffix) {
|
|
1624
|
-
return `/public/api/plugins/${encodeURIComponent(pluginId)}${suffix}`;
|
|
1625
|
-
}
|
|
1626
|
-
function pluginActionEndpoint(pluginId, action) {
|
|
1627
|
-
return pluginRootEndpoint(pluginId, `/actions/${action}`);
|
|
1628
|
-
}
|
|
1629
|
-
function pluginContentsEndpoint(pluginId, contentPath) {
|
|
1630
|
-
return `${pluginRootEndpoint(pluginId, "/contents/")}${encodePluginSegmentsForPlan(contentPath)}`;
|
|
1631
|
-
}
|
|
1632
|
-
function pluginFoldersEndpoint(pluginId, contentPath) {
|
|
1633
|
-
return `${pluginRootEndpoint(pluginId, "/folders/")}${encodePluginSegmentsForPlan(contentPath)}`;
|
|
1634
|
-
}
|
|
1635
|
-
/** Encodes a plugin content path per segment for plan-time endpoints. */
|
|
1636
|
-
function encodePluginSegmentsForPlan(contentPath) {
|
|
1637
|
-
return contentPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
1638
|
-
}
|
|
1639
|
-
/**
|
|
1640
|
-
* Plan payload for plugin git install/update bodies. The repository URL is
|
|
1641
|
-
* validated (embedded userinfo rejected) and secrets are never echoed: the
|
|
1642
|
-
* plan carries the URL only, never embedded credentials.
|
|
1643
|
-
*/
|
|
1644
|
-
function pluginGitPlanPayload(flags) {
|
|
1645
|
-
const repository = requiredPlanFlag(flags, "repository", "dss plugin install-from-git --repository URL");
|
|
1646
|
-
// Mirrors PluginsResource install/update bodies: absent options are sent as null.
|
|
1647
|
-
return {
|
|
1648
|
-
gitRepositoryUrl: validatedPlanRepositoryUrl(repository, "repository", "--repository URL"),
|
|
1649
|
-
gitCheckout: typeof flags["checkout"] === "string" ? flags["checkout"] : null,
|
|
1650
|
-
gitSubpath: typeof flags["path-in-repository"] === "string" ? flags["path-in-repository"] : null,
|
|
1651
|
-
};
|
|
1652
|
-
}
|
|
1653
|
-
function optionalPlanProjectScope(flags) {
|
|
1654
|
-
const projectKey = flags["project-key"];
|
|
1655
|
-
if (typeof projectKey !== "string" || projectKey.trim() === "")
|
|
1656
|
-
return "";
|
|
1657
|
-
return `?projectKey=${encodeURIComponent(projectKey.trim())}`;
|
|
1658
|
-
}
|
|
1659
|
-
function settingsConfigKeys(flags) {
|
|
1660
|
-
const content = flags["content"];
|
|
1661
|
-
if (typeof content !== "string")
|
|
1662
|
-
return [];
|
|
1663
|
-
try {
|
|
1664
|
-
const parsed = JSON.parse(content);
|
|
1665
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1666
|
-
return [];
|
|
1667
|
-
return Object.keys(parsed);
|
|
1668
|
-
}
|
|
1669
|
-
catch {
|
|
1670
|
-
return [];
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
function contentSourceKind(flags) {
|
|
1674
|
-
if (flags["stdin"] === true)
|
|
1675
|
-
return "stdin";
|
|
1676
|
-
if (typeof flags["file"] === "string")
|
|
1677
|
-
return "file";
|
|
1678
|
-
if (typeof flags["content"] === "string")
|
|
1679
|
-
return "content";
|
|
1680
|
-
return "unknown";
|
|
1681
|
-
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Plan-safe view of a user create/update body: password values are replaced by
|
|
1684
|
-
* a fixed marker so secrets never appear in --plan output.
|
|
1685
|
-
*/
|
|
1686
|
-
function redactedUserPayload(payload) {
|
|
1687
|
-
if (payload["password"] === undefined)
|
|
1688
|
-
return payload;
|
|
1689
|
-
return { ...payload, password: "<omitted>", };
|
|
1690
|
-
}
|
|
1691
|
-
/**
|
|
1692
|
-
* Plan-safe view of a connection create/update body: the connection-type
|
|
1693
|
-
* specific `params` object may carry credentials (passwords, keys, tokens), so
|
|
1694
|
-
* the plan carries only the marker, never the raw params.
|
|
1695
|
-
*/
|
|
1696
|
-
function redactedConnectionPayload(payload) {
|
|
1697
|
-
if (payload["params"] === undefined)
|
|
1698
|
-
return payload;
|
|
1699
|
-
return { ...payload, params: "<omitted; may include credentials>", };
|
|
1700
|
-
}
|
|
1701
|
-
/** Required CSV flag for plans (comma-separated logins list). */
|
|
1702
|
-
function requiredPlanCsv(flags, name, usage) {
|
|
1703
|
-
const raw = requiredPlanFlag(flags, name, usage);
|
|
1704
|
-
return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1705
|
-
}
|
|
1706
|
-
function requiredPlanRepositoryUrl(flags, flag, usage) {
|
|
1707
|
-
const url = requiredPlanFlag(flags, flag, usage);
|
|
1708
|
-
return validatedPlanRepositoryUrl(url, flag, usage);
|
|
1709
|
-
}
|
|
1710
|
-
/** Wait procedure advertised for the library calls that return a job id. */
|
|
1711
|
-
function projectGitFutureWait() {
|
|
1712
|
-
return {
|
|
1713
|
-
when: "after-dispatch",
|
|
1714
|
-
endpoint: `${PROJECT_GIT_API_ROOT}/futures/{jobId}?peek=false`,
|
|
1715
|
-
description: "Poll the returned job id until the future reports a result; never abort it.",
|
|
1716
|
-
};
|
|
1717
|
-
}
|
|
1718
|
-
function jobBuildPayload(target, projectKey, flags) {
|
|
1719
|
-
const targetType = jobBuildTargetTypeFromFlags(flags);
|
|
1720
|
-
const partition = flags["partition"];
|
|
1721
|
-
const output = { projectKey, id: target, type: targetType, };
|
|
1722
|
-
if (targetType === "DATASET") {
|
|
1723
|
-
if (partition !== undefined)
|
|
1724
|
-
output.partition = partition;
|
|
1725
|
-
}
|
|
1726
|
-
else {
|
|
1727
|
-
output.targetManagedFolderProjectKey = projectKey;
|
|
1728
|
-
output.targetManagedFolder = target;
|
|
1729
|
-
output.targetPartition = partition ?? "NP";
|
|
1730
|
-
}
|
|
1731
|
-
const payload = {
|
|
1732
|
-
outputs: [output,],
|
|
1733
|
-
type: flags["build-mode"] ?? "NON_RECURSIVE_FORCED_BUILD",
|
|
1734
|
-
};
|
|
1735
|
-
if (flags["force-rebuild"] === true && targetType === "DATASET") {
|
|
1736
|
-
payload.autoUpdateSchemaBeforeEachRecipeRun = true;
|
|
1737
|
-
}
|
|
1738
|
-
return payload;
|
|
1739
|
-
}
|
|
1740
|
-
function uploadPayload(filePath) {
|
|
1741
|
-
return {
|
|
1742
|
-
contentType: "multipart/form-data",
|
|
1743
|
-
fileField: "file",
|
|
1744
|
-
filePath,
|
|
1745
|
-
fileName: path.basename(filePath),
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
export function commandPlanShape(resource, action, args, flags, entry, projectKey) {
|
|
1749
|
-
const projectEndpoint = (suffix) => {
|
|
1750
|
-
if (!projectKey)
|
|
1751
|
-
throw new UsageError(`Missing project key for ${resource} ${action}.`);
|
|
1752
|
-
return encodedProjectEndpointForPlan(projectKey, suffix);
|
|
1753
|
-
};
|
|
1754
|
-
const id = args[0];
|
|
1755
|
-
const codeEnvEndpoint = (suffix = "") => `/public/api/admin/code-envs/${encodeURIComponent(codeEnvLang(args[0], entry.usage))}/${encodeURIComponent(args[1])}${suffix}`;
|
|
1756
|
-
const statisticsWorksheetsEndpoint = (datasetName) => projectEndpoint(`/datasets/${encodeURIComponent(datasetName)}/statistics/worksheets/`);
|
|
1757
|
-
const statisticsWorksheetEndpoint = (datasetName, worksheetId) => `${statisticsWorksheetsEndpoint(datasetName)}${encodeURIComponent(worksheetId)}`;
|
|
1758
|
-
switch (`${resource}.${action}`) {
|
|
1759
|
-
case "sql.query": {
|
|
1760
|
-
const payload = resolveSqlQueryInvocation(args, flags, planProjectKeyFromArgs(flags));
|
|
1761
|
-
return {
|
|
1762
|
-
method: "POST",
|
|
1763
|
-
endpoint: "/public/api/sql/queries/",
|
|
1764
|
-
identifiers: {
|
|
1765
|
-
connection: payload.connection,
|
|
1766
|
-
dataset: payload.datasetFullName,
|
|
1767
|
-
},
|
|
1768
|
-
payload,
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
case "ml-task.train":
|
|
1772
|
-
return {
|
|
1773
|
-
method: "POST",
|
|
1774
|
-
endpoint: projectEndpoint(`/models/lab/${encodeURIComponent(args[0])}/${encodeURIComponent(args[1])}/train`),
|
|
1775
|
-
identifiers: { analysisId: args[0], mlTaskId: args[1], },
|
|
1776
|
-
payload: {
|
|
1777
|
-
sessionName: flags["session-name"],
|
|
1778
|
-
runQueue: false,
|
|
1779
|
-
},
|
|
1780
|
-
wait: flags["wait"] === true,
|
|
1781
|
-
};
|
|
1782
|
-
case "wiki.create": {
|
|
1783
|
-
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
1784
|
-
const content = textInput(flags);
|
|
1785
|
-
const create = {
|
|
1786
|
-
method: "POST",
|
|
1787
|
-
endpoint: projectEndpoint("/wiki/"),
|
|
1788
|
-
payload: { projectKey, name, parent: flags["parent"] ?? null, },
|
|
1789
|
-
};
|
|
1790
|
-
return {
|
|
1791
|
-
...create,
|
|
1792
|
-
identifiers: { name, },
|
|
1793
|
-
// Content is not accepted by the create endpoint; it lands in a follow-up update.
|
|
1794
|
-
...(content === undefined ? {} : {
|
|
1795
|
-
requests: [
|
|
1796
|
-
{ sequence: 1, ...create, },
|
|
1797
|
-
{ sequence: 2, method: "GET", endpoint: projectEndpoint("/wiki/{createdArticleId}"), },
|
|
1798
|
-
{
|
|
1799
|
-
sequence: 3,
|
|
1800
|
-
method: "PUT",
|
|
1801
|
-
endpoint: projectEndpoint("/wiki/{createdArticleId}"),
|
|
1802
|
-
payload: { payload: content, },
|
|
1803
|
-
},
|
|
1804
|
-
],
|
|
1805
|
-
}),
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
case "wiki.update":
|
|
1809
|
-
return {
|
|
1810
|
-
method: "PUT",
|
|
1811
|
-
endpoint: projectEndpoint(`/wiki/${encodeURIComponent(id)}`),
|
|
1812
|
-
identifiers: { article: id, },
|
|
1813
|
-
payload: {
|
|
1814
|
-
...jsonInput(flags),
|
|
1815
|
-
name: flags["name"],
|
|
1816
|
-
content: textInput(flags),
|
|
1817
|
-
},
|
|
1818
|
-
};
|
|
1819
|
-
case "wiki.delete":
|
|
1820
|
-
return {
|
|
1821
|
-
method: "DELETE",
|
|
1822
|
-
endpoint: projectEndpoint(`/wiki/${encodeURIComponent(id)}`),
|
|
1823
|
-
identifiers: { article: id, },
|
|
1824
|
-
};
|
|
1825
|
-
case "dashboard.create": {
|
|
1826
|
-
const data = jsonInput(flags);
|
|
1827
|
-
const flagName = flags["name"];
|
|
1828
|
-
const dataName = data?.["name"];
|
|
1829
|
-
const name = flagName ?? (typeof dataName === "string" ? dataName : undefined);
|
|
1830
|
-
if (!name) {
|
|
1831
|
-
throw new UsageError(`--name or dashboard settings containing a string name are required. Usage: ${commandUsage("dashboard", "create")}`);
|
|
1832
|
-
}
|
|
1833
|
-
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
1834
|
-
const payload = { ...(data ?? { pages: [], }), name, };
|
|
1835
|
-
if (listed !== undefined)
|
|
1836
|
-
payload.listed = listed;
|
|
1837
|
-
return {
|
|
1838
|
-
method: "POST",
|
|
1839
|
-
endpoint: projectEndpoint("/dashboards/"),
|
|
1840
|
-
identifiers: { name, },
|
|
1841
|
-
payload,
|
|
1842
|
-
};
|
|
1843
|
-
}
|
|
1844
|
-
case "dashboard.update": {
|
|
1845
|
-
const payload = { ...jsonInput(flags), };
|
|
1846
|
-
if (typeof flags["name"] === "string")
|
|
1847
|
-
payload.name = flags["name"];
|
|
1848
|
-
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
1849
|
-
if (listed !== undefined)
|
|
1850
|
-
payload.listed = listed;
|
|
1851
|
-
return {
|
|
1852
|
-
method: "PUT",
|
|
1853
|
-
endpoint: projectEndpoint(`/dashboards/${encodeURIComponent(id)}/`),
|
|
1854
|
-
identifiers: { id, },
|
|
1855
|
-
payload,
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
case "dashboard.delete":
|
|
1859
|
-
return {
|
|
1860
|
-
method: "DELETE",
|
|
1861
|
-
endpoint: projectEndpoint(`/dashboards/${encodeURIComponent(id)}/`),
|
|
1862
|
-
identifiers: { id, },
|
|
1863
|
-
};
|
|
1864
|
-
case "insight.create": {
|
|
1865
|
-
const data = jsonInput(flags);
|
|
1866
|
-
const name = flags["name"];
|
|
1867
|
-
const type = flags["type"];
|
|
1868
|
-
if (!data && (!name || !type)) {
|
|
1869
|
-
throw new UsageError(`--data or both --name and --type are required. Usage: ${commandUsage("insight", "create")}`);
|
|
1870
|
-
}
|
|
1871
|
-
const prototype = { ...data, };
|
|
1872
|
-
if (name !== undefined)
|
|
1873
|
-
prototype.name = name;
|
|
1874
|
-
if (type !== undefined)
|
|
1875
|
-
prototype.type = type;
|
|
1876
|
-
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
1877
|
-
if (listed !== undefined)
|
|
1878
|
-
prototype.listed = listed;
|
|
1879
|
-
const params = optionalJsonFlag(flags, "params");
|
|
1880
|
-
if (params !== undefined)
|
|
1881
|
-
prototype.params = params;
|
|
1882
|
-
return {
|
|
1883
|
-
method: "POST",
|
|
1884
|
-
endpoint: projectEndpoint("/insights/"),
|
|
1885
|
-
identifiers: { name, type, },
|
|
1886
|
-
payload: {
|
|
1887
|
-
insightPrototype: prototype,
|
|
1888
|
-
contentType: flags["content-type"],
|
|
1889
|
-
payload: textInput(flags),
|
|
1890
|
-
},
|
|
1891
|
-
};
|
|
1892
|
-
}
|
|
1893
|
-
case "insight.update":
|
|
1894
|
-
return {
|
|
1895
|
-
method: "POST",
|
|
1896
|
-
endpoint: projectEndpoint(`/insights/${encodeURIComponent(id)}/`),
|
|
1897
|
-
identifiers: { id, },
|
|
1898
|
-
payload: {
|
|
1899
|
-
insight: {
|
|
1900
|
-
...jsonInput(flags),
|
|
1901
|
-
name: flags["name"],
|
|
1902
|
-
listed: parseBooleanOption(flags["listed"], "--listed"),
|
|
1903
|
-
params: optionalJsonFlag(flags, "params"),
|
|
1904
|
-
},
|
|
1905
|
-
contentType: flags["content-type"],
|
|
1906
|
-
payload: textInput(flags),
|
|
1907
|
-
},
|
|
1908
|
-
};
|
|
1909
|
-
case "insight.delete":
|
|
1910
|
-
return {
|
|
1911
|
-
method: "DELETE",
|
|
1912
|
-
endpoint: projectEndpoint(`/insights/${encodeURIComponent(id)}/`),
|
|
1913
|
-
identifiers: { id, },
|
|
1914
|
-
};
|
|
1915
|
-
case "data-quality.create-rule":
|
|
1916
|
-
return {
|
|
1917
|
-
method: "POST",
|
|
1918
|
-
endpoint: dataQualityEndpoint(projectKey, args[0], "/rules"),
|
|
1919
|
-
identifiers: { dataset: args[0], },
|
|
1920
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1921
|
-
};
|
|
1922
|
-
case "data-quality.update-rule":
|
|
1923
|
-
return {
|
|
1924
|
-
method: "PUT",
|
|
1925
|
-
endpoint: dataQualityEndpoint(projectKey, args[0], `/rules/${encodeURIComponent(args[1])}`),
|
|
1926
|
-
identifiers: { dataset: args[0], ruleId: args[1], },
|
|
1927
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1928
|
-
};
|
|
1929
|
-
case "data-quality.delete-rule":
|
|
1930
|
-
return {
|
|
1931
|
-
method: "DELETE",
|
|
1932
|
-
endpoint: dataQualityEndpoint(projectKey, args[0], `/rules/${encodeURIComponent(args[1])}${querySuffix({ ruleId: args[1], })}`),
|
|
1933
|
-
identifiers: { dataset: args[0], ruleId: args[1], },
|
|
1934
|
-
};
|
|
1935
|
-
case "data-quality.compute":
|
|
1936
|
-
return {
|
|
1937
|
-
method: "POST",
|
|
1938
|
-
endpoint: dataQualityEndpoint(projectKey, args[0], `/actions/compute-rules${querySuffix({
|
|
1939
|
-
partition: flags["partition"] ?? "NP",
|
|
1940
|
-
ruleId: flags["rule-id"],
|
|
1941
|
-
})}`),
|
|
1942
|
-
identifiers: { dataset: args[0], ruleId: flags["rule-id"], },
|
|
1943
|
-
wait: flags["wait"] === true,
|
|
1944
|
-
};
|
|
1945
|
-
case "future.abort":
|
|
1946
|
-
return {
|
|
1947
|
-
method: "DELETE",
|
|
1948
|
-
endpoint: `/public/api/futures/${encodeURIComponent(id)}`,
|
|
1949
|
-
identifiers: { id, },
|
|
1950
|
-
};
|
|
1951
|
-
case "flow-zone.create": {
|
|
1952
|
-
const name = flowZoneName(flags["name"]);
|
|
1953
|
-
const payload = { name, color: flowZoneColor(flags["color"]) ?? "#2ab1ac", };
|
|
1954
|
-
return {
|
|
1955
|
-
method: "POST",
|
|
1956
|
-
endpoint: projectEndpoint("/flow/zones"),
|
|
1957
|
-
identifiers: { name, },
|
|
1958
|
-
payload,
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
case "flow-zone.update":
|
|
1962
|
-
return {
|
|
1963
|
-
method: "PUT",
|
|
1964
|
-
endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}`),
|
|
1965
|
-
identifiers: { id, },
|
|
1966
|
-
payload: {
|
|
1967
|
-
name: typeof flags["name"] === "string" ? flowZoneName(flags["name"]) : undefined,
|
|
1968
|
-
color: flowZoneColor(flags["color"]),
|
|
1969
|
-
projectKey,
|
|
1970
|
-
},
|
|
1971
|
-
};
|
|
1972
|
-
case "flow-zone.delete":
|
|
1973
|
-
return {
|
|
1974
|
-
method: "DELETE",
|
|
1975
|
-
endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}`),
|
|
1976
|
-
identifiers: { id, },
|
|
1977
|
-
};
|
|
1978
|
-
case "flow-zone.move":
|
|
1979
|
-
return {
|
|
1980
|
-
method: "POST",
|
|
1981
|
-
endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}/add-items`),
|
|
1982
|
-
identifiers: { id, },
|
|
1983
|
-
payload: flowZoneMoveItems(flags),
|
|
1984
|
-
};
|
|
1985
|
-
case "dataset.create": {
|
|
1986
|
-
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
1987
|
-
const connection = flags["connection"];
|
|
1988
|
-
const dsType = requiredPlanFlag(flags, "type", entry.usage);
|
|
1989
|
-
if (!connection && dsType.toLowerCase() !== "uploadedfiles") {
|
|
1990
|
-
throw new UsageError("--connection is required unless --type is UploadedFiles.");
|
|
1991
|
-
}
|
|
1992
|
-
const endpoint = projectEndpoint("/datasets/"); // throws without a project key
|
|
1993
|
-
return {
|
|
1994
|
-
method: "POST",
|
|
1995
|
-
endpoint,
|
|
1996
|
-
identifiers: { name, },
|
|
1997
|
-
payload: buildDatasetCreateBody({
|
|
1998
|
-
projectKey: projectKey,
|
|
1999
|
-
datasetName: name,
|
|
2000
|
-
connection,
|
|
2001
|
-
dsType,
|
|
2002
|
-
}),
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
case "dataset.clone": {
|
|
2006
|
-
const source = args[0];
|
|
2007
|
-
const target = args[1];
|
|
2008
|
-
return {
|
|
2009
|
-
exact: false,
|
|
2010
|
-
reason: "Apply GETs the source dataset and copies its current connection, format, and schema into the new dataset body.",
|
|
2011
|
-
method: "POST",
|
|
2012
|
-
endpoint: projectEndpoint("/datasets/"),
|
|
2013
|
-
identifiers: { source, target, },
|
|
2014
|
-
payload: {
|
|
2015
|
-
sourceDataset: source,
|
|
2016
|
-
targetDataset: target,
|
|
2017
|
-
path: flags["path"],
|
|
2018
|
-
table: flags["table"],
|
|
2019
|
-
metastoreTableName: flags["metastore-table"],
|
|
2020
|
-
allowSamePath: flags["allow-same-path"] === true,
|
|
2021
|
-
projectKey,
|
|
2022
|
-
},
|
|
2023
|
-
};
|
|
2024
|
-
}
|
|
2025
|
-
case "dataset.rename":
|
|
2026
|
-
return {
|
|
2027
|
-
method: "POST",
|
|
2028
|
-
endpoint: projectEndpoint("/actions/renameDataset"),
|
|
2029
|
-
identifiers: { oldName: args[0], newName: args[1], },
|
|
2030
|
-
payload: { oldName: args[0], newName: args[1], },
|
|
2031
|
-
};
|
|
2032
|
-
case "dataset.upload-file": {
|
|
2033
|
-
const fileName = requiredPlanFlag(flags, "file-name", entry.usage);
|
|
2034
|
-
const datasetEndpoint = projectEndpoint(`/datasets/${encodeURIComponent(args[0])}`);
|
|
2035
|
-
const filesEndpoint = `${datasetEndpoint}/uploaded/files`;
|
|
2036
|
-
const upload = {
|
|
2037
|
-
method: "POST",
|
|
2038
|
-
endpoint: filesEndpoint,
|
|
2039
|
-
payload: { ...uploadPayload(args[1]), fileName, },
|
|
2040
|
-
};
|
|
2041
|
-
return {
|
|
2042
|
-
...upload,
|
|
2043
|
-
identifiers: {
|
|
2044
|
-
datasetName: args[0],
|
|
2045
|
-
localPath: args[1],
|
|
2046
|
-
fileName,
|
|
2047
|
-
},
|
|
2048
|
-
requests: [
|
|
2049
|
-
{ sequence: 1, method: "GET", endpoint: datasetEndpoint, },
|
|
2050
|
-
{ sequence: 2, method: "GET", endpoint: filesEndpoint, },
|
|
2051
|
-
{ sequence: 3, ...upload, },
|
|
2052
|
-
{
|
|
2053
|
-
sequence: 4,
|
|
2054
|
-
method: "GET",
|
|
2055
|
-
endpoint: filesEndpoint,
|
|
2056
|
-
assert: {
|
|
2057
|
-
filename: fileName,
|
|
2058
|
-
length: "local file byte length",
|
|
2059
|
-
},
|
|
2060
|
-
},
|
|
2061
|
-
],
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
case "dataset.delete":
|
|
2065
|
-
return {
|
|
2066
|
-
method: "DELETE",
|
|
2067
|
-
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}`),
|
|
2068
|
-
identifiers: { name: id, },
|
|
2069
|
-
};
|
|
2070
|
-
case "dataset.refresh-schema": {
|
|
2071
|
-
const columns = schemaColumnsInput(flags, entry.usage);
|
|
2072
|
-
return {
|
|
2073
|
-
method: "PUT",
|
|
2074
|
-
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/schema`),
|
|
2075
|
-
identifiers: { name: id, },
|
|
2076
|
-
payload: { columns, },
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2079
|
-
case "dataset.update":
|
|
2080
|
-
return {
|
|
2081
|
-
method: "PUT",
|
|
2082
|
-
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}`),
|
|
2083
|
-
identifiers: { name: id, },
|
|
2084
|
-
};
|
|
2085
|
-
case "dataset.metadata-set":
|
|
2086
|
-
return {
|
|
2087
|
-
method: "PUT",
|
|
2088
|
-
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/metadata`),
|
|
2089
|
-
identifiers: { name: id, },
|
|
2090
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2091
|
-
};
|
|
2092
|
-
case "dataset.create-managed": {
|
|
2093
|
-
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
2094
|
-
const connection = requiredPlanFlag(flags, "connection", entry.usage);
|
|
2095
|
-
const creationSettings = { connectionId: connection, };
|
|
2096
|
-
const specificSettings = {};
|
|
2097
|
-
if (typeof flags["type-option-id"] === "string") {
|
|
2098
|
-
creationSettings.typeOptionId = flags["type-option-id"];
|
|
2099
|
-
}
|
|
2100
|
-
if (typeof flags["format-option-id"] === "string") {
|
|
2101
|
-
specificSettings.formatOptionId = flags["format-option-id"];
|
|
2102
|
-
}
|
|
2103
|
-
if (typeof flags["copy-partitioning-from"] === "string") {
|
|
2104
|
-
specificSettings.partitioningOptionId = `copy:${flags["partitioning-folder"] === true ? "folder" : "dataset"}:${flags["copy-partitioning-from"]}`;
|
|
2105
|
-
}
|
|
2106
|
-
if (Object.keys(specificSettings).length > 0) {
|
|
2107
|
-
creationSettings.specificSettings = specificSettings;
|
|
2108
|
-
}
|
|
2109
|
-
return {
|
|
2110
|
-
method: "POST",
|
|
2111
|
-
endpoint: projectEndpoint("/datasets/managed"),
|
|
2112
|
-
identifiers: { name, },
|
|
2113
|
-
payload: { name, creationSettings, },
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
case "recipe.add-input":
|
|
2117
|
-
case "recipe.remove-input": {
|
|
2118
|
-
const role = flags["role"] ?? "main";
|
|
2119
|
-
return {
|
|
2120
|
-
method: "PUT",
|
|
2121
|
-
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(args[0])}`),
|
|
2122
|
-
identifiers: { recipe: args[0], dataset: args[1], role, },
|
|
2123
|
-
payload: {
|
|
2124
|
-
operation: action === "add-input" ? "append" : "remove",
|
|
2125
|
-
dataset: args[1],
|
|
2126
|
-
role,
|
|
2127
|
-
projectKey,
|
|
2128
|
-
},
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
case "recipe.clone": {
|
|
2132
|
-
const positionalSource = args[0];
|
|
2133
|
-
const fromFlag = typeof flags["from"] === "string" ? flags["from"].trim() : "";
|
|
2134
|
-
const source = positionalSource ?? fromFlag;
|
|
2135
|
-
if (!source) {
|
|
2136
|
-
throw new UsageError(`Source recipe is required. Usage: ${entry.usage}`, "missing_required_flag");
|
|
2137
|
-
}
|
|
2138
|
-
if (positionalSource && fromFlag && positionalSource !== fromFlag) {
|
|
2139
|
-
throw new UsageError("Positional source and --from must match when both are provided.", "invalid_enum");
|
|
2140
|
-
}
|
|
2141
|
-
const toFlag = typeof flags["to"] === "string" ? flags["to"].trim() : "";
|
|
2142
|
-
const nameFlag = typeof flags["name"] === "string" ? flags["name"].trim() : "";
|
|
2143
|
-
const target = toFlag || nameFlag;
|
|
2144
|
-
if (!target) {
|
|
2145
|
-
throw new UsageError(`--name or --to is required. Usage: ${entry.usage}`, "missing_required_flag");
|
|
2146
|
-
}
|
|
2147
|
-
return {
|
|
2148
|
-
method: "POST",
|
|
2149
|
-
endpoint: projectEndpoint("/recipes/"),
|
|
2150
|
-
identifiers: { source, target, },
|
|
2151
|
-
payload: {
|
|
2152
|
-
sourceRecipe: source,
|
|
2153
|
-
targetRecipe: target,
|
|
2154
|
-
inputRewrites: rewritePairsFromFlags(flags, "replace-input"),
|
|
2155
|
-
outputRewrites: rewritePairsFromFlags(flags, "replace-output"),
|
|
2156
|
-
payloadTextRewrites: rewritePairsFromFlags(flags, "replace-payload-text"),
|
|
2157
|
-
outputDataset: flags["output"],
|
|
2158
|
-
copyOutputSettings: flags["copy-output-settings"] === true,
|
|
2159
|
-
outputPath: flags["path"],
|
|
2160
|
-
metastoreTableName: flags["metastore-table"],
|
|
2161
|
-
projectKey,
|
|
2162
|
-
},
|
|
2163
|
-
};
|
|
2164
|
-
}
|
|
2165
|
-
case "recipe.delete":
|
|
2166
|
-
return {
|
|
2167
|
-
method: "DELETE",
|
|
2168
|
-
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
|
|
2169
|
-
identifiers: { name: id, },
|
|
2170
|
-
};
|
|
2171
|
-
case "recipe.create": {
|
|
2172
|
-
requiredPlanFlag(flags, "type", entry.usage);
|
|
2173
|
-
const outputDataset = flags["output"];
|
|
2174
|
-
const outputFolder = flags["output-folder"];
|
|
2175
|
-
if (outputDataset && outputFolder) {
|
|
2176
|
-
throw new UsageError("--output and --output-folder are mutually exclusive.");
|
|
2177
|
-
}
|
|
2178
|
-
if (!outputDataset && !outputFolder) {
|
|
2179
|
-
throw new UsageError("--output or --output-folder is required.");
|
|
2180
|
-
}
|
|
2181
|
-
if (outputFolder && !flags["output-connection"]) {
|
|
2182
|
-
throw new UsageError("--output-connection is required when using --output-folder.");
|
|
2183
|
-
}
|
|
2184
|
-
const endpoint = projectEndpoint("/recipes/"); // throws without a project key
|
|
2185
|
-
// Same flag mapping and body construction as the handler and RecipesResource.create.
|
|
2186
|
-
const { recipePrototype, creationSettings, } = buildRecipeCreateRequest(recipeCreateOptionsFromFlags(flags), projectKey);
|
|
2187
|
-
return {
|
|
2188
|
-
method: "POST",
|
|
2189
|
-
endpoint,
|
|
2190
|
-
identifiers: { name: recipePrototype.name, },
|
|
2191
|
-
payload: { recipePrototype, creationSettings, },
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
case "recipe.run":
|
|
2195
|
-
return {
|
|
2196
|
-
exact: false,
|
|
2197
|
-
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.",
|
|
2198
|
-
method: "POST",
|
|
2199
|
-
endpoint: projectEndpoint("/jobs/"),
|
|
2200
|
-
identifiers: { recipe: id, },
|
|
2201
|
-
wait: recipeRunShouldWait(flags),
|
|
2202
|
-
};
|
|
2203
|
-
case "code.run": {
|
|
2204
|
-
const { script, source, } = resolveCodeInputWithSource(args, flags);
|
|
2205
|
-
const sourceSha256 = sha256Hex(script);
|
|
2206
|
-
const scenarioBase = projectEndpoint("/scenarios/{generatedScenarioId}");
|
|
2207
|
-
const envName = flags["env"];
|
|
2208
|
-
const keepScenario = flags["keep"] === true;
|
|
2209
|
-
const timeoutMs = parseCodeRunIntegerFlag(flags["timeout"], "--timeout") ?? 120_000;
|
|
2210
|
-
const maxLogBytes = flags["full-log"] === true
|
|
2211
|
-
? 0
|
|
2212
|
-
: parseCodeRunIntegerFlag(flags["max-log-bytes"], "--max-log-bytes") ?? 1_048_576;
|
|
2213
|
-
return {
|
|
2214
|
-
method: "POST",
|
|
2215
|
-
endpoint: projectEndpoint("/scenarios/"),
|
|
2216
|
-
identifiers: {
|
|
2217
|
-
source,
|
|
2218
|
-
sourceSha256,
|
|
2219
|
-
sourceBytes: Buffer.byteLength(script),
|
|
2220
|
-
},
|
|
2221
|
-
requests: [
|
|
2222
|
-
{
|
|
2223
|
-
method: "POST",
|
|
2224
|
-
endpoint: projectEndpoint("/scenarios/"),
|
|
2225
|
-
payload: {
|
|
2226
|
-
id: "{generatedScenarioId}",
|
|
2227
|
-
name: "dss code run ({generatedScenarioId})",
|
|
2228
|
-
projectKey,
|
|
2229
|
-
type: "custom_python",
|
|
2230
|
-
params: {
|
|
2231
|
-
envSelection: envName
|
|
2232
|
-
? { envMode: "EXPLICIT_ENV", envName, }
|
|
2233
|
-
: { envMode: "INHERIT", },
|
|
2234
|
-
},
|
|
2235
|
-
},
|
|
2236
|
-
},
|
|
2237
|
-
{
|
|
2238
|
-
method: "PUT",
|
|
2239
|
-
endpoint: `${scenarioBase}/payload`,
|
|
2240
|
-
payload: { extension: "py", script: "<omitted>", },
|
|
2241
|
-
redactedFields: ["payload.script",],
|
|
2242
|
-
},
|
|
2243
|
-
{ method: "POST", endpoint: `${scenarioBase}/run/`, payload: {}, },
|
|
2244
|
-
{
|
|
2245
|
-
method: "GET",
|
|
2246
|
-
endpoint: `${scenarioBase}/get-run-for-trigger?triggerId={triggerId}&triggerRunId={triggerRunId}`,
|
|
2247
|
-
repeat: "until scenarioRun.result.outcome or timeout",
|
|
2248
|
-
},
|
|
2249
|
-
{
|
|
2250
|
-
method: "GET",
|
|
2251
|
-
endpoint: `${scenarioBase}/{runId}/log`,
|
|
2252
|
-
boundedBytes: maxLogBytes,
|
|
2253
|
-
},
|
|
2254
|
-
...keepScenario ? [] : [{ method: "DELETE", endpoint: scenarioBase, cleanup: true, },],
|
|
2255
|
-
],
|
|
2256
|
-
wait: {
|
|
2257
|
-
timeoutMs,
|
|
2258
|
-
pollEndpoint: `${scenarioBase}/get-run-for-trigger`,
|
|
2259
|
-
},
|
|
2260
|
-
};
|
|
2261
|
-
}
|
|
2262
|
-
case "recipe.update":
|
|
2263
|
-
return {
|
|
2264
|
-
method: "PUT",
|
|
2265
|
-
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
|
|
2266
|
-
identifiers: { name: id, },
|
|
2267
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2268
|
-
};
|
|
2269
|
-
case "recipe.set-payload": {
|
|
2270
|
-
const file = requiredPlanFlag(flags, "file", entry.usage);
|
|
2271
|
-
const backupDir = flags["no-backup"] === true
|
|
2272
|
-
? undefined
|
|
2273
|
-
: flags["backup-dir"]
|
|
2274
|
-
?? path.join(process.cwd(), ".dss-backups", "recipes");
|
|
2275
|
-
const backupPath = backupDir ? recipeBackupPath(id, backupDir) : undefined;
|
|
2276
|
-
return {
|
|
2277
|
-
method: "PUT",
|
|
2278
|
-
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
|
|
2279
|
-
identifiers: { name: id, },
|
|
2280
|
-
payload: {
|
|
2281
|
-
file,
|
|
2282
|
-
content: textInput(flags),
|
|
2283
|
-
...(backupPath ? { backupPath, } : {}),
|
|
2284
|
-
},
|
|
2285
|
-
...(backupPath
|
|
2286
|
-
? { localWrites: [{ path: backupPath, source: "remote recipe backup", before: "PUT", },], }
|
|
2287
|
-
: {}),
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
case "job.build":
|
|
2291
|
-
case "job.build-and-wait":
|
|
2292
|
-
return {
|
|
2293
|
-
method: "POST",
|
|
2294
|
-
endpoint: projectEndpoint("/jobs/"),
|
|
2295
|
-
identifiers: { target: id, },
|
|
2296
|
-
payload: jobBuildPayload(id, projectKey, flags),
|
|
2297
|
-
wait: action === "build-and-wait" || flags["wait"] === true,
|
|
2298
|
-
};
|
|
2299
|
-
case "scenario.abort":
|
|
2300
|
-
return {
|
|
2301
|
-
method: "POST",
|
|
2302
|
-
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/abort`),
|
|
2303
|
-
identifiers: { id, },
|
|
2304
|
-
};
|
|
2305
|
-
case "scenario.payload-set":
|
|
2306
|
-
return {
|
|
2307
|
-
method: "PUT",
|
|
2308
|
-
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/payload`),
|
|
2309
|
-
identifiers: { id, },
|
|
2310
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2311
|
-
};
|
|
2312
|
-
case "scenario.active-set": {
|
|
2313
|
-
const activeRaw = args[1];
|
|
2314
|
-
if (activeRaw !== "true" && activeRaw !== "false") {
|
|
2315
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2316
|
-
}
|
|
2317
|
-
const lightEndpoint = projectEndpoint(`/scenarios/${encodeURIComponent(id)}/light`);
|
|
2318
|
-
const active = activeRaw === "true";
|
|
2319
|
-
// DSS parses the light PUT as a full Scenario, so the command echoes the
|
|
2320
|
-
// current light status with `active` overridden (see ScenariosResource.setActive).
|
|
2321
|
-
return {
|
|
2322
|
-
method: "PUT",
|
|
2323
|
-
endpoint: lightEndpoint,
|
|
2324
|
-
identifiers: { id, },
|
|
2325
|
-
payload: { active, },
|
|
2326
|
-
requests: [
|
|
2327
|
-
{ sequence: 1, method: "GET", endpoint: lightEndpoint, },
|
|
2328
|
-
{
|
|
2329
|
-
sequence: 2,
|
|
2330
|
-
method: "PUT",
|
|
2331
|
-
endpoint: lightEndpoint,
|
|
2332
|
-
payload: { "...current": true, active, },
|
|
2333
|
-
},
|
|
2334
|
-
{ sequence: 3, method: "GET", endpoint: lightEndpoint, },
|
|
2335
|
-
],
|
|
2336
|
-
};
|
|
2337
|
-
}
|
|
2338
|
-
case "folder.create": {
|
|
2339
|
-
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
2340
|
-
const type = flags["type"];
|
|
2341
|
-
const connection = flags["connection"];
|
|
2342
|
-
const pathFlag = flags["path"];
|
|
2343
|
-
const body = {
|
|
2344
|
-
name,
|
|
2345
|
-
projectKey,
|
|
2346
|
-
type: type ?? null,
|
|
2347
|
-
params: { connection, path: pathFlag?.trim() || "/${projectKey}/${odbId}", },
|
|
2348
|
-
};
|
|
2349
|
-
// Mirrors FoldersResource.create.
|
|
2350
|
-
if (connection === undefined) {
|
|
2351
|
-
return {
|
|
2352
|
-
exact: false,
|
|
2353
|
-
reason: "Without --connection, apply reads DSS admin settings to pick the managed-folder connection (falling back to filesystem_folders); params.connection is set then.",
|
|
2354
|
-
method: "POST",
|
|
2355
|
-
endpoint: projectEndpoint("/managedfolders/"),
|
|
2356
|
-
identifiers: { name, },
|
|
2357
|
-
payload: body,
|
|
2358
|
-
};
|
|
2359
|
-
}
|
|
2360
|
-
return {
|
|
2361
|
-
method: "POST",
|
|
2362
|
-
endpoint: projectEndpoint("/managedfolders/"),
|
|
2363
|
-
identifiers: { name, },
|
|
2364
|
-
payload: body,
|
|
2365
|
-
};
|
|
2366
|
-
}
|
|
2367
|
-
case "job.abort":
|
|
2368
|
-
return {
|
|
2369
|
-
method: "POST",
|
|
2370
|
-
endpoint: projectEndpoint(`/jobs/${encodeURIComponent(id)}/abort/`),
|
|
2371
|
-
identifiers: { id, },
|
|
2372
|
-
};
|
|
2373
|
-
case "scenario.run":
|
|
2374
|
-
case "scenario.run-and-wait":
|
|
2375
|
-
return {
|
|
2376
|
-
method: "POST",
|
|
2377
|
-
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/run/`),
|
|
2378
|
-
identifiers: { id, },
|
|
2379
|
-
payload: {},
|
|
2380
|
-
wait: action === "run-and-wait" || flags["wait"] === true,
|
|
2381
|
-
};
|
|
2382
|
-
case "scenario.delete":
|
|
2383
|
-
return {
|
|
2384
|
-
method: "DELETE",
|
|
2385
|
-
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/`),
|
|
2386
|
-
identifiers: { id, },
|
|
2387
|
-
};
|
|
2388
|
-
case "scenario.create": {
|
|
2389
|
-
const type = flags["type"] ?? "step_based";
|
|
2390
|
-
return {
|
|
2391
|
-
method: "POST",
|
|
2392
|
-
endpoint: projectEndpoint("/scenarios/"),
|
|
2393
|
-
identifiers: { id: args[0], name: args[1], },
|
|
2394
|
-
// Mirrors ScenariosResource.create's default params for step-based scenarios.
|
|
2395
|
-
payload: {
|
|
2396
|
-
id: args[0],
|
|
2397
|
-
name: args[1],
|
|
2398
|
-
projectKey,
|
|
2399
|
-
type,
|
|
2400
|
-
params: type === "step_based" ? { steps: [], triggers: [], reporters: [], } : {},
|
|
2401
|
-
},
|
|
2402
|
-
};
|
|
2403
|
-
}
|
|
2404
|
-
case "scenario.update":
|
|
2405
|
-
return {
|
|
2406
|
-
method: "PUT",
|
|
2407
|
-
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/`),
|
|
2408
|
-
identifiers: { id, },
|
|
2409
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2410
|
-
};
|
|
2411
|
-
case "folder.update":
|
|
2412
|
-
return {
|
|
2413
|
-
method: "PUT",
|
|
2414
|
-
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(id)}`),
|
|
2415
|
-
identifiers: { folder: id, },
|
|
2416
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2417
|
-
};
|
|
2418
|
-
case "folder.delete":
|
|
2419
|
-
return {
|
|
2420
|
-
method: "DELETE",
|
|
2421
|
-
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(id)}`),
|
|
2422
|
-
identifiers: { folder: id, },
|
|
2423
|
-
};
|
|
2424
|
-
case "folder.upload":
|
|
2425
|
-
return {
|
|
2426
|
-
method: "POST",
|
|
2427
|
-
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(args[0])}/contents/${encodeURIComponent(args[1])}`),
|
|
2428
|
-
identifiers: { folder: args[0], path: args[1], localPath: args[2], },
|
|
2429
|
-
};
|
|
2430
|
-
case "folder.delete-file":
|
|
2431
|
-
return {
|
|
2432
|
-
method: "DELETE",
|
|
2433
|
-
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(args[0])}/contents/${encodeURIComponent(args[1])}`),
|
|
2434
|
-
identifiers: { folder: args[0], path: args[1], },
|
|
2435
|
-
};
|
|
2436
|
-
case "variable.set":
|
|
2437
|
-
return {
|
|
2438
|
-
exact: false,
|
|
2439
|
-
reason: "The payload lists the requested changes; apply PUTs the full {standard, local} object (merged with the current variables unless --replace).",
|
|
2440
|
-
method: "PUT",
|
|
2441
|
-
endpoint: projectEndpoint("/variables/"),
|
|
2442
|
-
payload: {
|
|
2443
|
-
standard: optionalJsonFlag(flags, "standard"),
|
|
2444
|
-
local: optionalJsonFlag(flags, "local"),
|
|
2445
|
-
replace: flags["replace"] === true,
|
|
2446
|
-
},
|
|
2447
|
-
};
|
|
2448
|
-
case "project-deployer.create-project": {
|
|
2449
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2450
|
-
return {
|
|
2451
|
-
method: "POST",
|
|
2452
|
-
endpoint: "/public/api/project-deployer/projects",
|
|
2453
|
-
identifiers: { projectKey: payload.projectKey, id: payload.id, },
|
|
2454
|
-
payload,
|
|
2455
|
-
};
|
|
2456
|
-
}
|
|
2457
|
-
case "project-deployer.upload-bundle":
|
|
2458
|
-
return {
|
|
2459
|
-
method: "POST",
|
|
2460
|
-
endpoint: "/public/api/project-deployer/projects/bundles",
|
|
2461
|
-
identifiers: { filePath: id, },
|
|
2462
|
-
payload: uploadPayload(id),
|
|
2463
|
-
};
|
|
2464
|
-
case "project-deployer.create-deployment": {
|
|
2465
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2466
|
-
return {
|
|
2467
|
-
method: "POST",
|
|
2468
|
-
endpoint: "/public/api/project-deployer/deployments",
|
|
2469
|
-
identifiers: { deploymentId: payload.deploymentId ?? payload.id, },
|
|
2470
|
-
payload,
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
case "project-deployer.save-deployment-settings":
|
|
2474
|
-
return {
|
|
2475
|
-
method: "PUT",
|
|
2476
|
-
endpoint: `/public/api/project-deployer/deployments/${encodeURIComponent(id)}/settings`,
|
|
2477
|
-
identifiers: { deploymentId: id, },
|
|
2478
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2479
|
-
};
|
|
2480
|
-
case "project-deployer.deploy":
|
|
2481
|
-
return {
|
|
2482
|
-
method: "POST",
|
|
2483
|
-
endpoint: `/public/api/project-deployer/deployments/${encodeURIComponent(id)}/actions/update`,
|
|
2484
|
-
identifiers: { deploymentId: id, },
|
|
2485
|
-
payload: {},
|
|
2486
|
-
};
|
|
2487
|
-
case "project-deployer.delete-deployment":
|
|
2488
|
-
return {
|
|
2489
|
-
method: "DELETE",
|
|
2490
|
-
endpoint: `/public/api/project-deployer/deployments/${encodeURIComponent(id)}`,
|
|
2491
|
-
identifiers: { deploymentId: id, },
|
|
2492
|
-
};
|
|
2493
|
-
case "project-deployer.create-infra": {
|
|
2494
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2495
|
-
return {
|
|
2496
|
-
method: "POST",
|
|
2497
|
-
endpoint: "/public/api/project-deployer/infras",
|
|
2498
|
-
identifiers: { infraId: payload.id, },
|
|
2499
|
-
payload,
|
|
2500
|
-
};
|
|
2501
|
-
}
|
|
2502
|
-
case "api-deployer.create-infra": {
|
|
2503
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2504
|
-
return {
|
|
2505
|
-
method: "POST",
|
|
2506
|
-
endpoint: "/public/api/api-deployer/infras",
|
|
2507
|
-
identifiers: { infraId: payload.id, },
|
|
2508
|
-
payload,
|
|
2509
|
-
};
|
|
2510
|
-
}
|
|
2511
|
-
case "api-deployer.delete-infra":
|
|
2512
|
-
return {
|
|
2513
|
-
method: "DELETE",
|
|
2514
|
-
endpoint: `/public/api/api-deployer/infras/${encodeURIComponent(id)}`,
|
|
2515
|
-
identifiers: { infraId: id, },
|
|
2516
|
-
};
|
|
2517
|
-
case "api-deployer.create-service": {
|
|
2518
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2519
|
-
return {
|
|
2520
|
-
method: "POST",
|
|
2521
|
-
endpoint: "/public/api/api-deployer/services",
|
|
2522
|
-
identifiers: { serviceId: payload.id ?? payload.publishedServiceId, },
|
|
2523
|
-
payload,
|
|
2524
|
-
};
|
|
2525
|
-
}
|
|
2526
|
-
case "api-deployer.delete-service":
|
|
2527
|
-
return {
|
|
2528
|
-
method: "DELETE",
|
|
2529
|
-
endpoint: `/public/api/api-deployer/services/${encodeURIComponent(id)}`,
|
|
2530
|
-
identifiers: { serviceId: id, },
|
|
2531
|
-
};
|
|
2532
|
-
case "api-deployer.publish-version":
|
|
2533
|
-
return {
|
|
2534
|
-
method: "POST",
|
|
2535
|
-
endpoint: `/public/api/api-deployer/services/${encodeURIComponent(args[0])}/versions`,
|
|
2536
|
-
identifiers: { serviceId: args[0], filePath: args[1], },
|
|
2537
|
-
payload: uploadPayload(args[1]),
|
|
2538
|
-
};
|
|
2539
|
-
case "api-deployer.delete-version":
|
|
2540
|
-
return {
|
|
2541
|
-
method: "DELETE",
|
|
2542
|
-
endpoint: `/public/api/api-deployer/services/${encodeURIComponent(args[0])}/versions/${encodeURIComponent(args[1])}`,
|
|
2543
|
-
identifiers: { serviceId: args[0], version: args[1], },
|
|
2544
|
-
};
|
|
2545
|
-
case "api-deployer.create-deployment": {
|
|
2546
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2547
|
-
return {
|
|
2548
|
-
method: "POST",
|
|
2549
|
-
endpoint: "/public/api/api-deployer/deployments",
|
|
2550
|
-
identifiers: { deploymentId: payload.deploymentId ?? payload.id, },
|
|
2551
|
-
payload,
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
case "api-deployer.save-deployment-settings":
|
|
2555
|
-
return {
|
|
2556
|
-
method: "PUT",
|
|
2557
|
-
endpoint: `/public/api/api-deployer/deployments/${encodeURIComponent(id)}/settings`,
|
|
2558
|
-
identifiers: { deploymentId: id, },
|
|
2559
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2560
|
-
};
|
|
2561
|
-
case "api-deployer.deploy":
|
|
2562
|
-
return {
|
|
2563
|
-
method: "POST",
|
|
2564
|
-
endpoint: `/public/api/api-deployer/deployments/${encodeURIComponent(id)}/actions/update`,
|
|
2565
|
-
identifiers: { deploymentId: id, },
|
|
2566
|
-
payload: {},
|
|
2567
|
-
};
|
|
2568
|
-
case "api-deployer.delete-deployment":
|
|
2569
|
-
return {
|
|
2570
|
-
method: "DELETE",
|
|
2571
|
-
endpoint: `/public/api/api-deployer/deployments/${encodeURIComponent(id)}`,
|
|
2572
|
-
identifiers: { deploymentId: id, },
|
|
2573
|
-
};
|
|
2574
|
-
case "workspace.create": {
|
|
2575
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2576
|
-
return {
|
|
2577
|
-
method: "POST",
|
|
2578
|
-
endpoint: "/public/api/workspaces/",
|
|
2579
|
-
identifiers: { workspaceKey: payload.workspaceKey, },
|
|
2580
|
-
payload,
|
|
2581
|
-
};
|
|
2582
|
-
}
|
|
2583
|
-
case "workspace.update-settings":
|
|
2584
|
-
return {
|
|
2585
|
-
method: "PUT",
|
|
2586
|
-
endpoint: `/public/api/workspaces/${encodeURIComponent(id)}`,
|
|
2587
|
-
identifiers: { workspaceKey: id, },
|
|
2588
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2589
|
-
};
|
|
2590
|
-
case "workspace.delete":
|
|
2591
|
-
return {
|
|
2592
|
-
method: "DELETE",
|
|
2593
|
-
endpoint: `/public/api/workspaces/${encodeURIComponent(id)}`,
|
|
2594
|
-
identifiers: { workspaceKey: id, },
|
|
2595
|
-
};
|
|
2596
|
-
case "workspace.add-object":
|
|
2597
|
-
return {
|
|
2598
|
-
method: "POST",
|
|
2599
|
-
endpoint: `/public/api/workspaces/${encodeURIComponent(id)}/objects`,
|
|
2600
|
-
identifiers: { workspaceKey: id, },
|
|
2601
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2602
|
-
};
|
|
2603
|
-
case "meaning.create": {
|
|
2604
|
-
const body = jsonInput(flags) ?? {};
|
|
2605
|
-
const payload = {
|
|
2606
|
-
...body,
|
|
2607
|
-
id: args[0],
|
|
2608
|
-
label: args[1],
|
|
2609
|
-
type: args[2],
|
|
2610
|
-
description: body.description ?? null,
|
|
2611
|
-
entries: body.entries ?? null,
|
|
2612
|
-
mappings: body.mappings ?? null,
|
|
2613
|
-
pattern: body.pattern ?? null,
|
|
2614
|
-
normalizationMode: body.normalizationMode ?? null,
|
|
2615
|
-
detectable: body.detectable ?? false,
|
|
2616
|
-
};
|
|
2617
|
-
return {
|
|
2618
|
-
method: "POST",
|
|
2619
|
-
endpoint: "/public/api/meanings/",
|
|
2620
|
-
identifiers: { id: args[0], },
|
|
2621
|
-
payload,
|
|
2622
|
-
};
|
|
2623
|
-
}
|
|
2624
|
-
case "meaning.update":
|
|
2625
|
-
return {
|
|
2626
|
-
method: "PUT",
|
|
2627
|
-
endpoint: `/public/api/meanings/${encodeURIComponent(id)}`,
|
|
2628
|
-
identifiers: { id, },
|
|
2629
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2630
|
-
};
|
|
2631
|
-
case "meaning.delete":
|
|
2632
|
-
return {
|
|
2633
|
-
method: "DELETE",
|
|
2634
|
-
endpoint: `/public/api/meanings/${encodeURIComponent(id)}`,
|
|
2635
|
-
identifiers: { id, },
|
|
2636
|
-
};
|
|
2637
|
-
case "llm.list": {
|
|
2638
|
-
const params = new URLSearchParams();
|
|
2639
|
-
const purpose = flags["purpose"];
|
|
2640
|
-
if (typeof purpose === "string" && purpose.trim() !== "") {
|
|
2641
|
-
params.set("purpose", purpose.trim());
|
|
2642
|
-
}
|
|
2643
|
-
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
2644
|
-
return {
|
|
2645
|
-
method: "GET",
|
|
2646
|
-
endpoint: projectEndpoint(`/llms/${query}`),
|
|
2647
|
-
};
|
|
2648
|
-
}
|
|
2649
|
-
case "llm.completions":
|
|
2650
|
-
case "llm.embeddings": {
|
|
2651
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2652
|
-
const llmId = stringField(payload, ["llmId",]);
|
|
2653
|
-
if (llmId === undefined || llmId.trim() === "") {
|
|
2654
|
-
throw new UsageError(`llmId is required and must be a non-empty string (e.g. "openai:openai1:gpt-4").\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
2655
|
-
}
|
|
2656
|
-
const queries = payload["queries"];
|
|
2657
|
-
if (!Array.isArray(queries) || queries.length === 0) {
|
|
2658
|
-
throw new UsageError(`queries is required and must be a non-empty array of query objects.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
2659
|
-
}
|
|
2660
|
-
return {
|
|
2661
|
-
method: "POST",
|
|
2662
|
-
endpoint: projectEndpoint(action === "completions" ? "/llms/completions" : "/llms/embeddings"),
|
|
2663
|
-
identifiers: { llmId, },
|
|
2664
|
-
payload,
|
|
2665
|
-
};
|
|
2666
|
-
}
|
|
2667
|
-
case "knowledge-bank.search": {
|
|
2668
|
-
if (typeof id !== "string" || id.trim() === "") {
|
|
2669
|
-
throw new UsageError(`knowledgeBankId must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
2670
|
-
}
|
|
2671
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2672
|
-
const query = stringField(payload, ["query",]);
|
|
2673
|
-
if (query === undefined || query.trim() === "") {
|
|
2674
|
-
throw new UsageError(`query is required and must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
2675
|
-
}
|
|
2676
|
-
return {
|
|
2677
|
-
method: "POST",
|
|
2678
|
-
endpoint: projectEndpoint(`/knowledge-banks/${encodeURIComponent(id)}/search`),
|
|
2679
|
-
identifiers: { knowledgeBankId: id, },
|
|
2680
|
-
payload,
|
|
2681
|
-
};
|
|
2682
|
-
}
|
|
2683
|
-
case "knowledge-bank.clear":
|
|
2684
|
-
if (typeof id !== "string" || id.trim() === "") {
|
|
2685
|
-
throw new UsageError(`knowledgeBankId must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
2686
|
-
}
|
|
2687
|
-
return {
|
|
2688
|
-
method: "POST",
|
|
2689
|
-
endpoint: projectEndpoint(`/knowledge-banks/${encodeURIComponent(id)}/clear`),
|
|
2690
|
-
identifiers: { knowledgeBankId: id, },
|
|
2691
|
-
};
|
|
2692
|
-
case "data-collection.create":
|
|
2693
|
-
return {
|
|
2694
|
-
method: "POST",
|
|
2695
|
-
endpoint: "/public/api/data-collections/",
|
|
2696
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2697
|
-
};
|
|
2698
|
-
case "data-collection.settings-set":
|
|
2699
|
-
return {
|
|
2700
|
-
method: "PUT",
|
|
2701
|
-
endpoint: `/public/api/data-collections/${encodeURIComponent(id)}`,
|
|
2702
|
-
identifiers: { dataCollectionId: id, },
|
|
2703
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2704
|
-
};
|
|
2705
|
-
case "data-collection.delete":
|
|
2706
|
-
return {
|
|
2707
|
-
method: "DELETE",
|
|
2708
|
-
endpoint: `/public/api/data-collections/${encodeURIComponent(id)}`,
|
|
2709
|
-
identifiers: { dataCollectionId: id, },
|
|
2710
|
-
};
|
|
2711
|
-
case "data-collection.add-object": {
|
|
2712
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2713
|
-
const cid = id
|
|
2714
|
-
?? (typeof payload["dataCollectionId"] === "string"
|
|
2715
|
-
? payload["dataCollectionId"]
|
|
2716
|
-
: undefined);
|
|
2717
|
-
if (!cid)
|
|
2718
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2719
|
-
const reference = { ...payload, };
|
|
2720
|
-
delete reference["dataCollectionId"];
|
|
2721
|
-
return {
|
|
2722
|
-
method: "POST",
|
|
2723
|
-
endpoint: `/public/api/data-collections/${encodeURIComponent(cid)}/objects`,
|
|
2724
|
-
identifiers: { dataCollectionId: cid, },
|
|
2725
|
-
payload: reference,
|
|
2726
|
-
};
|
|
2727
|
-
}
|
|
2728
|
-
case "data-collection.remove-dataset": {
|
|
2729
|
-
const projectKeyArg = args[1];
|
|
2730
|
-
const datasetName = args[2];
|
|
2731
|
-
if (!projectKeyArg || !datasetName) {
|
|
2732
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2733
|
-
}
|
|
2734
|
-
return {
|
|
2735
|
-
method: "DELETE",
|
|
2736
|
-
endpoint: `/public/api/data-collections/${encodeURIComponent(id)}/objects/dataset/${encodeURIComponent(projectKeyArg)}/${encodeURIComponent(datasetName)}`,
|
|
2737
|
-
identifiers: { dataCollectionId: id, projectKey: projectKeyArg, datasetName, },
|
|
2738
|
-
};
|
|
2739
|
-
}
|
|
2740
|
-
case "project.tags-set":
|
|
2741
|
-
return {
|
|
2742
|
-
method: "PUT",
|
|
2743
|
-
endpoint: encodedProjectEndpointForPlan(projectKey ?? requiredPlanProjectKey(flags, entry.usage), "/tags"),
|
|
2744
|
-
identifiers: { projectKey: projectKey ?? requiredPlanProjectKey(flags, entry.usage), },
|
|
2745
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2746
|
-
};
|
|
2747
|
-
case "project.metadata-set":
|
|
2748
|
-
return {
|
|
2749
|
-
method: "PUT",
|
|
2750
|
-
endpoint: encodedProjectEndpointForPlan(projectKey ?? requiredPlanProjectKey(flags, entry.usage), "/metadata"),
|
|
2751
|
-
identifiers: { projectKey: projectKey ?? requiredPlanProjectKey(flags, entry.usage), },
|
|
2752
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2753
|
-
};
|
|
2754
|
-
case "macro.run":
|
|
2755
|
-
case "macro.run-and-wait": {
|
|
2756
|
-
// The plan payload mirrors the live POST body exactly: params and
|
|
2757
|
-
// adminParams when provided via --data, {} otherwise. Validation is
|
|
2758
|
-
// identical to the live path (JSON object shape), zero network.
|
|
2759
|
-
const data = optionalJsonFlag(flags, "data");
|
|
2760
|
-
const runPayload = {};
|
|
2761
|
-
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
2762
|
-
if (data["params"] !== undefined)
|
|
2763
|
-
runPayload["params"] = data["params"];
|
|
2764
|
-
if (data["adminParams"] !== undefined)
|
|
2765
|
-
runPayload["adminParams"] = data["adminParams"];
|
|
2766
|
-
}
|
|
2767
|
-
return {
|
|
2768
|
-
method: "POST",
|
|
2769
|
-
endpoint: projectEndpoint(`/runnables/${encodeURIComponent(id)}?wait=false`),
|
|
2770
|
-
identifiers: { id, },
|
|
2771
|
-
payload: runPayload,
|
|
2772
|
-
wait: action === "macro.run-and-wait" || flags["wait"] === true,
|
|
2773
|
-
};
|
|
2774
|
-
}
|
|
2775
|
-
case "macro.abort": {
|
|
2776
|
-
const runId = args[1];
|
|
2777
|
-
if (!runId)
|
|
2778
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2779
|
-
return {
|
|
2780
|
-
method: "POST",
|
|
2781
|
-
endpoint: projectEndpoint(`/runnables/${encodeURIComponent(id)}/abort/${encodeURIComponent(runId)}`),
|
|
2782
|
-
identifiers: { id, runId, },
|
|
2783
|
-
};
|
|
2784
|
-
}
|
|
2785
|
-
case "recipe.metadata-set":
|
|
2786
|
-
return {
|
|
2787
|
-
method: "PUT",
|
|
2788
|
-
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}/metadata`),
|
|
2789
|
-
identifiers: { recipeName: id, },
|
|
2790
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2791
|
-
};
|
|
2792
|
-
case "user.create":
|
|
2793
|
-
return {
|
|
2794
|
-
method: "POST",
|
|
2795
|
-
endpoint: "/public/api/admin/users",
|
|
2796
|
-
payload: redactedUserPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
2797
|
-
};
|
|
2798
|
-
case "group.create":
|
|
2799
|
-
return {
|
|
2800
|
-
method: "POST",
|
|
2801
|
-
endpoint: "/public/api/admin/groups",
|
|
2802
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2803
|
-
};
|
|
2804
|
-
case "saved-model.create-external": {
|
|
2805
|
-
const config = optionalJsonFlag(flags, "configuration") ?? optionalJsonFlag(flags, "data");
|
|
2806
|
-
return {
|
|
2807
|
-
method: "POST",
|
|
2808
|
-
endpoint: projectEndpoint("/savedmodels/"),
|
|
2809
|
-
identifiers: { name: id, },
|
|
2810
|
-
payload: {
|
|
2811
|
-
savedModelType: requiredPlanFlag(flags, "type", entry.usage),
|
|
2812
|
-
name: id,
|
|
2813
|
-
...(typeof flags["prediction-type"] === "string"
|
|
2814
|
-
? { predictionType: flags["prediction-type"], }
|
|
2815
|
-
: {}),
|
|
2816
|
-
...(config ? { proxyModelConfiguration: config, } : {}),
|
|
2817
|
-
},
|
|
2818
|
-
};
|
|
2819
|
-
}
|
|
2820
|
-
case "saved-model.update-settings":
|
|
2821
|
-
return {
|
|
2822
|
-
method: "PUT",
|
|
2823
|
-
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}`),
|
|
2824
|
-
identifiers: { savedModelId: id, },
|
|
2825
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2826
|
-
};
|
|
2827
|
-
case "saved-model.delete-versions": {
|
|
2828
|
-
const versionsRaw = args[1];
|
|
2829
|
-
if (typeof versionsRaw !== "string" || versionsRaw.trim() === "") {
|
|
2830
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2831
|
-
}
|
|
2832
|
-
return {
|
|
2833
|
-
method: "POST",
|
|
2834
|
-
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/actions/delete-versions`),
|
|
2835
|
-
identifiers: { savedModelId: id, },
|
|
2836
|
-
payload: {
|
|
2837
|
-
versions: versionsRaw.split(",").map((v) => v.trim()).filter((v) => v.length > 0),
|
|
2838
|
-
removeIntermediate: parseBooleanOption(flags["remove-intermediate"], "--remove-intermediate") ?? true,
|
|
2839
|
-
},
|
|
2840
|
-
};
|
|
2841
|
-
}
|
|
2842
|
-
case "saved-model.import-mlflow-version":
|
|
2843
|
-
return {
|
|
2844
|
-
method: "POST",
|
|
2845
|
-
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
|
|
2846
|
-
identifiers: { savedModelId: id, versionId: args[1], },
|
|
2847
|
-
payload: {
|
|
2848
|
-
source: { kind: "local-archive", archive: requiredPlanFlag(flags, "archive", entry.usage), },
|
|
2849
|
-
...(typeof flags["code-env"] === "string"
|
|
2850
|
-
? { codeEnvName: flags["code-env"], }
|
|
2851
|
-
: {}),
|
|
2852
|
-
// Execute always sends the query parameter with the
|
|
2853
|
-
// external-caller default NONE; the plan mirrors it.
|
|
2854
|
-
containerExecConfigName: typeof flags["container-exec-config"] === "string"
|
|
2855
|
-
? flags["container-exec-config"]
|
|
2856
|
-
: "NONE",
|
|
2857
|
-
setActive: parseBooleanOption(flags["set-active"], "--set-active") ?? true,
|
|
2858
|
-
...(flags["binary-classification-threshold"] !== undefined
|
|
2859
|
-
? {
|
|
2860
|
-
binaryClassificationThreshold: num(flags["binary-classification-threshold"], "--binary-classification-threshold"),
|
|
2861
|
-
}
|
|
2862
|
-
: {}),
|
|
2863
|
-
},
|
|
2864
|
-
};
|
|
2865
|
-
case "saved-model.import-mlflow-version-from-folder":
|
|
2866
|
-
return {
|
|
2867
|
-
exact: false,
|
|
2868
|
-
reason: "Apply sends these values as query parameters (with codeEnvName and binaryClassificationThreshold defaults) on an empty multipart body; the payload shows them structured.",
|
|
2869
|
-
method: "POST",
|
|
2870
|
-
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
|
|
2871
|
-
identifiers: { savedModelId: id, versionId: args[1], },
|
|
2872
|
-
payload: {
|
|
2873
|
-
source: {
|
|
2874
|
-
kind: "managed-folder",
|
|
2875
|
-
folderRef: requiredPlanFlag(flags, "folder", entry.usage),
|
|
2876
|
-
path: flags["path"],
|
|
2877
|
-
},
|
|
2878
|
-
...(typeof flags["code-env"] === "string" ? { codeEnvName: flags["code-env"], } : {}),
|
|
2879
|
-
// Execute always sends the query parameter with the
|
|
2880
|
-
// external-caller default NONE; the plan mirrors it.
|
|
2881
|
-
containerExecConfigName: typeof flags["container-exec-config"] === "string"
|
|
2882
|
-
? flags["container-exec-config"]
|
|
2883
|
-
: "NONE",
|
|
2884
|
-
setActive: parseBooleanOption(flags["set-active"], "--set-active") ?? true,
|
|
2885
|
-
},
|
|
2886
|
-
};
|
|
2887
|
-
case "saved-model.external-metadata-put": {
|
|
2888
|
-
// DSS requires containerExecConfigName on this endpoint; for an
|
|
2889
|
-
// external API caller it resolves as LOCAL-CONFIG -> NONE, so the
|
|
2890
|
-
// plan mirrors the NONE default unless --container-exec-config
|
|
2891
|
-
// overrides it. Same contract as execute.
|
|
2892
|
-
const containerExecConfigName = typeof flags["container-exec-config"] === "string"
|
|
2893
|
-
? flags["container-exec-config"]
|
|
2894
|
-
: "NONE";
|
|
2895
|
-
return {
|
|
2896
|
-
method: "PUT",
|
|
2897
|
-
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/metadata?containerExecConfigName=${encodeURIComponent(containerExecConfigName)}`),
|
|
2898
|
-
identifiers: { savedModelId: id, versionId: args[1], },
|
|
2899
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2900
|
-
};
|
|
2901
|
-
}
|
|
2902
|
-
case "saved-model.evaluate-version": {
|
|
2903
|
-
const payload = {
|
|
2904
|
-
datasetRef: requiredPlanFlag(flags, "dataset", entry.usage),
|
|
2905
|
-
// Execute always sends an explicit containerExecConfigName
|
|
2906
|
-
// (official external-caller semantics: LOCAL-CONFIG -> NONE);
|
|
2907
|
-
// the plan mirrors that default and any explicit override.
|
|
2908
|
-
containerExecConfigName: typeof flags["container-exec-config"] === "string"
|
|
2909
|
-
? flags["container-exec-config"]
|
|
2910
|
-
: "NONE",
|
|
2911
|
-
};
|
|
2912
|
-
// --sampling is a JSON flag value; the plan carries the parsed
|
|
2913
|
-
// object under the same key as the execute body (samplingParam).
|
|
2914
|
-
if (flags["sampling"] !== undefined) {
|
|
2915
|
-
payload.samplingParam = json(flags["sampling"], "--sampling");
|
|
2916
|
-
}
|
|
2917
|
-
return {
|
|
2918
|
-
method: "POST",
|
|
2919
|
-
endpoint: `${projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/actions/evaluate`)}?useOptimalThreshold=${parseBooleanOption(flags["use-optimal-threshold"], "--use-optimal-threshold") ?? true}&skipExpensiveReports=${parseBooleanOption(flags["skip-expensive-reports"], "--skip-expensive-reports") ?? true}`,
|
|
2920
|
-
identifiers: { savedModelId: id, versionId: args[1], },
|
|
2921
|
-
payload,
|
|
2922
|
-
};
|
|
2923
|
-
}
|
|
2924
|
-
case "saved-model.set-user-meta":
|
|
2925
|
-
return {
|
|
2926
|
-
method: "PUT",
|
|
2927
|
-
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/user-meta`),
|
|
2928
|
-
identifiers: { savedModelId: id, versionId: args[1], },
|
|
2929
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2930
|
-
};
|
|
2931
|
-
case "user.update":
|
|
2932
|
-
return {
|
|
2933
|
-
method: "PUT",
|
|
2934
|
-
endpoint: `/public/api/admin/users/${encodeURIComponent(id)}`,
|
|
2935
|
-
identifiers: { login: id, },
|
|
2936
|
-
payload: redactedUserPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
2937
|
-
};
|
|
2938
|
-
case "user.delete":
|
|
2939
|
-
case "group.delete":
|
|
2940
|
-
return {
|
|
2941
|
-
method: "DELETE",
|
|
2942
|
-
endpoint: resource === "user"
|
|
2943
|
-
? `/public/api/admin/users/${encodeURIComponent(id)}`
|
|
2944
|
-
: `/public/api/admin/groups/${encodeURIComponent(id)}`,
|
|
2945
|
-
identifiers: resource === "user" ? { login: id, } : { name: id, },
|
|
2946
|
-
};
|
|
2947
|
-
case "user.resync":
|
|
2948
|
-
return {
|
|
2949
|
-
method: "POST",
|
|
2950
|
-
endpoint: `/public/api/admin/users/${encodeURIComponent(id)}/actions/resync`,
|
|
2951
|
-
identifiers: { login: id, },
|
|
2952
|
-
};
|
|
2953
|
-
case "user.resync-multi": {
|
|
2954
|
-
const logins = requiredPlanCsv(flags, "logins", entry.usage);
|
|
2955
|
-
return {
|
|
2956
|
-
method: "POST",
|
|
2957
|
-
endpoint: "/public/api/admin/users/actions/resync-multi",
|
|
2958
|
-
identifiers: { count: logins.length, },
|
|
2959
|
-
payload: logins,
|
|
2960
|
-
};
|
|
2961
|
-
}
|
|
2962
|
-
case "user.external-users":
|
|
2963
|
-
return {
|
|
2964
|
-
method: "GET",
|
|
2965
|
-
endpoint: "/public/api/admin/users/actions/external-users",
|
|
2966
|
-
};
|
|
2967
|
-
case "user.external-groups":
|
|
2968
|
-
return {
|
|
2969
|
-
method: "GET",
|
|
2970
|
-
endpoint: "/public/api/admin/users/actions/external-groups",
|
|
2971
|
-
};
|
|
2972
|
-
case "connection.test":
|
|
2973
|
-
return {
|
|
2974
|
-
method: "GET",
|
|
2975
|
-
endpoint: `/public/api/connections/${encodeURIComponent(id)}/test`,
|
|
2976
|
-
identifiers: { connectionName: id, },
|
|
2977
|
-
};
|
|
2978
|
-
case "user.provision":
|
|
2979
|
-
return {
|
|
2980
|
-
method: "POST",
|
|
2981
|
-
endpoint: "/public/api/admin/users/actions/provision",
|
|
2982
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2983
|
-
};
|
|
2984
|
-
case "group.update":
|
|
2985
|
-
return {
|
|
2986
|
-
method: "PUT",
|
|
2987
|
-
endpoint: `/public/api/admin/groups/${encodeURIComponent(id)}`,
|
|
2988
|
-
identifiers: { name: id, },
|
|
2989
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2990
|
-
};
|
|
2991
|
-
case "connection.create":
|
|
2992
|
-
return {
|
|
2993
|
-
method: "POST",
|
|
2994
|
-
endpoint: "/public/api/admin/connections",
|
|
2995
|
-
payload: redactedConnectionPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
2996
|
-
};
|
|
2997
|
-
case "connection.update":
|
|
2998
|
-
return {
|
|
2999
|
-
method: "PUT",
|
|
3000
|
-
endpoint: `/public/api/admin/connections/${encodeURIComponent(id)}`,
|
|
3001
|
-
identifiers: { connectionName: id, },
|
|
3002
|
-
payload: redactedConnectionPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
3003
|
-
};
|
|
3004
|
-
case "connection.delete":
|
|
3005
|
-
return {
|
|
3006
|
-
method: "DELETE",
|
|
3007
|
-
endpoint: `/public/api/admin/connections/${encodeURIComponent(id)}`,
|
|
3008
|
-
identifiers: { connectionName: id, },
|
|
3009
|
-
};
|
|
3010
|
-
case "connection.prepare-import": {
|
|
3011
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
3012
|
-
if (!projectKey)
|
|
3013
|
-
throw new UsageError(`Missing project key for ${resource} ${action}.`);
|
|
3014
|
-
return {
|
|
3015
|
-
method: "POST",
|
|
3016
|
-
endpoint: encodedProjectEndpointForPlan(projectKey, "/datasets/tables-import/actions/prepare-from-keys"),
|
|
3017
|
-
identifiers: { projectKey, },
|
|
3018
|
-
payload,
|
|
3019
|
-
};
|
|
3020
|
-
}
|
|
3021
|
-
case "connection.execute-import": {
|
|
3022
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
3023
|
-
if (!projectKey)
|
|
3024
|
-
throw new UsageError(`Missing project key for ${resource} ${action}.`);
|
|
3025
|
-
return {
|
|
3026
|
-
method: "POST",
|
|
3027
|
-
endpoint: encodedProjectEndpointForPlan(projectKey, "/datasets/tables-import/actions/execute-from-candidates"),
|
|
3028
|
-
identifiers: { projectKey, },
|
|
3029
|
-
payload,
|
|
3030
|
-
};
|
|
3031
|
-
}
|
|
3032
|
-
case "plugin.install-from-zip":
|
|
3033
|
-
return {
|
|
3034
|
-
method: "POST",
|
|
3035
|
-
endpoint: "/public/api/plugins/actions/installFromZip",
|
|
3036
|
-
payload: { file: requiredPlanFlag(flags, "file", entry.usage), upload: "multipart", },
|
|
3037
|
-
};
|
|
3038
|
-
case "plugin.install-from-store":
|
|
3039
|
-
return {
|
|
3040
|
-
method: "POST",
|
|
3041
|
-
endpoint: "/public/api/plugins/actions/installFromStore",
|
|
3042
|
-
identifiers: { pluginId: id, },
|
|
3043
|
-
payload: { pluginId: id, },
|
|
3044
|
-
};
|
|
3045
|
-
case "plugin.install-from-git": {
|
|
3046
|
-
const payload = pluginGitPlanPayload(flags);
|
|
3047
|
-
return {
|
|
3048
|
-
method: "POST",
|
|
3049
|
-
endpoint: "/public/api/plugins/actions/installFromGit",
|
|
3050
|
-
payload,
|
|
3051
|
-
};
|
|
3052
|
-
}
|
|
3053
|
-
case "plugin.update-from-zip":
|
|
3054
|
-
return {
|
|
3055
|
-
method: "POST",
|
|
3056
|
-
endpoint: pluginActionEndpoint(id, "updateFromZip"),
|
|
3057
|
-
identifiers: { pluginId: id, },
|
|
3058
|
-
payload: { file: requiredPlanFlag(flags, "file", entry.usage), upload: "multipart", },
|
|
3059
|
-
};
|
|
3060
|
-
case "plugin.update-from-store":
|
|
3061
|
-
return {
|
|
3062
|
-
method: "POST",
|
|
3063
|
-
endpoint: pluginActionEndpoint(id, "updateFromStore"),
|
|
3064
|
-
identifiers: { pluginId: id, },
|
|
3065
|
-
};
|
|
3066
|
-
case "plugin.update-from-git":
|
|
3067
|
-
return {
|
|
3068
|
-
method: "POST",
|
|
3069
|
-
endpoint: pluginActionEndpoint(id, "updateFromGit"),
|
|
3070
|
-
identifiers: { pluginId: id, },
|
|
3071
|
-
payload: pluginGitPlanPayload(flags),
|
|
3072
|
-
};
|
|
3073
|
-
case "plugin.settings-set": {
|
|
3074
|
-
const scope = optionalPlanProjectScope(flags);
|
|
3075
|
-
return {
|
|
3076
|
-
method: "POST",
|
|
3077
|
-
endpoint: `${pluginRootEndpoint(id, "/settings")}${scope}`,
|
|
3078
|
-
identifiers: { pluginId: id, },
|
|
3079
|
-
payload: { configKeys: settingsConfigKeys(flags), },
|
|
3080
|
-
};
|
|
3081
|
-
}
|
|
3082
|
-
case "plugin.code-env-create": {
|
|
3083
|
-
const conda = parseBooleanOption(flags["conda"], "--conda") ?? false;
|
|
3084
|
-
return {
|
|
3085
|
-
method: "POST",
|
|
3086
|
-
endpoint: `${pluginRootEndpoint(id, "/code-env/actions/create")}`,
|
|
3087
|
-
identifiers: { pluginId: id, },
|
|
3088
|
-
payload: {
|
|
3089
|
-
deploymentMode: "PLUGIN_MANAGED",
|
|
3090
|
-
conda,
|
|
3091
|
-
pythonInterpreter: flags["python-interpreter"] ?? null,
|
|
3092
|
-
},
|
|
3093
|
-
wait: flags["wait"] === true,
|
|
3094
|
-
};
|
|
3095
|
-
}
|
|
3096
|
-
case "plugin.code-env-update":
|
|
3097
|
-
return {
|
|
3098
|
-
method: "POST",
|
|
3099
|
-
endpoint: `${pluginRootEndpoint(id, "/code-env/actions/update")}`,
|
|
3100
|
-
identifiers: { pluginId: id, },
|
|
3101
|
-
wait: flags["wait"] === true,
|
|
3102
|
-
};
|
|
3103
|
-
case "plugin.move-to-dev":
|
|
3104
|
-
return {
|
|
3105
|
-
method: "POST",
|
|
3106
|
-
endpoint: pluginActionEndpoint(id, "moveToDev"),
|
|
3107
|
-
identifiers: { pluginId: id, },
|
|
3108
|
-
};
|
|
3109
|
-
case "plugin.delete":
|
|
3110
|
-
return {
|
|
3111
|
-
method: "POST",
|
|
3112
|
-
endpoint: pluginActionEndpoint(id, "delete"),
|
|
3113
|
-
identifiers: { pluginId: id, },
|
|
3114
|
-
payload: { force: parseBooleanOption(flags["force"], "--force") ?? false, },
|
|
3115
|
-
};
|
|
3116
|
-
case "plugin.create-dev": {
|
|
3117
|
-
const creationMode = requiredPlanFlag(flags, "creation-mode", entry.usage);
|
|
3118
|
-
const needsGit = creationMode !== "EMPTY";
|
|
3119
|
-
// Mirrors PluginsResource.createDev: git fields are always present, null when unused.
|
|
3120
|
-
const payload = {
|
|
3121
|
-
pluginId: id,
|
|
3122
|
-
creationMode,
|
|
3123
|
-
gitRepository: needsGit && typeof flags["repository"] === "string"
|
|
3124
|
-
? validatedPlanRepositoryUrl(flags["repository"], "repository", "--repository URL")
|
|
3125
|
-
: null,
|
|
3126
|
-
gitCheckout: needsGit && typeof flags["checkout"] === "string" ? flags["checkout"] : null,
|
|
3127
|
-
gitSubpath: creationMode === "GIT_EXPORT" && typeof flags["path-in-repository"] === "string"
|
|
3128
|
-
? flags["path-in-repository"]
|
|
3129
|
-
: null,
|
|
3130
|
-
};
|
|
3131
|
-
return {
|
|
3132
|
-
method: "POST",
|
|
3133
|
-
endpoint: "/public/api/plugins/actions/createDev",
|
|
3134
|
-
identifiers: { pluginId: id, creationMode, },
|
|
3135
|
-
payload,
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
case "plugin.set-git-remote": {
|
|
3139
|
-
const repository = requiredPlanFlag(flags, "repository", entry.usage);
|
|
3140
|
-
return {
|
|
3141
|
-
method: "POST",
|
|
3142
|
-
endpoint: `${pluginRootEndpoint(id, "/gitRemote")}`,
|
|
3143
|
-
identifiers: { pluginId: id, },
|
|
3144
|
-
payload: { repositoryUrl: validatedPlanRepositoryUrl(repository, "repository", entry.usage), },
|
|
3145
|
-
};
|
|
3146
|
-
}
|
|
3147
|
-
case "plugin.delete-git-remote":
|
|
3148
|
-
return {
|
|
3149
|
-
method: "DELETE",
|
|
3150
|
-
endpoint: pluginRootEndpoint(id, "/gitRemote"),
|
|
3151
|
-
identifiers: { pluginId: id, },
|
|
3152
|
-
};
|
|
3153
|
-
case "plugin.push":
|
|
3154
|
-
case "plugin.pull":
|
|
3155
|
-
case "plugin.fetch":
|
|
3156
|
-
case "plugin.reset-local":
|
|
3157
|
-
case "plugin.reset-remote": {
|
|
3158
|
-
const actionMap = {
|
|
3159
|
-
"plugin.push": "push",
|
|
3160
|
-
"plugin.pull": "pullRebase",
|
|
3161
|
-
"plugin.fetch": "fetch",
|
|
3162
|
-
"plugin.reset-local": "resetToLocalHeadState",
|
|
3163
|
-
"plugin.reset-remote": "resetToRemoteHeadState",
|
|
3164
|
-
};
|
|
3165
|
-
return {
|
|
3166
|
-
method: "POST",
|
|
3167
|
-
endpoint: pluginActionEndpoint(id, actionMap[`${resource}.${action}`]),
|
|
3168
|
-
identifiers: { pluginId: id, },
|
|
3169
|
-
};
|
|
3170
|
-
}
|
|
3171
|
-
case "plugin.contents-put":
|
|
3172
|
-
return {
|
|
3173
|
-
method: "POST",
|
|
3174
|
-
endpoint: pluginContentsEndpoint(id, args[1] ?? ""),
|
|
3175
|
-
identifiers: { pluginId: id, path: args[1], },
|
|
3176
|
-
payload: { contentSource: contentSourceKind(flags), },
|
|
3177
|
-
};
|
|
3178
|
-
case "plugin.contents-delete":
|
|
3179
|
-
return {
|
|
3180
|
-
method: "DELETE",
|
|
3181
|
-
endpoint: pluginContentsEndpoint(id, args[1] ?? ""),
|
|
3182
|
-
identifiers: { pluginId: id, path: args[1], },
|
|
3183
|
-
};
|
|
3184
|
-
case "plugin.folder-add":
|
|
3185
|
-
return {
|
|
3186
|
-
method: "POST",
|
|
3187
|
-
endpoint: pluginFoldersEndpoint(id, args[1] ?? ""),
|
|
3188
|
-
identifiers: { pluginId: id, path: args[1], },
|
|
3189
|
-
};
|
|
3190
|
-
case "plugin.rename": {
|
|
3191
|
-
if (!args[2])
|
|
3192
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
3193
|
-
return {
|
|
3194
|
-
method: "POST",
|
|
3195
|
-
endpoint: `${pluginRootEndpoint(id, "/contents-actions/rename")}`,
|
|
3196
|
-
identifiers: { pluginId: id, path: args[1], },
|
|
3197
|
-
payload: { oldPath: `/${validatePluginPath(args[1] ?? "")}`, newName: args[2], },
|
|
3198
|
-
};
|
|
3199
|
-
}
|
|
3200
|
-
case "plugin.move": {
|
|
3201
|
-
const destination = args[2];
|
|
3202
|
-
if (!destination)
|
|
3203
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
3204
|
-
return {
|
|
3205
|
-
method: "POST",
|
|
3206
|
-
endpoint: `${pluginRootEndpoint(id, "/contents-actions/move")}`,
|
|
3207
|
-
identifiers: { pluginId: id, path: args[1], },
|
|
3208
|
-
payload: {
|
|
3209
|
-
oldPath: `/${validatePluginPath(args[1] ?? "")}`,
|
|
3210
|
-
newPath: validatePluginDestinationPath(destination),
|
|
3211
|
-
},
|
|
3212
|
-
};
|
|
3213
|
-
}
|
|
3214
|
-
case "business-app.save-settings":
|
|
3215
|
-
return {
|
|
3216
|
-
method: "PUT",
|
|
3217
|
-
endpoint: `/public/api/business-apps/${encodeURIComponent(id)}/settings`,
|
|
3218
|
-
identifiers: { id, },
|
|
3219
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3220
|
-
};
|
|
3221
|
-
case "business-app.create-instance":
|
|
3222
|
-
return {
|
|
3223
|
-
method: "POST",
|
|
3224
|
-
endpoint: `/public/api/business-apps/${encodeURIComponent(id)}/instances`,
|
|
3225
|
-
identifiers: { id, },
|
|
3226
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3227
|
-
};
|
|
3228
|
-
case "business-app.upgrade-instance":
|
|
3229
|
-
return {
|
|
3230
|
-
method: "POST",
|
|
3231
|
-
endpoint: `/public/api/business-apps/${encodeURIComponent(args[0])}/instances/${encodeURIComponent(args[1])}/upgrade`,
|
|
3232
|
-
identifiers: { id: args[0], projectKey: args[1], },
|
|
3233
|
-
payload: {},
|
|
3234
|
-
};
|
|
3235
|
-
case "business-app.install-from-archive":
|
|
3236
|
-
return {
|
|
3237
|
-
method: "POST",
|
|
3238
|
-
endpoint: "/public/api/business-apps/install-from-archive",
|
|
3239
|
-
identifiers: { filePath: id, },
|
|
3240
|
-
payload: uploadPayload(id),
|
|
3241
|
-
};
|
|
3242
|
-
case "app.save-instance-manifest": {
|
|
3243
|
-
const targetProjectKey = requiredPlanProjectKey(flags, entry.usage);
|
|
3244
|
-
return {
|
|
3245
|
-
method: "PUT",
|
|
3246
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/app-manifest`,
|
|
3247
|
-
identifiers: { projectKey: targetProjectKey, },
|
|
3248
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3249
|
-
};
|
|
3250
|
-
}
|
|
3251
|
-
case "app.create-instance": {
|
|
3252
|
-
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
3253
|
-
if (payload["targetProjectKey"] === undefined) {
|
|
3254
|
-
// Generated-at-apply mode: --plan never allocates or reserves a
|
|
3255
|
-
// random key. The payload stays exactly as supplied, the runtime
|
|
3256
|
-
// generates the key (and defaults the display name to it) during
|
|
3257
|
-
// apply, and no absence probe runs for a generated key, so the
|
|
3258
|
-
// plan carries no preflight requests and no fake concrete GET
|
|
3259
|
-
// path: `{targetProjectKey}` resolves at apply time.
|
|
3260
|
-
return {
|
|
3261
|
-
exact: false,
|
|
3262
|
-
reason: "targetProjectKey (and targetProjectName when omitted) are generated during apply; the sent body adds them.",
|
|
3263
|
-
method: "POST",
|
|
3264
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
3265
|
-
identifiers: {
|
|
3266
|
-
appId: id,
|
|
3267
|
-
targetProjectKeyGeneratedDuringApply: true,
|
|
3268
|
-
...(payload["targetProjectName"] === undefined
|
|
3269
|
-
? { targetProjectNameGeneratedDuringApply: true, }
|
|
3270
|
-
: {}),
|
|
3271
|
-
preflightExecuted: false,
|
|
3272
|
-
preflightWillRunDuringApply: false,
|
|
3273
|
-
preflightRequests: [],
|
|
3274
|
-
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
3275
|
-
incarnationObservationRequests: [
|
|
3276
|
-
{
|
|
3277
|
-
method: "GET",
|
|
3278
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
3279
|
-
when: flags["wait"] === true
|
|
3280
|
-
? "after-terminal-future-target"
|
|
3281
|
-
: "conditional-inline-hasResult-target",
|
|
3282
|
-
intent: "After DSS reports inline or terminal creation success for the generated key, observe creationTag for later cleanup binding.",
|
|
3283
|
-
},
|
|
3284
|
-
],
|
|
3285
|
-
note: "Generated-at-apply: a random APP_ targetProjectKey is generated client-side during apply (never at plan time); targetProjectName defaults to it when omitted. No absent-project preflight runs for a generated key.",
|
|
3286
|
-
},
|
|
3287
|
-
payload,
|
|
3288
|
-
wait: flags["wait"] === true,
|
|
3289
|
-
};
|
|
3290
|
-
}
|
|
3291
|
-
const rawTargetProjectKey = stringField(payload, ["targetProjectKey",]);
|
|
3292
|
-
if (!rawTargetProjectKey || rawTargetProjectKey.trim() === "") {
|
|
3293
|
-
throw new UsageError("Instance creation payload must include a non-empty targetProjectKey.", "validation_failed", `Usage: ${entry.usage}`);
|
|
3294
|
-
}
|
|
3295
|
-
// The runtime trims and rewrites body.targetProjectKey; the plan
|
|
3296
|
-
// advertises the normalized identifier and payload.
|
|
3297
|
-
const targetProjectKey = rawTargetProjectKey.trim();
|
|
3298
|
-
const normalizedPayload = { ...payload, targetProjectKey, };
|
|
3299
|
-
return {
|
|
3300
|
-
method: "POST",
|
|
3301
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
3302
|
-
identifiers: {
|
|
3303
|
-
appId: id,
|
|
3304
|
-
targetProjectKey,
|
|
3305
|
-
preflightExecuted: false,
|
|
3306
|
-
preflightWillRunDuringApply: true,
|
|
3307
|
-
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
3308
|
-
incarnationObservationRequests: [
|
|
3309
|
-
{
|
|
3310
|
-
method: "GET",
|
|
3311
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3312
|
-
when: flags["wait"] === true
|
|
3313
|
-
? "after-terminal-future-target"
|
|
3314
|
-
: "conditional-inline-hasResult-target",
|
|
3315
|
-
intent: "After DSS reports inline or terminal creation success for the requested key, observe creationTag for later cleanup binding.",
|
|
3316
|
-
},
|
|
3317
|
-
],
|
|
3318
|
-
preflightRequests: [
|
|
3319
|
-
{
|
|
3320
|
-
method: "GET",
|
|
3321
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3322
|
-
when: "before-create",
|
|
3323
|
-
intent: "Require the target project to be absent before creating the instance.",
|
|
3324
|
-
},
|
|
3325
|
-
{
|
|
3326
|
-
method: "GET",
|
|
3327
|
-
endpoint: "/public/api/projects/",
|
|
3328
|
-
when: "conditional",
|
|
3329
|
-
intent: "Fallback list probe issued only when the direct project GET is forbidden (403). Presence proves a collision; absence cannot prove availability.",
|
|
3330
|
-
},
|
|
3331
|
-
{
|
|
3332
|
-
method: "GET",
|
|
3333
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
3334
|
-
when: "conditional",
|
|
3335
|
-
intent: "Fallback app-instance list probe issued when the direct project GET is forbidden (403). Presence proves a collision; absence still rejects creation as unverifiable.",
|
|
3336
|
-
},
|
|
3337
|
-
],
|
|
3338
|
-
note: "Strict preflight: the instance POST runs only after the payload targetProjectKey is confirmed absent. Creation never writes into an existing or unprovable project.",
|
|
3339
|
-
},
|
|
3340
|
-
payload: normalizedPayload,
|
|
3341
|
-
wait: flags["wait"] === true,
|
|
3342
|
-
};
|
|
3343
|
-
}
|
|
3344
|
-
case "app.set-manifest-version": {
|
|
3345
|
-
const targetProjectKey = requiredPlanProjectKey(flags, entry.usage);
|
|
3346
|
-
const payloadPatch = {};
|
|
3347
|
-
const version = flags["manifest-version"];
|
|
3348
|
-
if (version !== undefined) {
|
|
3349
|
-
if (version.trim() === "") {
|
|
3350
|
-
throw new UsageError("App manifest version must be a non-empty string.", "validation_failed", `Usage: ${entry.usage}`);
|
|
3351
|
-
}
|
|
3352
|
-
payloadPatch.version = version;
|
|
3353
|
-
}
|
|
3354
|
-
const versionNotes = flags["version-notes"];
|
|
3355
|
-
if (versionNotes !== undefined)
|
|
3356
|
-
payloadPatch.versionNotes = versionNotes;
|
|
3357
|
-
if (version === undefined && versionNotes === undefined) {
|
|
3358
|
-
throw new UsageError("At least one of --manifest-version or --version-notes is required.", "usage_error", `Usage: ${entry.usage}`);
|
|
3359
|
-
}
|
|
3360
|
-
const expectHash = flags["expect-hash"];
|
|
3361
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3362
|
-
throw new UsageError("Expected manifest hash must be a 64-character SHA-256 hex digest.", "validation_failed", `Usage: ${entry.usage}`);
|
|
3363
|
-
}
|
|
3364
|
-
return {
|
|
3365
|
-
method: "PUT",
|
|
3366
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/app-manifest`,
|
|
3367
|
-
identifiers: {
|
|
3368
|
-
projectKey: targetProjectKey,
|
|
3369
|
-
payloadPatch,
|
|
3370
|
-
...(expectHash !== undefined ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3371
|
-
concurrencyControl: APP_MANIFEST_CONCURRENCY_CONTROL,
|
|
3372
|
-
staleReadCheck: expectHash === undefined
|
|
3373
|
-
? "none"
|
|
3374
|
-
: "client-side-expect-hash-compare-before-put",
|
|
3375
|
-
note: expectHash === undefined
|
|
3376
|
-
? "Unconditional PUT: no stale-read check is armed because --expect-hash was not supplied."
|
|
3377
|
-
: "The hash is compared client-side against a fresh read; the PUT itself stays unconditional, so this command can overwrite a writer that commits between that read and this PUT without detecting the lost update.",
|
|
3378
|
-
},
|
|
3379
|
-
};
|
|
3380
|
-
}
|
|
3381
|
-
case "app.create-successor-instance": {
|
|
3382
|
-
const sourceProjectKey = requiredPlanFlag(flags, "from", entry.usage);
|
|
3383
|
-
const targetProjectKey = optionalPlanFlag(flags, "to", entry.usage);
|
|
3384
|
-
if (targetProjectKey !== undefined && sourceProjectKey === targetProjectKey) {
|
|
3385
|
-
throw new UsageError("--from and --to must be different project keys.", "validation_failed", `Usage: ${entry.usage}`);
|
|
3386
|
-
}
|
|
3387
|
-
const targetProjectName = flags["name"];
|
|
3388
|
-
const copyPermissions = parseBooleanOption(flags["copy-permissions"], "--copy-permissions")
|
|
3389
|
-
?? false;
|
|
3390
|
-
if (targetProjectKey === undefined) {
|
|
3391
|
-
// Generated-at-apply mode: --plan never allocates a key and no
|
|
3392
|
-
// target exists yet, so the plan makes no target-absence claim
|
|
3393
|
-
// and advertises no target probe. The key is generated once
|
|
3394
|
-
// during apply, immediately before the single instance POST;
|
|
3395
|
-
// `{targetProjectKey}` resolves only after the terminal future
|
|
3396
|
-
// names it. Source and template gates still run before the POST.
|
|
3397
|
-
return {
|
|
3398
|
-
method: "POST",
|
|
3399
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
3400
|
-
identifiers: {
|
|
3401
|
-
appId: id,
|
|
3402
|
-
sourceProjectKey,
|
|
3403
|
-
targetProjectKeyGeneratedDuringApply: true,
|
|
3404
|
-
targetPreflight: "not-applicable-generated-key",
|
|
3405
|
-
preflightExecuted: false,
|
|
3406
|
-
preflightWillRunDuringApply: true,
|
|
3407
|
-
...(targetProjectName !== undefined
|
|
3408
|
-
? { targetProjectName, }
|
|
3409
|
-
: { targetProjectNameGeneratedDuringApply: true, }),
|
|
3410
|
-
copyPermissions,
|
|
3411
|
-
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
3412
|
-
incarnationObservationRequests: [
|
|
3413
|
-
{
|
|
3414
|
-
method: "GET",
|
|
3415
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
3416
|
-
when: "after-terminal-future-target",
|
|
3417
|
-
intent: "After the terminal future names the generated successor key, observe creationTag and bind later target checks and cleanup to that hash.",
|
|
3418
|
-
},
|
|
3419
|
-
],
|
|
3420
|
-
preflightRequests: [
|
|
3421
|
-
{
|
|
3422
|
-
method: "GET",
|
|
3423
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
3424
|
-
when: "before-create",
|
|
3425
|
-
intent: "Verify the --from project is a registered instance of the app.",
|
|
3426
|
-
},
|
|
3427
|
-
{
|
|
3428
|
-
method: "GET",
|
|
3429
|
-
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/app-manifest`,
|
|
3430
|
-
when: "before-create",
|
|
3431
|
-
intent: "Verify the --from project is an APP_INSTANCE project.",
|
|
3432
|
-
},
|
|
3433
|
-
],
|
|
3434
|
-
...(copyPermissions
|
|
3435
|
-
? {
|
|
3436
|
-
permissionConcurrencyControl: "client-side-non-atomic-stale-identity-and-hash-checks",
|
|
3437
|
-
permissionRequests: [
|
|
3438
|
-
{
|
|
3439
|
-
method: "GET",
|
|
3440
|
-
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
3441
|
-
intent: "Snapshot the predecessor instance ACL before creation.",
|
|
3442
|
-
},
|
|
3443
|
-
{
|
|
3444
|
-
method: "GET",
|
|
3445
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
|
|
3446
|
-
intent: "Read the generated successor ACL to decide whether the copy is a no-op.",
|
|
3447
|
-
},
|
|
3448
|
-
{
|
|
3449
|
-
method: "GET",
|
|
3450
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
3451
|
-
intent: "Recheck generated successor creationTag after reading its ACL; stop if the project key was reused.",
|
|
3452
|
-
},
|
|
3453
|
-
{
|
|
3454
|
-
method: "GET",
|
|
3455
|
-
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
3456
|
-
intent: "Recheck the predecessor ACL immediately before the write and stop if its hash drifted.",
|
|
3457
|
-
},
|
|
3458
|
-
{
|
|
3459
|
-
method: "GET",
|
|
3460
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
3461
|
-
intent: "Recheck generated successor creationTag immediately before the unconditional permission PUT.",
|
|
3462
|
-
},
|
|
3463
|
-
{
|
|
3464
|
-
method: "PUT",
|
|
3465
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
|
|
3466
|
-
intent: "Apply the predecessor ACL snapshot to the generated successor instance.",
|
|
3467
|
-
},
|
|
3468
|
-
{
|
|
3469
|
-
method: "GET",
|
|
3470
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
|
|
3471
|
-
intent: "Verify the generated successor ACL hash equals the predecessor snapshot hash.",
|
|
3472
|
-
},
|
|
3473
|
-
{
|
|
3474
|
-
method: "GET",
|
|
3475
|
-
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
3476
|
-
intent: "Detect generated successor project-key reuse across the permission write and verification read.",
|
|
3477
|
-
},
|
|
3478
|
-
],
|
|
3479
|
-
}
|
|
3480
|
-
: {}),
|
|
3481
|
-
note: "Generated-at-apply: the successor key is generated once during apply (never at plan time) and no absent-project preflight runs for a generated key, so the plan claims no target absence. Source and template gates run before the single instance POST, then the terminal future's own result names the successor key. The predecessor is never modified or deleted; cleanup targets the generated successor key only.",
|
|
3482
|
-
},
|
|
3483
|
-
payload: targetProjectName !== undefined ? { targetProjectName, } : {},
|
|
3484
|
-
wait: true,
|
|
3485
|
-
};
|
|
3486
|
-
}
|
|
3487
|
-
return {
|
|
3488
|
-
method: "POST",
|
|
3489
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
3490
|
-
identifiers: {
|
|
3491
|
-
appId: id,
|
|
3492
|
-
sourceProjectKey,
|
|
3493
|
-
targetProjectKey,
|
|
3494
|
-
preflightExecuted: false,
|
|
3495
|
-
preflightWillRunDuringApply: true,
|
|
3496
|
-
...(targetProjectName !== undefined ? { targetProjectName, } : {}),
|
|
3497
|
-
copyPermissions,
|
|
3498
|
-
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
3499
|
-
postFutureRequests: [
|
|
3500
|
-
{
|
|
3501
|
-
method: "GET",
|
|
3502
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3503
|
-
intent: "After the terminal future names the successor key, observe creationTag and bind later target checks and cleanup to that hash.",
|
|
3504
|
-
},
|
|
3505
|
-
],
|
|
3506
|
-
...(copyPermissions
|
|
3507
|
-
? {
|
|
3508
|
-
permissionConcurrencyControl: "client-side-non-atomic-stale-identity-and-hash-checks",
|
|
3509
|
-
}
|
|
3510
|
-
: {}),
|
|
3511
|
-
preflightRequests: [
|
|
3512
|
-
{
|
|
3513
|
-
method: "GET",
|
|
3514
|
-
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
3515
|
-
when: "before-create",
|
|
3516
|
-
intent: "Verify the --from project is a registered instance of the app.",
|
|
3517
|
-
},
|
|
3518
|
-
{
|
|
3519
|
-
method: "GET",
|
|
3520
|
-
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/app-manifest`,
|
|
3521
|
-
when: "before-create",
|
|
3522
|
-
intent: "Verify the --from project is an APP_INSTANCE project.",
|
|
3523
|
-
},
|
|
3524
|
-
{
|
|
3525
|
-
method: "GET",
|
|
3526
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3527
|
-
when: "before-create",
|
|
3528
|
-
intent: "Require the --to target project to be absent before creating the successor.",
|
|
3529
|
-
},
|
|
3530
|
-
{
|
|
3531
|
-
method: "GET",
|
|
3532
|
-
endpoint: "/public/api/projects/",
|
|
3533
|
-
when: "conditional",
|
|
3534
|
-
intent: "Fallback list probe issued only when the direct project GET is forbidden (403). Presence proves a collision; absence cannot prove availability and rejects creation.",
|
|
3535
|
-
},
|
|
3536
|
-
],
|
|
3537
|
-
...(copyPermissions
|
|
3538
|
-
? {
|
|
3539
|
-
permissionRequests: [
|
|
3540
|
-
{
|
|
3541
|
-
method: "GET",
|
|
3542
|
-
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
3543
|
-
intent: "Snapshot the predecessor instance ACL before creation.",
|
|
3544
|
-
},
|
|
3545
|
-
{
|
|
3546
|
-
method: "GET",
|
|
3547
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`,
|
|
3548
|
-
intent: "Read the successor ACL to decide whether the copy is a no-op.",
|
|
3549
|
-
},
|
|
3550
|
-
{
|
|
3551
|
-
method: "GET",
|
|
3552
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3553
|
-
intent: "Recheck successor creationTag after reading its ACL; stop if the project key was reused.",
|
|
3554
|
-
},
|
|
3555
|
-
{
|
|
3556
|
-
method: "GET",
|
|
3557
|
-
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
3558
|
-
intent: "Recheck the predecessor ACL immediately before the write and stop if its hash drifted.",
|
|
3559
|
-
},
|
|
3560
|
-
{
|
|
3561
|
-
method: "GET",
|
|
3562
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3563
|
-
intent: "Recheck successor creationTag immediately before the unconditional permission PUT.",
|
|
3564
|
-
},
|
|
3565
|
-
{
|
|
3566
|
-
method: "PUT",
|
|
3567
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`,
|
|
3568
|
-
intent: "Apply the predecessor ACL snapshot to the successor instance.",
|
|
3569
|
-
},
|
|
3570
|
-
{
|
|
3571
|
-
method: "GET",
|
|
3572
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`,
|
|
3573
|
-
intent: "Verify the successor ACL hash equals the predecessor snapshot hash.",
|
|
3574
|
-
},
|
|
3575
|
-
{
|
|
3576
|
-
method: "GET",
|
|
3577
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
3578
|
-
intent: "Detect successor project-key reuse across the permission write and verification read.",
|
|
3579
|
-
},
|
|
3580
|
-
],
|
|
3581
|
-
}
|
|
3582
|
-
: {}),
|
|
3583
|
-
note: "Additive, non-transactional: strict preflight verifies the predecessor instance and the --to target absence before the single instance POST, then waits on the DSS future. The predecessor is never modified or deleted; cleanup targets only the successor key. DSS exposes no immutable future target ID, conditional DELETE, or conditional permission PUT: later creationTag and ACL checks narrow and detect races but cannot atomically join creation provenance or serialize writes.",
|
|
3584
|
-
},
|
|
3585
|
-
payload: {
|
|
3586
|
-
targetProjectKey,
|
|
3587
|
-
targetProjectName: targetProjectName ?? targetProjectKey,
|
|
3588
|
-
},
|
|
3589
|
-
wait: true,
|
|
3590
|
-
};
|
|
3591
|
-
}
|
|
3592
|
-
case "app.delete-instance": {
|
|
3593
|
-
const targetProjectKey = requiredPlanFlag(flags, "project-key", entry.usage);
|
|
3594
|
-
const futureId = flags["future-id"] === undefined
|
|
3595
|
-
? undefined
|
|
3596
|
-
: requiredPlanFlag(flags, "future-id", entry.usage);
|
|
3597
|
-
const expectedProjectIncarnation = flags["expect-project-incarnation"] === undefined
|
|
3598
|
-
? undefined
|
|
3599
|
-
: requiredPlanFlag(flags, "expect-project-incarnation", entry.usage);
|
|
3600
|
-
if (expectedProjectIncarnation !== undefined
|
|
3601
|
-
&& !/^[0-9a-f]{64}$/.test(expectedProjectIncarnation)) {
|
|
3602
|
-
throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
|
|
3603
|
-
}
|
|
3604
|
-
const unconfirmedCreation = parseBooleanOption(flags["unconfirmed-creation"], "--unconfirmed-creation") ?? false;
|
|
3605
|
-
const manifestProbe = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/app-manifest`;
|
|
3606
|
-
const projectDetailsProbe = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`;
|
|
3607
|
-
const typeValidationRequests = [
|
|
3608
|
-
{
|
|
3609
|
-
method: "GET",
|
|
3610
|
-
endpoint: manifestProbe,
|
|
3611
|
-
when: "before-delete",
|
|
3612
|
-
intent: "Verify the target is an APP_INSTANCE project before deleting.",
|
|
3613
|
-
},
|
|
3614
|
-
{
|
|
3615
|
-
method: "GET",
|
|
3616
|
-
endpoint: projectDetailsProbe,
|
|
3617
|
-
when: expectedProjectIncarnation === undefined
|
|
3618
|
-
? "conditional-type-check-before-delete"
|
|
3619
|
-
: "incarnation-and-conditional-type-check-before-delete",
|
|
3620
|
-
intent: expectedProjectIncarnation === undefined
|
|
3621
|
-
? "Fallback probe issued only when the app-manifest response omits projectAppType (live DSS does)."
|
|
3622
|
-
: "Recompute the current project-incarnation hash from creationTag after the manifest probe; the same response supplies projectAppType when the manifest omits it.",
|
|
3623
|
-
},
|
|
3624
|
-
];
|
|
3625
|
-
const preflightRequests = typeValidationRequests;
|
|
3626
|
-
if (unconfirmedCreation) {
|
|
3627
|
-
return {
|
|
3628
|
-
identifiers: {
|
|
3629
|
-
projectKey: targetProjectKey,
|
|
3630
|
-
...(futureId !== undefined ? { futureId, } : {}),
|
|
3631
|
-
unconfirmedCreation: true,
|
|
3632
|
-
note: "Indeterminate creation without a DSS future ID: no DSS request is issued. The command reports an unresolved cleanup failure (exit 4, cleanupResolved false) without deleting, because creation may still be running.",
|
|
3633
|
-
},
|
|
3634
|
-
};
|
|
3635
|
-
}
|
|
3636
|
-
if (futureId === undefined) {
|
|
3637
|
-
return {
|
|
3638
|
-
method: "DELETE",
|
|
3639
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}`,
|
|
3640
|
-
identifiers: {
|
|
3641
|
-
projectKey: targetProjectKey,
|
|
3642
|
-
...(expectedProjectIncarnation === undefined
|
|
3643
|
-
? {}
|
|
3644
|
-
: {
|
|
3645
|
-
projectIncarnationGate: {
|
|
3646
|
-
required: false,
|
|
3647
|
-
provided: true,
|
|
3648
|
-
expectedHash: expectedProjectIncarnation,
|
|
3649
|
-
},
|
|
3650
|
-
incarnationControl: "client-side-non-atomic-stale-identity-check",
|
|
3651
|
-
}),
|
|
3652
|
-
preflightRequests,
|
|
3653
|
-
note: expectedProjectIncarnation === undefined
|
|
3654
|
-
? "Convergent direct delete: the manifest preflight rejects non-instance targets, an absent target (404) is an already-absent success issued without any DELETE, and only a verified instance target receives the project DELETE. No project-incarnation binding was requested."
|
|
3655
|
-
: "Incarnation-bound direct delete: after verifying APP_INSTANCE type, a project GET must match the expected creationTag hash before an unconditional DELETE. DSS exposes no immutable project ID or conditional DELETE, so this client-side check narrows but cannot serialize against project-key reuse after the GET.",
|
|
3656
|
-
},
|
|
3657
|
-
};
|
|
3658
|
-
}
|
|
3659
|
-
return {
|
|
3660
|
-
method: "DELETE",
|
|
3661
|
-
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}`,
|
|
3662
|
-
identifiers: {
|
|
3663
|
-
projectKey: targetProjectKey,
|
|
3664
|
-
futureId,
|
|
3665
|
-
projectIncarnationGate: {
|
|
3666
|
-
required: true,
|
|
3667
|
-
provided: expectedProjectIncarnation !== undefined,
|
|
3668
|
-
...(expectedProjectIncarnation === undefined
|
|
3669
|
-
? {}
|
|
3670
|
-
: { expectedHash: expectedProjectIncarnation, }),
|
|
3671
|
-
},
|
|
3672
|
-
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
3673
|
-
preflightRequests: [
|
|
3674
|
-
{
|
|
3675
|
-
method: "GET",
|
|
3676
|
-
endpoint: manifestProbe,
|
|
3677
|
-
when: "before-future-wait",
|
|
3678
|
-
intent: "Verify the target is an APP_INSTANCE project before the supplied future is touched, so an invalid target cannot affect it.",
|
|
3679
|
-
},
|
|
3680
|
-
{
|
|
3681
|
-
method: "GET",
|
|
3682
|
-
endpoint: projectDetailsProbe,
|
|
3683
|
-
when: "conditional-before-wait",
|
|
3684
|
-
intent: "Fallback probe issued only when the pre-wait app-manifest response omits projectAppType (live DSS does).",
|
|
3685
|
-
},
|
|
3686
|
-
],
|
|
3687
|
-
futureGate: [
|
|
3688
|
-
{
|
|
3689
|
-
method: "GET",
|
|
3690
|
-
endpoint: `/public/api/futures/${encodeURIComponent(futureId)}?peek=false`,
|
|
3691
|
-
intent: "Wait for the supplied creation future to settle or time out; never abort it, and repeat this read-only GET while it is live.",
|
|
3692
|
-
},
|
|
3693
|
-
],
|
|
3694
|
-
postFutureValidationRequests: expectedProjectIncarnation === undefined
|
|
3695
|
-
? []
|
|
3696
|
-
: typeValidationRequests,
|
|
3697
|
-
note: expectedProjectIncarnation === undefined
|
|
3698
|
-
? "The target type is verified before the future is touched. Waiting never aborts the future, but a terminal target match still cannot authorize deletion without --expect-project-incarnation; the command then exits with validation_failed and issues no DELETE."
|
|
3699
|
-
: "The target type is verified before the future is touched. The DELETE runs only after the terminal future reports the requested target, then a later project GET still matches the recorded creationTag hash and the target is re-verified as APP_INSTANCE. DSS exposes no immutable future target ID or conditional DELETE: the future target and creationTag are independent, non-atomic observations that narrow but cannot eliminate a project-key-reuse race.",
|
|
3700
|
-
},
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
case "app.permissions-restore": {
|
|
3704
|
-
const file = requiredPlanFlag(flags, "file", entry.usage);
|
|
3705
|
-
const targetProjectKey = requiredPlanProjectKey(flags, entry.usage);
|
|
3706
|
-
const permissionsEndpoint = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`;
|
|
3707
|
-
const projectDetailsEndpoint = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`;
|
|
3708
|
-
return {
|
|
3709
|
-
method: "PUT",
|
|
3710
|
-
endpoint: permissionsEndpoint,
|
|
3711
|
-
identifiers: {
|
|
3712
|
-
file,
|
|
3713
|
-
projectKey: targetProjectKey,
|
|
3714
|
-
localPreflight: [
|
|
3715
|
-
"Read and hash-verify the owner-only snapshot file.",
|
|
3716
|
-
"Require its project key and canonical DSS URL to match this invocation.",
|
|
3717
|
-
],
|
|
3718
|
-
preflightRequests: [
|
|
3719
|
-
{
|
|
3720
|
-
method: "GET",
|
|
3721
|
-
endpoint: projectDetailsEndpoint,
|
|
3722
|
-
intent: "Require the snapshot project-incarnation hash to match creationTag.",
|
|
3723
|
-
},
|
|
3724
|
-
{
|
|
3725
|
-
method: "GET",
|
|
3726
|
-
endpoint: permissionsEndpoint,
|
|
3727
|
-
intent: "Read current permissions; an equal hash makes the PUT unnecessary.",
|
|
3728
|
-
},
|
|
3729
|
-
{
|
|
3730
|
-
method: "GET",
|
|
3731
|
-
endpoint: projectDetailsEndpoint,
|
|
3732
|
-
intent: "Recheck the project incarnation after the permission read and immediately before any PUT.",
|
|
3733
|
-
},
|
|
3734
|
-
],
|
|
3735
|
-
conditionalWrite: {
|
|
3736
|
-
method: "PUT",
|
|
3737
|
-
endpoint: permissionsEndpoint,
|
|
3738
|
-
when: "permissions-differ-and-dry-run-is-false",
|
|
3739
|
-
},
|
|
3740
|
-
verificationRequests: [
|
|
3741
|
-
{
|
|
3742
|
-
method: "GET",
|
|
3743
|
-
endpoint: permissionsEndpoint,
|
|
3744
|
-
intent: "Verify DSS persisted the desired permission hash.",
|
|
3745
|
-
},
|
|
3746
|
-
{
|
|
3747
|
-
method: "GET",
|
|
3748
|
-
endpoint: projectDetailsEndpoint,
|
|
3749
|
-
intent: "Detect project-key reuse across the permission write.",
|
|
3750
|
-
},
|
|
3751
|
-
],
|
|
3752
|
-
incarnationControl: "client-side-non-atomic-stale-identity-check",
|
|
3753
|
-
note: "DSS exposes no conditional permission PUT or immutable project ID. The repeated creationTag checks narrow and detect key-reuse races but cannot serialize the check with the PUT.",
|
|
3754
|
-
},
|
|
3755
|
-
};
|
|
3756
|
-
}
|
|
3757
|
-
case "statistics.create-worksheet":
|
|
3758
|
-
return {
|
|
3759
|
-
method: "POST",
|
|
3760
|
-
endpoint: statisticsWorksheetsEndpoint(args[0]),
|
|
3761
|
-
identifiers: { dataset: args[0], },
|
|
3762
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3763
|
-
};
|
|
3764
|
-
case "statistics.update-worksheet":
|
|
3765
|
-
return {
|
|
3766
|
-
method: "PUT",
|
|
3767
|
-
endpoint: statisticsWorksheetEndpoint(args[0], args[1]),
|
|
3768
|
-
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
3769
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3770
|
-
};
|
|
3771
|
-
case "statistics.delete-worksheet":
|
|
3772
|
-
return {
|
|
3773
|
-
method: "DELETE",
|
|
3774
|
-
endpoint: statisticsWorksheetEndpoint(args[0], args[1]),
|
|
3775
|
-
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
3776
|
-
};
|
|
3777
|
-
case "statistics.run-worksheet":
|
|
3778
|
-
return {
|
|
3779
|
-
method: "POST",
|
|
3780
|
-
endpoint: `${statisticsWorksheetEndpoint(args[0], args[1])}/actions/run-card`,
|
|
3781
|
-
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
3782
|
-
};
|
|
3783
|
-
case "statistics.run-card":
|
|
3784
|
-
return {
|
|
3785
|
-
method: "POST",
|
|
3786
|
-
endpoint: `${statisticsWorksheetEndpoint(args[0], args[1])}/actions/run-card`,
|
|
3787
|
-
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
3788
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3789
|
-
};
|
|
3790
|
-
case "statistics.run-computation":
|
|
3791
|
-
return {
|
|
3792
|
-
method: "POST",
|
|
3793
|
-
endpoint: `${statisticsWorksheetEndpoint(args[0], args[1])}/actions/run-computation`,
|
|
3794
|
-
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
3795
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3796
|
-
};
|
|
3797
|
-
case "webapp.create":
|
|
3798
|
-
return {
|
|
3799
|
-
method: "POST",
|
|
3800
|
-
endpoint: projectEndpoint("/webapps/"),
|
|
3801
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3802
|
-
};
|
|
3803
|
-
case "webapp.update-settings": {
|
|
3804
|
-
const patch = requiredPlanJsonInput(flags, entry.usage);
|
|
3805
|
-
const expectHash = flags["expect-hash"];
|
|
3806
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3807
|
-
throw new UsageError("Expected webapp hash must be a 64-character SHA-256 hex digest.");
|
|
3808
|
-
}
|
|
3809
|
-
const endpoint = projectEndpoint(`/webapps/${encodeURIComponent(id)}`);
|
|
3810
|
-
return {
|
|
3811
|
-
exact: false,
|
|
3812
|
-
reason: "Apply GETs the current settings, deep-merges the patch, then PUTs the full object.",
|
|
3813
|
-
method: "PUT",
|
|
3814
|
-
endpoint,
|
|
3815
|
-
identifiers: {
|
|
3816
|
-
webappId: id,
|
|
3817
|
-
patchHash: stableHash(patch),
|
|
3818
|
-
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3819
|
-
},
|
|
3820
|
-
payload: { patch: "<omitted>", patchHash: stableHash(patch), },
|
|
3821
|
-
requests: [
|
|
3822
|
-
{ method: "GET", endpoint, purpose: "read full settings and verify expectHash", },
|
|
3823
|
-
{
|
|
3824
|
-
method: "PUT",
|
|
3825
|
-
endpoint,
|
|
3826
|
-
condition: "settings read and expected hash matched",
|
|
3827
|
-
payload: "<merged full settings>",
|
|
3828
|
-
},
|
|
3829
|
-
],
|
|
3830
|
-
};
|
|
3831
|
-
}
|
|
3832
|
-
case "webapp.stop-backend":
|
|
3833
|
-
return {
|
|
3834
|
-
method: "PUT",
|
|
3835
|
-
endpoint: projectEndpoint(`/webapps/${encodeURIComponent(id)}/backend/actions/stop`),
|
|
3836
|
-
identifiers: { webappId: id, },
|
|
3837
|
-
payload: {},
|
|
3838
|
-
};
|
|
3839
|
-
case "webapp.restart-backend": {
|
|
3840
|
-
const endpoint = projectEndpoint(`/webapps/${encodeURIComponent(id)}/backend/actions/restart`);
|
|
3841
|
-
return {
|
|
3842
|
-
method: "PUT",
|
|
3843
|
-
endpoint,
|
|
3844
|
-
identifiers: { webappId: id, },
|
|
3845
|
-
payload: {},
|
|
3846
|
-
wait: {
|
|
3847
|
-
requested: flags["wait"] === true,
|
|
3848
|
-
timeoutMs: num(flags["timeout"], "--timeout"),
|
|
3849
|
-
pollIntervalMs: num(flags["poll-interval"], "--poll-interval"),
|
|
3850
|
-
},
|
|
3851
|
-
};
|
|
3852
|
-
}
|
|
3853
|
-
case "api-service.create":
|
|
3854
|
-
return {
|
|
3855
|
-
method: "POST",
|
|
3856
|
-
endpoint: projectEndpoint(`/apiservices/${encodeURIComponent(id)}`),
|
|
3857
|
-
identifiers: { serviceId: id, },
|
|
3858
|
-
payload: {},
|
|
3859
|
-
};
|
|
3860
|
-
case "api-service.save-settings": {
|
|
3861
|
-
const settings = requiredPlanJsonInput(flags, entry.usage);
|
|
3862
|
-
const expectHash = flags["expect-hash"];
|
|
3863
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3864
|
-
throw new UsageError("Expected API service settings hash must be a 64-character SHA-256 hex digest.");
|
|
3865
|
-
}
|
|
3866
|
-
const endpoint = projectEndpoint(`/apiservices/${encodeURIComponent(id)}/settings`);
|
|
3867
|
-
const payload = {
|
|
3868
|
-
settings: "<omitted>",
|
|
3869
|
-
settingsHash: stableHash(settings),
|
|
3870
|
-
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3871
|
-
};
|
|
3872
|
-
return {
|
|
3873
|
-
method: "PUT",
|
|
3874
|
-
endpoint,
|
|
3875
|
-
identifiers: { serviceId: id, },
|
|
3876
|
-
payload,
|
|
3877
|
-
requests: [
|
|
3878
|
-
...(expectHash
|
|
3879
|
-
? [{ method: "GET", endpoint, purpose: "verify expectHash", },]
|
|
3880
|
-
: []),
|
|
3881
|
-
{
|
|
3882
|
-
method: "PUT",
|
|
3883
|
-
endpoint,
|
|
3884
|
-
condition: expectHash ? "hash matched" : "unconditional",
|
|
3885
|
-
payload,
|
|
3886
|
-
},
|
|
3887
|
-
],
|
|
3888
|
-
};
|
|
3889
|
-
}
|
|
3890
|
-
case "api-service.add-prediction-endpoint": {
|
|
3891
|
-
const endpoint = projectEndpoint(`/apiservices/${encodeURIComponent(id)}/settings`);
|
|
3892
|
-
return {
|
|
3893
|
-
exact: false,
|
|
3894
|
-
reason: "Apply GETs the full settings, appends one endpoint, then PUTs the full object.",
|
|
3895
|
-
method: "PUT",
|
|
3896
|
-
endpoint,
|
|
3897
|
-
identifiers: { serviceId: id, endpointId: args[1], savedModelId: args[2], },
|
|
3898
|
-
payload: { id: args[1], type: "STD_PREDICTION", modelRef: args[2], },
|
|
3899
|
-
requests: [
|
|
3900
|
-
{ method: "GET", endpoint, purpose: "read full settings", },
|
|
3901
|
-
{ method: "PUT", endpoint, payload: "<merged full settings>", },
|
|
3902
|
-
],
|
|
3903
|
-
};
|
|
3904
|
-
}
|
|
3905
|
-
case "api-service.create-package":
|
|
3906
|
-
case "api-service.delete-package":
|
|
3907
|
-
case "api-service.publish-package": {
|
|
3908
|
-
const serviceId = args[0];
|
|
3909
|
-
const packageId = args[1];
|
|
3910
|
-
const packageEndpoint = projectEndpoint(`/apiservices/${encodeURIComponent(serviceId)}/packages/${encodeURIComponent(packageId)}`);
|
|
3911
|
-
if (action === "delete-package") {
|
|
3912
|
-
return {
|
|
3913
|
-
method: "DELETE",
|
|
3914
|
-
endpoint: packageEndpoint,
|
|
3915
|
-
identifiers: { serviceId, packageId, },
|
|
3916
|
-
};
|
|
3917
|
-
}
|
|
3918
|
-
if (action === "publish-package") {
|
|
3919
|
-
return {
|
|
3920
|
-
method: "POST",
|
|
3921
|
-
endpoint: `${packageEndpoint}/publish${querySuffix({
|
|
3922
|
-
publishedServiceId: flags["published-service-id"],
|
|
3923
|
-
})}`,
|
|
3924
|
-
identifiers: { serviceId, packageId, },
|
|
3925
|
-
};
|
|
3926
|
-
}
|
|
3927
|
-
return {
|
|
3928
|
-
method: "POST",
|
|
3929
|
-
endpoint: packageEndpoint + querySuffix({
|
|
3930
|
-
releaseNotes: flags["release-notes"],
|
|
3931
|
-
}),
|
|
3932
|
-
identifiers: { serviceId, packageId, },
|
|
3933
|
-
};
|
|
3934
|
-
}
|
|
3935
|
-
case "bundle.export": {
|
|
3936
|
-
const evaluateProjectStandardsChecks = parseBooleanOption(flags["evaluate-standards-checks"], "--evaluate-standards-checks") ?? true;
|
|
3937
|
-
return {
|
|
3938
|
-
method: "PUT",
|
|
3939
|
-
endpoint: projectEndpoint("/bundles/exported/" + encodeURIComponent(id)) + querySuffix({
|
|
3940
|
-
releaseNotes: flags["release-notes"],
|
|
3941
|
-
evaluateProjectStandardsChecks,
|
|
3942
|
-
}),
|
|
3943
|
-
identifiers: { bundleId: id, },
|
|
3944
|
-
};
|
|
3945
|
-
}
|
|
3946
|
-
case "bundle.publish":
|
|
3947
|
-
return {
|
|
3948
|
-
method: "POST",
|
|
3949
|
-
endpoint: projectEndpoint("/bundles/" + encodeURIComponent(id) + "/publish") + querySuffix({
|
|
3950
|
-
publishedProjectKey: flags["published-project-key"],
|
|
3951
|
-
}),
|
|
3952
|
-
identifiers: { bundleId: id, },
|
|
3953
|
-
payload: {},
|
|
3954
|
-
};
|
|
3955
|
-
case "bundle.activate": {
|
|
3956
|
-
const rawScenarios = flags["scenarios"];
|
|
3957
|
-
let scenariosToEnable;
|
|
3958
|
-
if (rawScenarios !== undefined && rawScenarios !== false) {
|
|
3959
|
-
const parsed = json(rawScenarios);
|
|
3960
|
-
if (typeof parsed !== "object"
|
|
3961
|
-
|| parsed === null
|
|
3962
|
-
|| Array.isArray(parsed)
|
|
3963
|
-
|| !Object.values(parsed).every((value) => typeof value === "boolean")) {
|
|
3964
|
-
throw new UsageError("--scenarios must be a JSON object mapping scenario IDs to true|false.");
|
|
3965
|
-
}
|
|
3966
|
-
scenariosToEnable = parsed;
|
|
3967
|
-
}
|
|
3968
|
-
return {
|
|
3969
|
-
method: "POST",
|
|
3970
|
-
endpoint: projectEndpoint(`/bundles/imported/${encodeURIComponent(id)}/actions/activate`),
|
|
3971
|
-
identifiers: { bundleId: id, },
|
|
3972
|
-
payload: scenariosToEnable === undefined
|
|
3973
|
-
? {}
|
|
3974
|
-
: { scenariosActiveOnActivation: scenariosToEnable, },
|
|
3975
|
-
};
|
|
3976
|
-
}
|
|
3977
|
-
case "bundle.preload":
|
|
3978
|
-
return {
|
|
3979
|
-
method: "POST",
|
|
3980
|
-
endpoint: projectEndpoint(`/bundles/imported/${encodeURIComponent(id)}/actions/preload`),
|
|
3981
|
-
identifiers: { bundleId: id, },
|
|
3982
|
-
payload: {},
|
|
3983
|
-
};
|
|
3984
|
-
case "project-library.create-file":
|
|
3985
|
-
case "project-library.create-folder": {
|
|
3986
|
-
const libraryPath = validateLibraryPath(id);
|
|
3987
|
-
const kind = action === "create-file" ? "contents" : "folders";
|
|
3988
|
-
return {
|
|
3989
|
-
exact: false,
|
|
3990
|
-
reason: "Apply performs a live absence check before POST; --if-not-exists may stop after that read, and create never overwrites an existing item.",
|
|
3991
|
-
method: "POST",
|
|
3992
|
-
endpoint: projectEndpoint(`/libraries/${kind}/${encodeLibraryPath(libraryPath)}`),
|
|
3993
|
-
identifiers: { path: libraryPath, },
|
|
3994
|
-
};
|
|
3995
|
-
}
|
|
3996
|
-
case "project-library.put": {
|
|
3997
|
-
const libraryPath = validateLibraryPath(id);
|
|
3998
|
-
const endpoint = projectEndpoint(`/libraries/contents/${encodeLibraryPath(libraryPath)}`);
|
|
3999
|
-
const payload = projectLibraryPutPayload(flags);
|
|
4000
|
-
return {
|
|
4001
|
-
method: "POST",
|
|
4002
|
-
endpoint,
|
|
4003
|
-
identifiers: {
|
|
4004
|
-
path: libraryPath,
|
|
4005
|
-
concurrencyControl: payload.expectSha256
|
|
4006
|
-
? PROJECT_LIBRARY_CONCURRENCY_CONTROL
|
|
4007
|
-
: "none",
|
|
4008
|
-
},
|
|
4009
|
-
payload,
|
|
4010
|
-
...(payload.expectSha256
|
|
4011
|
-
? {
|
|
4012
|
-
requests: [
|
|
4013
|
-
{
|
|
4014
|
-
method: "GET",
|
|
4015
|
-
endpoint: `${endpoint}?dataEncoding=base64`,
|
|
4016
|
-
purpose: "verify expectSha256",
|
|
4017
|
-
},
|
|
4018
|
-
{ method: "POST", endpoint, condition: "hash matched", payload, },
|
|
4019
|
-
],
|
|
4020
|
-
}
|
|
4021
|
-
: {}),
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
case "project-library.delete": {
|
|
4025
|
-
const libraryPath = validateLibraryPath(id);
|
|
4026
|
-
return {
|
|
4027
|
-
method: "DELETE",
|
|
4028
|
-
endpoint: projectEndpoint(`/libraries/contents/${encodeLibraryPath(libraryPath)}`),
|
|
4029
|
-
identifiers: { path: libraryPath, },
|
|
4030
|
-
};
|
|
4031
|
-
}
|
|
4032
|
-
case "project-library.rename": {
|
|
4033
|
-
const libraryPath = validateLibraryPath(id);
|
|
4034
|
-
const newName = validateLibraryName(args[1]);
|
|
4035
|
-
return {
|
|
4036
|
-
method: "POST",
|
|
4037
|
-
endpoint: projectEndpoint("/libraries/contents-actions/rename/"),
|
|
4038
|
-
identifiers: { path: libraryPath, newName, },
|
|
4039
|
-
payload: { oldPath: `/${libraryPath}`, newName, },
|
|
4040
|
-
};
|
|
4041
|
-
}
|
|
4042
|
-
case "project-library.move": {
|
|
4043
|
-
const libraryPath = validateLibraryPath(id);
|
|
4044
|
-
const destinationFolder = validateLibraryDestinationPath(args[1]);
|
|
4045
|
-
return {
|
|
4046
|
-
method: "POST",
|
|
4047
|
-
endpoint: projectEndpoint("/libraries/contents-actions/move"),
|
|
4048
|
-
identifiers: { path: libraryPath, destinationFolder, },
|
|
4049
|
-
payload: { oldPath: `/${libraryPath}`, newPath: destinationFolder, },
|
|
4050
|
-
};
|
|
4051
|
-
}
|
|
4052
|
-
case "code-env.create": {
|
|
4053
|
-
const wait = codeEnvWait(flags);
|
|
4054
|
-
return {
|
|
4055
|
-
method: "POST",
|
|
4056
|
-
endpoint: `${codeEnvEndpoint()}?wait=${wait}`,
|
|
4057
|
-
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
4058
|
-
payload: {
|
|
4059
|
-
...codeEnvParams(flags),
|
|
4060
|
-
deploymentMode: requiredPlanFlag(flags, "deployment-mode", entry.usage),
|
|
4061
|
-
},
|
|
4062
|
-
wait,
|
|
4063
|
-
};
|
|
4064
|
-
}
|
|
4065
|
-
case "code-env.set-definition": {
|
|
4066
|
-
const definition = requiredPlanJsonInput(flags, entry.usage);
|
|
4067
|
-
const expectHash = flags["expect-hash"];
|
|
4068
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
4069
|
-
throw new UsageError("--expect-hash must be a 64-character SHA-256 hex digest.");
|
|
4070
|
-
}
|
|
4071
|
-
const endpoint = codeEnvEndpoint();
|
|
4072
|
-
const definitionHash = stableHash(definition);
|
|
4073
|
-
const normalizedExpectHash = expectHash?.toLowerCase();
|
|
4074
|
-
return {
|
|
4075
|
-
exact: false,
|
|
4076
|
-
reason: "Apply PUTs the supplied definition object unchanged; this plan omits that body and reports only its hash.",
|
|
4077
|
-
method: "PUT",
|
|
4078
|
-
endpoint,
|
|
4079
|
-
identifiers: {
|
|
4080
|
-
lang: codeEnvLang(args[0], entry.usage),
|
|
4081
|
-
name: args[1],
|
|
4082
|
-
definitionHash,
|
|
4083
|
-
...(normalizedExpectHash ? { expectHash: normalizedExpectHash, } : {}),
|
|
4084
|
-
},
|
|
4085
|
-
payload: "<omitted>",
|
|
4086
|
-
...(normalizedExpectHash
|
|
4087
|
-
? {
|
|
4088
|
-
requests: [
|
|
4089
|
-
{ method: "GET", endpoint, purpose: "verify expectHash", },
|
|
4090
|
-
{
|
|
4091
|
-
method: "PUT",
|
|
4092
|
-
endpoint,
|
|
4093
|
-
condition: "hash matched",
|
|
4094
|
-
payload: "<omitted>",
|
|
4095
|
-
redactedFields: ["payload",],
|
|
4096
|
-
},
|
|
4097
|
-
],
|
|
4098
|
-
}
|
|
4099
|
-
: {}),
|
|
4100
|
-
};
|
|
4101
|
-
}
|
|
4102
|
-
case "code-env.set-packages": {
|
|
4103
|
-
const installCorePackages = parseBooleanOption(flags["install-core-packages"], "--install-core-packages");
|
|
4104
|
-
const expectHash = flags["expect-hash"];
|
|
4105
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
4106
|
-
throw new UsageError("--expect-hash must be a 64-character SHA-256 hex digest.");
|
|
4107
|
-
}
|
|
4108
|
-
const endpoint = codeEnvEndpoint();
|
|
4109
|
-
return {
|
|
4110
|
-
exact: false,
|
|
4111
|
-
reason: "Apply GETs the full definition, merges only package fields, then PUTs the full result.",
|
|
4112
|
-
method: "PUT",
|
|
4113
|
-
endpoint,
|
|
4114
|
-
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
4115
|
-
payload: {
|
|
4116
|
-
packages: codeEnvPackageList(flags),
|
|
4117
|
-
...(installCorePackages !== undefined ? { installCorePackages, } : {}),
|
|
4118
|
-
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
4119
|
-
},
|
|
4120
|
-
requests: [
|
|
4121
|
-
{ method: "GET", endpoint, purpose: "read full definition and verify expectHash", },
|
|
4122
|
-
{
|
|
4123
|
-
method: "PUT",
|
|
4124
|
-
endpoint,
|
|
4125
|
-
condition: "definition read and hash matched",
|
|
4126
|
-
payload: "<merged full definition>",
|
|
4127
|
-
},
|
|
4128
|
-
],
|
|
4129
|
-
};
|
|
4130
|
-
}
|
|
4131
|
-
case "code-env.update-packages": {
|
|
4132
|
-
const versionToUpdate = typeof flags["env-version"] === "string"
|
|
4133
|
-
? flags["env-version"]
|
|
4134
|
-
: typeof flags["version"] === "string"
|
|
4135
|
-
? flags["version"]
|
|
4136
|
-
: undefined;
|
|
4137
|
-
const wait = codeEnvWait(flags);
|
|
4138
|
-
return {
|
|
4139
|
-
method: "POST",
|
|
4140
|
-
endpoint: `${codeEnvEndpoint("/packages")}${querySuffix({
|
|
4141
|
-
forceRebuildEnv: flags["force-rebuild"] === true,
|
|
4142
|
-
versionToUpdate,
|
|
4143
|
-
wait,
|
|
4144
|
-
})}`,
|
|
4145
|
-
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
4146
|
-
wait,
|
|
4147
|
-
};
|
|
4148
|
-
}
|
|
4149
|
-
case "code-env.update-images": {
|
|
4150
|
-
const wait = codeEnvWait(flags);
|
|
4151
|
-
return {
|
|
4152
|
-
method: "POST",
|
|
4153
|
-
endpoint: `${codeEnvEndpoint("/images")}${querySuffix({ envVersion: flags["env-version"], wait, })}`,
|
|
4154
|
-
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
4155
|
-
wait,
|
|
4156
|
-
};
|
|
4157
|
-
}
|
|
4158
|
-
case "code-env.set-jupyter": {
|
|
4159
|
-
const active = parseBooleanOption(flags["active"], "--active");
|
|
4160
|
-
if (active === undefined) {
|
|
4161
|
-
throw new UsageError(`--active is required. Usage: ${commandUsage("code-env", "set-jupyter")}`);
|
|
4162
|
-
}
|
|
4163
|
-
const wait = codeEnvWait(flags);
|
|
4164
|
-
return {
|
|
4165
|
-
method: "POST",
|
|
4166
|
-
endpoint: `${codeEnvEndpoint("/jupyter")}${querySuffix({ active, wait, })}`,
|
|
4167
|
-
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
4168
|
-
wait,
|
|
4169
|
-
};
|
|
4170
|
-
}
|
|
4171
|
-
case "code-env.delete": {
|
|
4172
|
-
const wait = codeEnvWait(flags);
|
|
4173
|
-
return {
|
|
4174
|
-
method: "DELETE",
|
|
4175
|
-
endpoint: `${codeEnvEndpoint()}?wait=${wait}`,
|
|
4176
|
-
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
4177
|
-
wait,
|
|
4178
|
-
};
|
|
4179
|
-
}
|
|
4180
|
-
case "notebook.save-jupyter": {
|
|
4181
|
-
const content = requiredPlanJsonInput(flags, entry.usage);
|
|
4182
|
-
const expectHash = flags["expect-hash"];
|
|
4183
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
4184
|
-
throw new UsageError("Expected notebook hash must be a 64-character SHA-256 hex digest.");
|
|
4185
|
-
}
|
|
4186
|
-
const endpoint = projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}`);
|
|
4187
|
-
return {
|
|
4188
|
-
exact: false,
|
|
4189
|
-
reason: "A fresh GET selects POST when absent or PUT when present; a confirming GET supplies the persisted hash.",
|
|
4190
|
-
endpoint,
|
|
4191
|
-
identifiers: {
|
|
4192
|
-
name: id,
|
|
4193
|
-
contentHash: stableHash(content),
|
|
4194
|
-
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
4195
|
-
},
|
|
4196
|
-
requests: [
|
|
4197
|
-
{ method: "GET", endpoint, purpose: "resolve create/update and check --expect-hash", },
|
|
4198
|
-
{
|
|
4199
|
-
method: "POST|PUT",
|
|
4200
|
-
endpoint,
|
|
4201
|
-
condition: "POST after not_found; otherwise PUT",
|
|
4202
|
-
payload: "<omitted>",
|
|
4203
|
-
redactedFields: ["payload",],
|
|
4204
|
-
},
|
|
4205
|
-
{ method: "GET", endpoint, purpose: "confirm persisted hash", },
|
|
4206
|
-
],
|
|
4207
|
-
};
|
|
4208
|
-
}
|
|
4209
|
-
case "notebook.delete-jupyter":
|
|
4210
|
-
return {
|
|
4211
|
-
method: "DELETE",
|
|
4212
|
-
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}`),
|
|
4213
|
-
identifiers: { name: id, },
|
|
4214
|
-
};
|
|
4215
|
-
case "notebook.clear-jupyter-outputs":
|
|
4216
|
-
return {
|
|
4217
|
-
method: "DELETE",
|
|
4218
|
-
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}/outputs`),
|
|
4219
|
-
identifiers: { name: id, },
|
|
4220
|
-
};
|
|
4221
|
-
case "notebook.unload-jupyter":
|
|
4222
|
-
if (flags["all"] === true) {
|
|
4223
|
-
if (args.length > 0)
|
|
4224
|
-
throw new UsageError(entry.usage);
|
|
4225
|
-
return {
|
|
4226
|
-
exact: false,
|
|
4227
|
-
reason: "DSS has no unload-all endpoint; apply lists active notebooks and sessions, then issues one verified session DELETE per result.",
|
|
4228
|
-
identifiers: { all: true, },
|
|
4229
|
-
requests: [
|
|
4230
|
-
{ method: "GET", endpoint: projectEndpoint("/jupyter-notebooks/?active=true"), },
|
|
4231
|
-
{
|
|
4232
|
-
method: "GET",
|
|
4233
|
-
endpoint: projectEndpoint("/jupyter-notebooks/{name}/sessions"),
|
|
4234
|
-
forEach: "active notebook",
|
|
4235
|
-
},
|
|
4236
|
-
{
|
|
4237
|
-
method: "DELETE",
|
|
4238
|
-
endpoint: projectEndpoint("/jupyter-notebooks/{name}/sessions/{sessionId}"),
|
|
4239
|
-
forEach: "listed session",
|
|
4240
|
-
},
|
|
4241
|
-
],
|
|
4242
|
-
};
|
|
4243
|
-
}
|
|
4244
|
-
requireArgs(args, 2, entry.usage);
|
|
4245
|
-
return {
|
|
4246
|
-
method: "DELETE",
|
|
4247
|
-
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(args[0])}/sessions/${encodeURIComponent(args[1])}`),
|
|
4248
|
-
identifiers: { name: args[0], sessionId: args[1], },
|
|
4249
|
-
};
|
|
4250
|
-
case "notebook.save-sql": {
|
|
4251
|
-
const content = requiredPlanJsonInput(flags, entry.usage);
|
|
4252
|
-
const expectHash = flags["expect-hash"];
|
|
4253
|
-
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
4254
|
-
throw new UsageError("Expected notebook hash must be a 64-character SHA-256 hex digest.");
|
|
4255
|
-
}
|
|
4256
|
-
const endpoint = projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}`);
|
|
4257
|
-
return {
|
|
4258
|
-
exact: false,
|
|
4259
|
-
reason: "A fresh GET selects collection POST when absent or entity PUT when present; a confirming GET supplies the persisted hash.",
|
|
4260
|
-
endpoint,
|
|
4261
|
-
identifiers: {
|
|
4262
|
-
id,
|
|
4263
|
-
contentHash: stableHash(content),
|
|
4264
|
-
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
4265
|
-
},
|
|
4266
|
-
requests: [
|
|
4267
|
-
{ method: "GET", endpoint, purpose: "resolve create/update and check --expect-hash", },
|
|
4268
|
-
{
|
|
4269
|
-
method: "POST",
|
|
4270
|
-
endpoint: projectEndpoint("/sql-notebooks/"),
|
|
4271
|
-
condition: "after not_found",
|
|
4272
|
-
payload: "<omitted; includes id and projectKey>",
|
|
4273
|
-
redactedFields: ["payload",],
|
|
4274
|
-
},
|
|
4275
|
-
{
|
|
4276
|
-
method: "PUT",
|
|
4277
|
-
endpoint,
|
|
4278
|
-
condition: "when present",
|
|
4279
|
-
payload: "<omitted>",
|
|
4280
|
-
redactedFields: ["payload",],
|
|
4281
|
-
},
|
|
4282
|
-
{ method: "GET", endpoint, purpose: "confirm persisted hash", },
|
|
4283
|
-
],
|
|
4284
|
-
};
|
|
4285
|
-
}
|
|
4286
|
-
case "notebook.delete-sql":
|
|
4287
|
-
return {
|
|
4288
|
-
method: "DELETE",
|
|
4289
|
-
endpoint: projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}`),
|
|
4290
|
-
identifiers: { id, },
|
|
4291
|
-
};
|
|
4292
|
-
case "notebook.clear-sql-history":
|
|
4293
|
-
return {
|
|
4294
|
-
method: "POST",
|
|
4295
|
-
endpoint: projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}/history/clear`),
|
|
4296
|
-
identifiers: { id, },
|
|
4297
|
-
payload: {
|
|
4298
|
-
cellId: flags["cell-id"],
|
|
4299
|
-
numRunsToRetain: num(flags["retain"], "--retain"),
|
|
4300
|
-
},
|
|
4301
|
-
};
|
|
4302
|
-
case "project.create": {
|
|
4303
|
-
const settings = jsonInput(flags) ?? null;
|
|
4304
|
-
return {
|
|
4305
|
-
method: "POST",
|
|
4306
|
-
endpoint: "/public/api/projects/",
|
|
4307
|
-
identifiers: { projectKey: args[0], name: args[1], },
|
|
4308
|
-
payload: {
|
|
4309
|
-
projectKey: args[0],
|
|
4310
|
-
name: args[1],
|
|
4311
|
-
owner: flags["owner"] ?? null,
|
|
4312
|
-
settings,
|
|
4313
|
-
description: null,
|
|
4314
|
-
permissions: [],
|
|
4315
|
-
tags: [],
|
|
4316
|
-
},
|
|
4317
|
-
};
|
|
4318
|
-
}
|
|
4319
|
-
case "project.delete": {
|
|
4320
|
-
const expectedProjectIncarnation = flags["expect-project-incarnation"] === undefined
|
|
4321
|
-
? undefined
|
|
4322
|
-
: requiredPlanFlag(flags, "expect-project-incarnation", entry.usage);
|
|
4323
|
-
if (expectedProjectIncarnation !== undefined
|
|
4324
|
-
&& !/^[0-9a-f]{64}$/.test(expectedProjectIncarnation)) {
|
|
4325
|
-
throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
|
|
4326
|
-
}
|
|
4327
|
-
const endpoint = `/public/api/projects/${encodeURIComponent(id)}${querySuffix({
|
|
4328
|
-
clearManagedDatasets: flags["drop-data"] === true,
|
|
4329
|
-
clearOutputManagedFolders: false,
|
|
4330
|
-
clearJobAndScenarioLogs: true,
|
|
4331
|
-
wait: true,
|
|
4332
|
-
})}`;
|
|
4333
|
-
const guarded = flags["if-exists"] === true
|
|
4334
|
-
|| executionMode(flags).dryRun
|
|
4335
|
-
|| expectedProjectIncarnation !== undefined;
|
|
4336
|
-
if (!guarded) {
|
|
4337
|
-
return {
|
|
4338
|
-
method: "DELETE",
|
|
4339
|
-
endpoint,
|
|
4340
|
-
identifiers: { projectKey: id, },
|
|
4341
|
-
};
|
|
4342
|
-
}
|
|
4343
|
-
const projectProbe = `/public/api/projects/${encodeURIComponent(id)}/`;
|
|
4344
|
-
const incarnationGate = expectedProjectIncarnation === undefined
|
|
4345
|
-
? { required: false, provided: false, }
|
|
4346
|
-
: {
|
|
4347
|
-
required: true,
|
|
4348
|
-
provided: true,
|
|
4349
|
-
expectedHash: expectedProjectIncarnation,
|
|
4350
|
-
};
|
|
4351
|
-
if (executionMode(flags).dryRun) {
|
|
4352
|
-
return {
|
|
4353
|
-
method: "GET",
|
|
4354
|
-
endpoint: projectProbe,
|
|
4355
|
-
identifiers: {
|
|
4356
|
-
projectKey: id,
|
|
4357
|
-
dryRun: true,
|
|
4358
|
-
ifExists: flags["if-exists"] === true,
|
|
4359
|
-
projectIncarnationGate: incarnationGate,
|
|
4360
|
-
note: "Read-only guarded-delete preflight. No DELETE is issued; a supplied creationTag hash must match.",
|
|
4361
|
-
},
|
|
4362
|
-
};
|
|
4363
|
-
}
|
|
4364
|
-
return {
|
|
4365
|
-
method: "DELETE",
|
|
4366
|
-
endpoint,
|
|
4367
|
-
identifiers: {
|
|
4368
|
-
projectKey: id,
|
|
4369
|
-
ifExists: flags["if-exists"] === true,
|
|
4370
|
-
projectIncarnationGate: incarnationGate,
|
|
4371
|
-
incarnationControl: expectedProjectIncarnation === undefined
|
|
4372
|
-
? "none"
|
|
4373
|
-
: "client-side-non-atomic-stale-identity-check",
|
|
4374
|
-
preflightRequests: [{
|
|
4375
|
-
method: "GET",
|
|
4376
|
-
endpoint: projectProbe,
|
|
4377
|
-
intent: expectedProjectIncarnation === undefined
|
|
4378
|
-
? "Confirm existence before a convergent delete."
|
|
4379
|
-
: "Recompute the current project-incarnation hash from creationTag before DELETE.",
|
|
4380
|
-
},],
|
|
4381
|
-
note: expectedProjectIncarnation === undefined
|
|
4382
|
-
? "Convergent delete: a 404 preflight is an already-absent success only with --if-exists."
|
|
4383
|
-
: "Incarnation-bound delete: the GET must match the expected creationTag hash before an unconditional DELETE. DSS exposes no conditional project DELETE, so the client-side gate cannot serialize against key reuse after the GET.",
|
|
4384
|
-
},
|
|
4385
|
-
};
|
|
4386
|
-
}
|
|
4387
|
-
case "project.duplicate": {
|
|
4388
|
-
const options = jsonInput(flags);
|
|
4389
|
-
return {
|
|
4390
|
-
method: "POST",
|
|
4391
|
-
endpoint: `/public/api/projects/${encodeURIComponent(args[0])}/duplicate/`,
|
|
4392
|
-
identifiers: { sourceKey: args[0], targetKey: args[1], targetName: args[2], },
|
|
4393
|
-
payload: {
|
|
4394
|
-
targetProjectName: args[2],
|
|
4395
|
-
targetProjectKey: args[1],
|
|
4396
|
-
duplicationMode: options?.duplicationMode ?? "MINIMAL",
|
|
4397
|
-
exportAnalysisModels: options?.exportAnalysisModels ?? true,
|
|
4398
|
-
exportSavedModels: options?.exportSavedModels ?? true,
|
|
4399
|
-
exportGitRepository: options?.exportGitRepository ?? null,
|
|
4400
|
-
exportInsightsData: options?.exportInsightsData ?? true,
|
|
4401
|
-
remapping: options?.remapping ?? {},
|
|
4402
|
-
...(options?.targetProjectFolderId !== undefined
|
|
4403
|
-
? { targetProjectFolderId: options.targetProjectFolderId, }
|
|
4404
|
-
: {}),
|
|
4405
|
-
},
|
|
4406
|
-
};
|
|
4407
|
-
}
|
|
4408
|
-
case "project.import": {
|
|
4409
|
-
const settings = jsonInput(flags) ?? {};
|
|
4410
|
-
const rawTarget = flags["target-project-key"];
|
|
4411
|
-
const targetProjectKey = rawTarget?.trim();
|
|
4412
|
-
if (rawTarget !== undefined && targetProjectKey === "") {
|
|
4413
|
-
throw new UsageError(`--target-project-key must not be empty. Usage: ${entry.usage}`);
|
|
4414
|
-
}
|
|
4415
|
-
const settingsTarget = settings.targetProjectKey;
|
|
4416
|
-
if (settingsTarget !== undefined
|
|
4417
|
-
&& (typeof settingsTarget !== "string" || settingsTarget.trim() === "")) {
|
|
4418
|
-
throw new UsageError(`targetProjectKey in import settings must be a non-empty string. Usage: ${entry.usage}`);
|
|
4419
|
-
}
|
|
4420
|
-
if (targetProjectKey !== undefined
|
|
4421
|
-
&& settingsTarget !== undefined
|
|
4422
|
-
&& targetProjectKey !== settingsTarget.trim()) {
|
|
4423
|
-
throw new UsageError(`--target-project-key conflicts with targetProjectKey in import settings. Usage: ${entry.usage}`);
|
|
4424
|
-
}
|
|
4425
|
-
const processPayload = targetProjectKey === undefined
|
|
4426
|
-
? settings
|
|
4427
|
-
: { ...settings, targetProjectKey, };
|
|
4428
|
-
const finalizedPayload = Object.keys(processPayload).length === 0
|
|
4429
|
-
? { _: "_", }
|
|
4430
|
-
: processPayload;
|
|
4431
|
-
const upload = {
|
|
4432
|
-
method: "POST",
|
|
4433
|
-
endpoint: "/public/api/projects/import/upload",
|
|
4434
|
-
payload: {
|
|
4435
|
-
contentType: "multipart/form-data",
|
|
4436
|
-
fileField: "file",
|
|
4437
|
-
filePath: id,
|
|
4438
|
-
fileName: "tmp-import.zip",
|
|
4439
|
-
},
|
|
4440
|
-
};
|
|
4441
|
-
return {
|
|
4442
|
-
...upload,
|
|
4443
|
-
identifiers: {
|
|
4444
|
-
filePath: id,
|
|
4445
|
-
...(targetProjectKey ? { targetProjectKey, } : {}),
|
|
4446
|
-
archivePreflight: {
|
|
4447
|
-
local: true,
|
|
4448
|
-
required: true,
|
|
4449
|
-
checks: [
|
|
4450
|
-
"zip-integrity",
|
|
4451
|
-
"safe-unique-members",
|
|
4452
|
-
"manifest",
|
|
4453
|
-
"flow-references",
|
|
4454
|
-
"orphan-members",
|
|
4455
|
-
],
|
|
4456
|
-
},
|
|
4457
|
-
successVerification: {
|
|
4458
|
-
usedProjectKeyRequired: true,
|
|
4459
|
-
projectReadRequired: true,
|
|
4460
|
-
projectIncarnationRequired: true,
|
|
4461
|
-
remappingReported: true,
|
|
4462
|
-
},
|
|
4463
|
-
...(flags["record-cleanup"] === undefined
|
|
4464
|
-
? {}
|
|
4465
|
-
: {
|
|
4466
|
-
cleanupBinding: "actual used project key plus verified creationTag incarnation hash",
|
|
4467
|
-
}),
|
|
4468
|
-
},
|
|
4469
|
-
requests: [
|
|
4470
|
-
{ sequence: 1, ...upload, },
|
|
4471
|
-
{
|
|
4472
|
-
sequence: 2,
|
|
4473
|
-
method: "POST",
|
|
4474
|
-
endpoint: "/public/api/projects/import/{importId}/process",
|
|
4475
|
-
pathBindings: { importId: "requests[0].response.id", },
|
|
4476
|
-
payload: finalizedPayload,
|
|
4477
|
-
},
|
|
4478
|
-
],
|
|
4479
|
-
};
|
|
4480
|
-
}
|
|
4481
|
-
case "project.settings-set":
|
|
4482
|
-
return {
|
|
4483
|
-
method: "PUT",
|
|
4484
|
-
endpoint: projectEndpoint("/settings"),
|
|
4485
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
4486
|
-
};
|
|
4487
|
-
case "project.permissions-set":
|
|
4488
|
-
return {
|
|
4489
|
-
method: "PUT",
|
|
4490
|
-
endpoint: projectEndpoint("/permissions"),
|
|
4491
|
-
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
4492
|
-
};
|
|
4493
|
-
case "continuous-activity.start":
|
|
4494
|
-
return {
|
|
4495
|
-
method: "POST",
|
|
4496
|
-
endpoint: projectEndpoint(`/continuous-activities/${encodeURIComponent(id)}/start`),
|
|
4497
|
-
identifiers: { recipeId: id, },
|
|
4498
|
-
payload: jsonInput(flags) ?? {},
|
|
4499
|
-
};
|
|
4500
|
-
case "continuous-activity.stop":
|
|
4501
|
-
return {
|
|
4502
|
-
method: "POST",
|
|
4503
|
-
endpoint: projectEndpoint(`/continuous-activities/${encodeURIComponent(id)}/stop`),
|
|
4504
|
-
identifiers: { recipeId: id, },
|
|
4505
|
-
};
|
|
4506
|
-
case "metrics.dataset-compute":
|
|
4507
|
-
return {
|
|
4508
|
-
method: "POST",
|
|
4509
|
-
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/actions/computeMetrics?partition=`),
|
|
4510
|
-
identifiers: { dataset: id, },
|
|
4511
|
-
};
|
|
4512
|
-
case "flow-zone.organize":
|
|
4513
|
-
return {
|
|
4514
|
-
method: "POST",
|
|
4515
|
-
endpoint: projectEndpoint("/flow/zones"),
|
|
4516
|
-
payload: jsonInput(flags),
|
|
4517
|
-
};
|
|
4518
|
-
/* ---- project-git: mutations only; reads are never planned ---- */
|
|
4519
|
-
case "project-git.set-remote": {
|
|
4520
|
-
const remote = flags["name"] ?? "origin";
|
|
4521
|
-
return {
|
|
4522
|
-
method: "POST",
|
|
4523
|
-
endpoint: projectGitEndpoint(projectKey, `/remotes/${encodeURIComponent(remote)}`),
|
|
4524
|
-
identifiers: { remote, },
|
|
4525
|
-
payload: { url: requiredPlanRepositoryUrl(flags, "repository", entry.usage), },
|
|
4526
|
-
};
|
|
4527
|
-
}
|
|
4528
|
-
case "project-git.remove-remote": {
|
|
4529
|
-
const remote = flags["name"] ?? "origin";
|
|
4530
|
-
return {
|
|
4531
|
-
method: "DELETE",
|
|
4532
|
-
endpoint: projectGitEndpoint(projectKey, `/remotes/${encodeURIComponent(remote)}`),
|
|
4533
|
-
identifiers: { remote, },
|
|
4534
|
-
};
|
|
4535
|
-
}
|
|
4536
|
-
case "project-git.create-branch":
|
|
4537
|
-
return {
|
|
4538
|
-
method: "POST",
|
|
4539
|
-
endpoint: projectGitEndpoint(projectKey, "/branches/"),
|
|
4540
|
-
identifiers: { name: id, },
|
|
4541
|
-
payload: {
|
|
4542
|
-
name: id,
|
|
4543
|
-
commit: flags["commit"] ?? null,
|
|
4544
|
-
duplicateProject: parseBooleanOption(flags["duplicate-project"], "--duplicate-project") ?? false,
|
|
4545
|
-
targetProjectKey: flags["target-project-key"] ?? null,
|
|
4546
|
-
targetProjectFolderId: flags["target-project-folder-id"] ?? null,
|
|
4547
|
-
},
|
|
4548
|
-
};
|
|
4549
|
-
case "project-git.delete-branch": {
|
|
4550
|
-
const remote = parseBooleanOption(flags["remote"], "--remote") ?? false;
|
|
4551
|
-
return {
|
|
4552
|
-
method: "POST",
|
|
4553
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/deleteBranch"),
|
|
4554
|
-
identifiers: { name: id, remote, },
|
|
4555
|
-
payload: {
|
|
4556
|
-
name: id,
|
|
4557
|
-
remote,
|
|
4558
|
-
deleteRemotely: parseBooleanOption(flags["delete-remotely"], "--delete-remotely") ?? false,
|
|
4559
|
-
forceDelete: parseBooleanOption(flags["force-delete"], "--force-delete") ?? false,
|
|
4560
|
-
},
|
|
4561
|
-
};
|
|
4562
|
-
}
|
|
4563
|
-
case "project-git.switch":
|
|
4564
|
-
return {
|
|
4565
|
-
method: "POST",
|
|
4566
|
-
endpoint: projectGitEndpoint(projectKey, `/actions/switchBranch${querySuffix({ branchName: id, })}`),
|
|
4567
|
-
identifiers: { branch: id, },
|
|
4568
|
-
};
|
|
4569
|
-
case "project-git.create-tag":
|
|
4570
|
-
return {
|
|
4571
|
-
method: "POST",
|
|
4572
|
-
endpoint: projectGitEndpoint(projectKey, "/tags/"),
|
|
4573
|
-
identifiers: { name: id, },
|
|
4574
|
-
payload: {
|
|
4575
|
-
name: id,
|
|
4576
|
-
reference: flags["reference"] ?? "HEAD",
|
|
4577
|
-
message: flags["message"] ?? "",
|
|
4578
|
-
},
|
|
4579
|
-
};
|
|
4580
|
-
case "project-git.delete-tag":
|
|
4581
|
-
return {
|
|
4582
|
-
method: "POST",
|
|
4583
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/deleteTag"),
|
|
4584
|
-
identifiers: { name: id, },
|
|
4585
|
-
payload: { name: id, },
|
|
4586
|
-
};
|
|
4587
|
-
case "project-git.fetch":
|
|
4588
|
-
return {
|
|
4589
|
-
method: "POST",
|
|
4590
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/fetch"),
|
|
4591
|
-
};
|
|
4592
|
-
case "project-git.pull":
|
|
4593
|
-
return {
|
|
4594
|
-
method: "POST",
|
|
4595
|
-
endpoint: projectGitEndpoint(projectKey, `/actions/pullRebase${querySuffix({
|
|
4596
|
-
branchName: flags["branch"],
|
|
4597
|
-
})}`),
|
|
4598
|
-
...(flags["branch"] !== undefined
|
|
4599
|
-
? { identifiers: { branch: flags["branch"], }, }
|
|
4600
|
-
: {}),
|
|
4601
|
-
};
|
|
4602
|
-
case "project-git.push":
|
|
4603
|
-
return {
|
|
4604
|
-
method: "POST",
|
|
4605
|
-
endpoint: projectGitEndpoint(projectKey, `/actions/push${querySuffix({
|
|
4606
|
-
branchName: flags["branch"],
|
|
4607
|
-
})}`),
|
|
4608
|
-
...(flags["branch"] !== undefined
|
|
4609
|
-
? { identifiers: { branch: flags["branch"], }, }
|
|
4610
|
-
: {}),
|
|
4611
|
-
};
|
|
4612
|
-
case "project-git.commit": {
|
|
4613
|
-
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
4614
|
-
return {
|
|
4615
|
-
method: "POST",
|
|
4616
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/commit"),
|
|
4617
|
-
identifiers: { message, },
|
|
4618
|
-
payload: { message, },
|
|
4619
|
-
};
|
|
4620
|
-
}
|
|
4621
|
-
case "project-git.revert-to-revision":
|
|
4622
|
-
return {
|
|
4623
|
-
method: "POST",
|
|
4624
|
-
endpoint: projectGitEndpoint(projectKey, `/actions/revertToRevision${querySuffix({ commit: id, })}`),
|
|
4625
|
-
identifiers: { commit: id, },
|
|
4626
|
-
};
|
|
4627
|
-
case "project-git.revert-commit":
|
|
4628
|
-
return {
|
|
4629
|
-
method: "POST",
|
|
4630
|
-
endpoint: projectGitEndpoint(projectKey, `/actions/revertCommit${querySuffix({ commit: id, })}`),
|
|
4631
|
-
identifiers: { commit: id, },
|
|
4632
|
-
};
|
|
4633
|
-
case "project-git.reset-to-head":
|
|
4634
|
-
return {
|
|
4635
|
-
method: "POST",
|
|
4636
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/resetToLocalHeadState"),
|
|
4637
|
-
};
|
|
4638
|
-
case "project-git.reset-to-upstream":
|
|
4639
|
-
return {
|
|
4640
|
-
method: "POST",
|
|
4641
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/resetToRemoteHeadState"),
|
|
4642
|
-
};
|
|
4643
|
-
case "project-git.drop-and-rebuild": {
|
|
4644
|
-
if (parseBooleanOption(flags["i-know-what-i-am-doing"], "--i-know-what-i-am-doing") !== true) {
|
|
4645
|
-
throw new UsageError(`--i-know-what-i-am-doing is required to acknowledge the irreversible Git history loss. Usage: ${entry.usage}`);
|
|
4646
|
-
}
|
|
4647
|
-
return {
|
|
4648
|
-
method: "POST",
|
|
4649
|
-
endpoint: projectGitEndpoint(projectKey, `/actions/dropAndRebuild${querySuffix({ iKnowWhatIAmDoing: true, })}`),
|
|
4650
|
-
};
|
|
4651
|
-
}
|
|
4652
|
-
case "project-git.add-library": {
|
|
4653
|
-
const targetPath = id;
|
|
4654
|
-
return {
|
|
4655
|
-
method: "POST",
|
|
4656
|
-
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/"),
|
|
4657
|
-
identifiers: { localTargetPath: targetPath, },
|
|
4658
|
-
payload: {
|
|
4659
|
-
repository: requiredPlanRepositoryUrl(flags, "repository", entry.usage),
|
|
4660
|
-
login: flags["login"] ?? null,
|
|
4661
|
-
password: flags["password-env"] !== undefined ? "***" : null,
|
|
4662
|
-
pathInGitRepository: flags["path-in-repository"] ?? "",
|
|
4663
|
-
localTargetPath: targetPath,
|
|
4664
|
-
checkout: requiredPlanFlag(flags, "checkout", entry.usage),
|
|
4665
|
-
addToPythonPath: parseBooleanOption(flags["no-add-to-python-path"], "--no-add-to-python-path") !== true,
|
|
4666
|
-
},
|
|
4667
|
-
wait: projectGitFutureWait(),
|
|
4668
|
-
};
|
|
4669
|
-
}
|
|
4670
|
-
case "project-git.set-library": {
|
|
4671
|
-
const targetPath = validateGitReferencePath(id);
|
|
4672
|
-
return {
|
|
4673
|
-
method: "PUT",
|
|
4674
|
-
endpoint: projectGitEndpoint(projectKey, `/lib-git-refs/${encodeGitReferencePath(targetPath)}`),
|
|
4675
|
-
identifiers: { library: targetPath, },
|
|
4676
|
-
payload: {
|
|
4677
|
-
repository: requiredPlanRepositoryUrl(flags, "repository", entry.usage),
|
|
4678
|
-
login: flags["login"] ?? null,
|
|
4679
|
-
password: flags["password-env"] !== undefined ? "***" : null,
|
|
4680
|
-
pathInGitRepository: flags["path-in-repository"] ?? "",
|
|
4681
|
-
checkout: requiredPlanFlag(flags, "checkout", entry.usage),
|
|
4682
|
-
},
|
|
4683
|
-
};
|
|
4684
|
-
}
|
|
4685
|
-
case "project-git.remove-library": {
|
|
4686
|
-
const targetPath = validateGitReferencePath(id);
|
|
4687
|
-
return {
|
|
4688
|
-
method: "DELETE",
|
|
4689
|
-
endpoint: projectGitEndpoint(projectKey, `/lib-git-refs/${encodeGitReferencePath(targetPath)}${querySuffix({
|
|
4690
|
-
deleteDirectory: parseBooleanOption(flags["delete-directory"], "--delete-directory") ?? false,
|
|
4691
|
-
})}`),
|
|
4692
|
-
identifiers: { library: targetPath, },
|
|
4693
|
-
};
|
|
4694
|
-
}
|
|
4695
|
-
case "project-git.reset-library": {
|
|
4696
|
-
const targetPath = validateGitReferencePath(id);
|
|
4697
|
-
return {
|
|
4698
|
-
method: "POST",
|
|
4699
|
-
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/action/reset"),
|
|
4700
|
-
identifiers: { library: targetPath, },
|
|
4701
|
-
payload: { gitRef: targetPath, },
|
|
4702
|
-
wait: projectGitFutureWait(),
|
|
4703
|
-
};
|
|
4704
|
-
}
|
|
4705
|
-
case "project-git.push-library": {
|
|
4706
|
-
const targetPath = validateGitReferencePath(id);
|
|
4707
|
-
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
4708
|
-
return {
|
|
4709
|
-
method: "POST",
|
|
4710
|
-
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/action/push"),
|
|
4711
|
-
identifiers: { library: targetPath, message, },
|
|
4712
|
-
payload: { gitRef: targetPath, commitMessage: message, },
|
|
4713
|
-
wait: projectGitFutureWait(),
|
|
4714
|
-
};
|
|
4715
|
-
}
|
|
4716
|
-
case "project-git.push-all-libraries": {
|
|
4717
|
-
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
4718
|
-
return {
|
|
4719
|
-
method: "POST",
|
|
4720
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/git-refs/push-all"),
|
|
4721
|
-
identifiers: { message, },
|
|
4722
|
-
payload: { commitMessage: message, },
|
|
4723
|
-
wait: projectGitFutureWait(),
|
|
4724
|
-
};
|
|
4725
|
-
}
|
|
4726
|
-
case "project-git.reset-all-libraries":
|
|
4727
|
-
return {
|
|
4728
|
-
method: "POST",
|
|
4729
|
-
endpoint: projectGitEndpoint(projectKey, "/actions/git-refs/reset-all"),
|
|
4730
|
-
wait: projectGitFutureWait(),
|
|
4731
|
-
};
|
|
4732
|
-
case "project-git.future-abort":
|
|
4733
|
-
return {
|
|
4734
|
-
method: "DELETE",
|
|
4735
|
-
endpoint: projectGitFutureEndpoint(id),
|
|
4736
|
-
identifiers: { jobId: id, },
|
|
4737
|
-
};
|
|
4738
|
-
case "project-folder.settings-set": {
|
|
4739
|
-
const payload = requiredPlanJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${entry.usage}`);
|
|
4740
|
-
return {
|
|
4741
|
-
method: "PUT",
|
|
4742
|
-
endpoint: `${projectFolderEndpoint(id)}/settings`,
|
|
4743
|
-
identifiers: { folderId: id, },
|
|
4744
|
-
payload,
|
|
4745
|
-
};
|
|
4746
|
-
}
|
|
4747
|
-
case "project-folder.move": {
|
|
4748
|
-
const destination = args[1];
|
|
4749
|
-
if (!destination)
|
|
4750
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
4751
|
-
return {
|
|
4752
|
-
method: "POST",
|
|
4753
|
-
endpoint: `${projectFolderEndpoint(id)}/move${querySuffix({ destination, })}`,
|
|
4754
|
-
identifiers: { folderId: id, destination, },
|
|
4755
|
-
};
|
|
4756
|
-
}
|
|
4757
|
-
case "project-folder.delete":
|
|
4758
|
-
return {
|
|
4759
|
-
method: "DELETE",
|
|
4760
|
-
endpoint: projectFolderEndpoint(id),
|
|
4761
|
-
identifiers: { folderId: id, },
|
|
4762
|
-
};
|
|
4763
|
-
case "project-folder.create-child": {
|
|
4764
|
-
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
4765
|
-
return {
|
|
4766
|
-
method: "POST",
|
|
4767
|
-
endpoint: `${projectFolderEndpoint(id)}/children${querySuffix({ name, })}`,
|
|
4768
|
-
identifiers: { parentFolderId: id, name, },
|
|
4769
|
-
};
|
|
4770
|
-
}
|
|
4771
|
-
case "project-folder.move-project": {
|
|
4772
|
-
const projectKeyArg = args[1];
|
|
4773
|
-
const destination = args[2];
|
|
4774
|
-
if (!projectKeyArg || !destination) {
|
|
4775
|
-
throw new UsageError(`Usage: ${entry.usage}`);
|
|
4776
|
-
}
|
|
4777
|
-
return {
|
|
4778
|
-
method: "POST",
|
|
4779
|
-
endpoint: `${projectFolderEndpoint(id)}/projects/${encodeURIComponent(projectKeyArg)}/move${querySuffix({ destination, })}`,
|
|
4780
|
-
identifiers: { folderId: id, projectKey: projectKeyArg, destination, },
|
|
4781
|
-
};
|
|
4782
|
-
}
|
|
4783
|
-
default:
|
|
4784
|
-
return {
|
|
4785
|
-
exact: false,
|
|
4786
|
-
reason: `No exact offline request shape is defined for ${resource}.${action}; no endpoint was guessed.`,
|
|
4787
|
-
identifiers: id ? { id, } : undefined,
|
|
4788
|
-
};
|
|
4789
|
-
}
|
|
4790
|
-
}
|
|
4791
|
-
export function buildMutationPlan(resource, action, meta, args, flags) {
|
|
4792
|
-
const entry = buildRegistryEntry(resource, action, meta);
|
|
4793
|
-
if (!entry.mutatesDss && entry.sideEffect !== "write") {
|
|
4794
|
-
throw new UsageError(`--plan is only supported for mutating commands. Usage: ${meta.usage}`);
|
|
4795
|
-
}
|
|
4796
|
-
const requiredPositionals = entry.positionalArguments.filter((positional) => positional.required);
|
|
4797
|
-
requireArgs(args, requiredPositionals.length, meta.usage);
|
|
4798
|
-
const projectKey = projectKeyForPlan(entry, flags);
|
|
4799
|
-
const shape = commandPlanShape(resource, action, args, flags, entry, projectKey);
|
|
4800
|
-
return planResult(resource, action, {
|
|
4801
|
-
...shape,
|
|
4802
|
-
asyncKind: entry.async,
|
|
4803
|
-
exitCodesOnFailure: exitCodesOnFailure(entry),
|
|
4804
|
-
idempotency: entry.idempotency,
|
|
4805
|
-
plannedAndDryRun: executionMode(flags).dryRun,
|
|
4806
|
-
});
|
|
4807
|
-
}
|
|
4808
1543
|
/**
|
|
4809
1544
|
* Batch plans may surface exit 4: any dispatched step can be a long-running
|
|
4810
1545
|
* lifecycle command whose remote work fails, or a synchronous assertion
|