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
|
@@ -0,0 +1,3684 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { APP_MANIFEST_CONCURRENCY_CONTROL, } from "../resources/applications.js";
|
|
4
|
+
import { assertDatasetTypeCreatable, buildDatasetCreateBody, } from "../resources/dataset-create.js";
|
|
5
|
+
import { schemaPropagationBody, } from "../resources/flow.js";
|
|
6
|
+
import { mlTaskCreateBody, mlTaskReguessQuery, } from "../resources/ml-tasks.js";
|
|
7
|
+
import { deployRunQuery, experimentsDatasetBody, inferenceInfoBody, } from "../resources/mlflow-extension.js";
|
|
8
|
+
import { validatePluginDestinationPath, validatePluginPath, } from "../resources/plugins.js";
|
|
9
|
+
import { encodeLibraryPath, PROJECT_LIBRARY_CONCURRENCY_CONTROL, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../resources/project-library.js";
|
|
10
|
+
import { buildRecipeCreateRequest, } from "../resources/recipe-create.js";
|
|
11
|
+
import { modelDiagnosticsBody, } from "../resources/trained-model.js";
|
|
12
|
+
import { encodeGitReferencePath, validateGitReferencePath, } from "../utils/git-reference.js";
|
|
13
|
+
import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, unknownJsonInput, } from "./coerce.js";
|
|
14
|
+
import { parseCodeRunIntegerFlag, resolveCodeInputWithSource, } from "./commands/code.js";
|
|
15
|
+
import { schemaPropagationOptionsFromFlags, } from "./commands/flow.js";
|
|
16
|
+
import { mlTaskFieldsFromFlags, mlTaskReguessOptionsFromFlags, } from "./commands/ml-task.js";
|
|
17
|
+
import { deployRunOptionsFromFlags, experimentsDatasetOptionsFromFlags, inferenceInfoFromFlags, } from "./commands/mlflow.js";
|
|
18
|
+
import { projectLibraryPutPayload, } from "./commands/project-library.js";
|
|
19
|
+
import { recipeCreateOptionsFromFlags, } from "./commands/recipe.js";
|
|
20
|
+
import { resolveSqlQueryInvocation, } from "./commands/sql.js";
|
|
21
|
+
import { buildRegistryEntry, splitPackageSpec, } from "./contract.js";
|
|
22
|
+
import { ambientProjectKey, } from "./env.js";
|
|
23
|
+
import { executionMode, } from "./flags.js";
|
|
24
|
+
import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-zone.js";
|
|
25
|
+
import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
|
|
26
|
+
import { diagnosticsComputationFromFlags, } from "./helpers/trained-model.js";
|
|
27
|
+
import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
|
|
28
|
+
import { commandUsage, } from "./syntax.js";
|
|
29
|
+
import { requireArgs, UsageError, } from "./usage.js";
|
|
30
|
+
function codeEnvWait(flags) {
|
|
31
|
+
return flags["no-wait"] !== true;
|
|
32
|
+
}
|
|
33
|
+
function codeEnvLang(value, usage) {
|
|
34
|
+
if (value !== "PYTHON" && value !== "R") {
|
|
35
|
+
throw new UsageError(`Invalid code environment language ${JSON.stringify(value)}. Usage: ${usage}`);
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function codeEnvParams(flags) {
|
|
40
|
+
const params = json(flags["params"]) ?? jsonInput(flags) ?? {};
|
|
41
|
+
if (typeof flags["python-interpreter"] === "string") {
|
|
42
|
+
params.pythonInterpreter = flags["python-interpreter"];
|
|
43
|
+
}
|
|
44
|
+
return params;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve requested package specs from --file/--packages/--package. An
|
|
48
|
+
* explicit source that resolves to zero specs is a legitimate clear
|
|
49
|
+
* (set-packages replaces specPackageList wholesale); only a call with no
|
|
50
|
+
* package source flag at all is a usage error.
|
|
51
|
+
*/
|
|
52
|
+
function codeEnvPackageList(flags) {
|
|
53
|
+
const file = flags["file"];
|
|
54
|
+
const packages = typeof file === "string"
|
|
55
|
+
? splitPackageSpec(fs.readFileSync(file, "utf-8"))
|
|
56
|
+
: [];
|
|
57
|
+
if (typeof flags["packages"] === "string") {
|
|
58
|
+
packages.push(...splitPackageSpec(flags["packages"]));
|
|
59
|
+
}
|
|
60
|
+
if (typeof flags["package"] === "string") {
|
|
61
|
+
packages.push(...splitPackageSpec(flags["package"]));
|
|
62
|
+
}
|
|
63
|
+
if (typeof file !== "string"
|
|
64
|
+
&& typeof flags["packages"] !== "string"
|
|
65
|
+
&& typeof flags["package"] !== "string") {
|
|
66
|
+
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.");
|
|
67
|
+
}
|
|
68
|
+
return packages;
|
|
69
|
+
}
|
|
70
|
+
function exitCodesOnFailure(entry) {
|
|
71
|
+
return {
|
|
72
|
+
usage: entry.exitCodes.usage,
|
|
73
|
+
error: entry.exitCodes.error,
|
|
74
|
+
transient: entry.exitCodes.transient,
|
|
75
|
+
...(entry.exitCodes.longRunningFailure !== undefined
|
|
76
|
+
? { longRunningFailure: entry.exitCodes.longRunningFailure, }
|
|
77
|
+
: {}),
|
|
78
|
+
...(entry.exitCodes.assertionFailure !== undefined
|
|
79
|
+
? { assertionFailure: entry.exitCodes.assertionFailure, }
|
|
80
|
+
: {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Plan-local project key resolution. `--plan` is a purely local preview, so it
|
|
85
|
+
* resolves the target project from the explicit `--project-key` flag and the
|
|
86
|
+
* documented `DATAIKU_PROJECT_KEY` environment variable only. The saved
|
|
87
|
+
* credentials file is never opened and no API key is ever resolved, so planning
|
|
88
|
+
* can neither depend on nor leak stored secrets.
|
|
89
|
+
*/
|
|
90
|
+
function planProjectKeyFromArgs(flags) {
|
|
91
|
+
const fromFlag = flags["project-key"];
|
|
92
|
+
if (typeof fromFlag === "string" && fromFlag.trim().length > 0)
|
|
93
|
+
return fromFlag.trim();
|
|
94
|
+
const fromEnv = ambientProjectKey();
|
|
95
|
+
return fromEnv !== undefined && fromEnv.trim().length > 0 ? fromEnv.trim() : undefined;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Commands whose plan must pin the target with an explicit `--project-key`
|
|
99
|
+
* instead of falling back to ambient `DATAIKU_PROJECT_KEY`, so a cleanup plan
|
|
100
|
+
* can never silently retarget a project the caller did not name.
|
|
101
|
+
*/
|
|
102
|
+
const EXPLICIT_PLAN_PROJECT_KEY = { "app.delete-instance": true, };
|
|
103
|
+
/** Plan-local project key, or the canonical `--project-key` usage error. */
|
|
104
|
+
function requiredPlanProjectKey(flags, usage) {
|
|
105
|
+
return planProjectKeyFromArgs(flags) ?? requiredPlanFlag(flags, "project-key", usage);
|
|
106
|
+
}
|
|
107
|
+
function projectKeyForPlan(entry, flags) {
|
|
108
|
+
if (!entry.requiresProject)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (EXPLICIT_PLAN_PROJECT_KEY[`${entry.resource}.${entry.action}`] === true) {
|
|
111
|
+
return requiredPlanFlag(flags, "project-key", entry.usage);
|
|
112
|
+
}
|
|
113
|
+
const projectKey = planProjectKeyFromArgs(flags);
|
|
114
|
+
if (projectKey)
|
|
115
|
+
return projectKey;
|
|
116
|
+
throw new UsageError(`Missing project key. Pass --project-key or set DATAIKU_PROJECT_KEY before planning ${entry.resource} ${entry.action}.`);
|
|
117
|
+
}
|
|
118
|
+
function requiredPlanFlag(flags, name, usage) {
|
|
119
|
+
const value = flags[name];
|
|
120
|
+
// Return trimmed like the runtime's requiredStringFlag so plans advertise
|
|
121
|
+
// exactly the identifier the command will act on.
|
|
122
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
123
|
+
return value.trim();
|
|
124
|
+
throw new UsageError(`--${name} is required. Usage: ${usage}`);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Optional caller-chosen identifier. Absent selects the command's generated
|
|
128
|
+
* mode; a present-but-empty value is an input error, never a silent omission.
|
|
129
|
+
*/
|
|
130
|
+
function optionalPlanFlag(flags, name, usage) {
|
|
131
|
+
const value = flags[name];
|
|
132
|
+
if (value === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
135
|
+
throw new UsageError(`--${name} must not be empty when supplied; omit --${name} entirely to generate the successor key during apply. Usage: ${usage}`, "validation_failed");
|
|
136
|
+
}
|
|
137
|
+
return value.trim();
|
|
138
|
+
}
|
|
139
|
+
function optionalJsonFlag(flags, name) {
|
|
140
|
+
const value = flags[name];
|
|
141
|
+
return typeof value === "string" ? parseJsonObject(value, `--${name}`) : undefined;
|
|
142
|
+
}
|
|
143
|
+
function requiredPlanJsonInput(flags, usage) {
|
|
144
|
+
return requiredJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${usage}`);
|
|
145
|
+
}
|
|
146
|
+
function dataQualityEndpoint(projectKey, datasetName, suffix) {
|
|
147
|
+
return encodedProjectEndpointForPlan(projectKey, `/datasets/${encodeURIComponent(datasetName)}/data-quality${suffix}`);
|
|
148
|
+
}
|
|
149
|
+
function querySuffix(params) {
|
|
150
|
+
const search = new URLSearchParams();
|
|
151
|
+
for (const [key, value,] of Object.entries(params)) {
|
|
152
|
+
if (value !== undefined)
|
|
153
|
+
search.set(key, String(value));
|
|
154
|
+
}
|
|
155
|
+
const raw = search.toString();
|
|
156
|
+
return raw ? `?${raw}` : "";
|
|
157
|
+
}
|
|
158
|
+
function projectFolderEndpoint(folderId) {
|
|
159
|
+
return `/public/api/project-folders/${encodeURIComponent(folderId)}`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Git and future API mount. The official Python client mounts these routes on
|
|
163
|
+
* `/dip/publicapi`, which is also what the project-git resource uses; do not
|
|
164
|
+
* normalize them to the repo-wide `/public/api`.
|
|
165
|
+
*/
|
|
166
|
+
const PROJECT_GIT_API_ROOT = "/dip/publicapi";
|
|
167
|
+
function projectGitEndpoint(projectKey, suffix) {
|
|
168
|
+
if (!projectKey)
|
|
169
|
+
throw new UsageError("--project-key is required for project-git mutations.");
|
|
170
|
+
return `${PROJECT_GIT_API_ROOT}/projects/${encodeURIComponent(projectKey)}/git${suffix}`;
|
|
171
|
+
}
|
|
172
|
+
function projectGitFutureEndpoint(jobId) {
|
|
173
|
+
return `${PROJECT_GIT_API_ROOT}/futures/${encodeURIComponent(jobId)}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* `--plan` bypasses the SDK boundary guard, so HTTP(S) URLs with embedded
|
|
177
|
+
* userinfo are rejected here before any plan is printed. SSH/scp-style URLs
|
|
178
|
+
* (`git@host:org/repo.git`, `ssh://...`) remain valid.
|
|
179
|
+
*/
|
|
180
|
+
function validatedPlanRepositoryUrl(url, flag, usage) {
|
|
181
|
+
const candidate = url.trim();
|
|
182
|
+
if (/[\u0000-\u001f\u007f]/u.test(candidate)) {
|
|
183
|
+
throw new UsageError(`--${flag} must not contain control characters. Usage: ${usage}`);
|
|
184
|
+
}
|
|
185
|
+
if (/^https?:/i.test(candidate)) {
|
|
186
|
+
if (candidate.includes("\\")) {
|
|
187
|
+
throw new UsageError(`--${flag} must be a valid HTTP(S) URL. Usage: ${usage}`);
|
|
188
|
+
}
|
|
189
|
+
if (!/^https?:\/\//i.test(candidate)) {
|
|
190
|
+
throw new UsageError(`--${flag} must be a valid HTTP(S) URL. Usage: ${usage}`);
|
|
191
|
+
}
|
|
192
|
+
let parsed;
|
|
193
|
+
try {
|
|
194
|
+
parsed = new URL(candidate);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
throw new UsageError(`--${flag} must be a valid HTTP(S) URL. Usage: ${usage}`);
|
|
198
|
+
}
|
|
199
|
+
if (parsed.username !== "" || parsed.password !== "") {
|
|
200
|
+
throw new UsageError(`--${flag} must not contain embedded credentials (userinfo). Usage: ${usage}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return candidate;
|
|
204
|
+
}
|
|
205
|
+
function pluginRootEndpoint(pluginId, suffix) {
|
|
206
|
+
return `/public/api/plugins/${encodeURIComponent(pluginId)}${suffix}`;
|
|
207
|
+
}
|
|
208
|
+
function pluginActionEndpoint(pluginId, action) {
|
|
209
|
+
return pluginRootEndpoint(pluginId, `/actions/${action}`);
|
|
210
|
+
}
|
|
211
|
+
function pluginContentsEndpoint(pluginId, contentPath) {
|
|
212
|
+
return `${pluginRootEndpoint(pluginId, "/contents/")}${encodePluginSegmentsForPlan(contentPath)}`;
|
|
213
|
+
}
|
|
214
|
+
function pluginFoldersEndpoint(pluginId, contentPath) {
|
|
215
|
+
return `${pluginRootEndpoint(pluginId, "/folders/")}${encodePluginSegmentsForPlan(contentPath)}`;
|
|
216
|
+
}
|
|
217
|
+
/** Encodes a plugin content path per segment for plan-time endpoints. */
|
|
218
|
+
function encodePluginSegmentsForPlan(contentPath) {
|
|
219
|
+
return contentPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Plan payload for plugin git install/update bodies. The repository URL is
|
|
223
|
+
* validated (embedded userinfo rejected) and secrets are never echoed: the
|
|
224
|
+
* plan carries the URL only, never embedded credentials.
|
|
225
|
+
*/
|
|
226
|
+
function pluginGitPlanPayload(flags) {
|
|
227
|
+
const repository = requiredPlanFlag(flags, "repository", "dss plugin install-from-git --repository URL");
|
|
228
|
+
// Mirrors PluginsResource install/update bodies: absent options are sent as null.
|
|
229
|
+
return {
|
|
230
|
+
gitRepositoryUrl: validatedPlanRepositoryUrl(repository, "repository", "--repository URL"),
|
|
231
|
+
gitCheckout: typeof flags["checkout"] === "string" ? flags["checkout"] : null,
|
|
232
|
+
gitSubpath: typeof flags["path-in-repository"] === "string" ? flags["path-in-repository"] : null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function optionalPlanProjectScope(flags) {
|
|
236
|
+
const projectKey = flags["project-key"];
|
|
237
|
+
if (typeof projectKey !== "string" || projectKey.trim() === "")
|
|
238
|
+
return "";
|
|
239
|
+
return `?projectKey=${encodeURIComponent(projectKey.trim())}`;
|
|
240
|
+
}
|
|
241
|
+
function settingsConfigKeys(flags) {
|
|
242
|
+
const content = flags["content"];
|
|
243
|
+
if (typeof content !== "string")
|
|
244
|
+
return [];
|
|
245
|
+
try {
|
|
246
|
+
const parsed = JSON.parse(content);
|
|
247
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
248
|
+
return [];
|
|
249
|
+
return Object.keys(parsed);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function contentSourceKind(flags) {
|
|
256
|
+
if (flags["stdin"] === true)
|
|
257
|
+
return "stdin";
|
|
258
|
+
if (typeof flags["file"] === "string")
|
|
259
|
+
return "file";
|
|
260
|
+
if (typeof flags["content"] === "string")
|
|
261
|
+
return "content";
|
|
262
|
+
return "unknown";
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Plan-safe view of a user create/update body: password values are replaced by
|
|
266
|
+
* a fixed marker so secrets never appear in --plan output.
|
|
267
|
+
*/
|
|
268
|
+
function redactedUserPayload(payload) {
|
|
269
|
+
if (payload["password"] === undefined)
|
|
270
|
+
return payload;
|
|
271
|
+
return { ...payload, password: "<omitted>", };
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Plan-safe view of a connection create/update body: the connection-type
|
|
275
|
+
* specific `params` object may carry credentials (passwords, keys, tokens), so
|
|
276
|
+
* the plan carries only the marker, never the raw params.
|
|
277
|
+
*/
|
|
278
|
+
function redactedConnectionPayload(payload) {
|
|
279
|
+
if (payload["params"] === undefined)
|
|
280
|
+
return payload;
|
|
281
|
+
return { ...payload, params: "<omitted; may include credentials>", };
|
|
282
|
+
}
|
|
283
|
+
/** Required CSV flag for plans (comma-separated logins list). */
|
|
284
|
+
function requiredPlanCsv(flags, name, usage) {
|
|
285
|
+
const raw = requiredPlanFlag(flags, name, usage);
|
|
286
|
+
return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
287
|
+
}
|
|
288
|
+
function requiredPlanRepositoryUrl(flags, flag, usage) {
|
|
289
|
+
const url = requiredPlanFlag(flags, flag, usage);
|
|
290
|
+
return validatedPlanRepositoryUrl(url, flag, usage);
|
|
291
|
+
}
|
|
292
|
+
/** Wait procedure advertised for the library calls that return a job id. */
|
|
293
|
+
function projectGitFutureWait() {
|
|
294
|
+
return {
|
|
295
|
+
when: "after-dispatch",
|
|
296
|
+
endpoint: `${PROJECT_GIT_API_ROOT}/futures/{jobId}?peek=false`,
|
|
297
|
+
description: "Poll the returned job id until the future reports a result; never abort it.",
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function jobBuildPayload(target, projectKey, flags) {
|
|
301
|
+
const targetType = jobBuildTargetTypeFromFlags(flags);
|
|
302
|
+
const partition = flags["partition"];
|
|
303
|
+
const output = { projectKey, id: target, type: targetType, };
|
|
304
|
+
if (targetType === "DATASET") {
|
|
305
|
+
if (partition !== undefined)
|
|
306
|
+
output.partition = partition;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
output.targetManagedFolderProjectKey = projectKey;
|
|
310
|
+
output.targetManagedFolder = target;
|
|
311
|
+
output.targetPartition = partition ?? "NP";
|
|
312
|
+
}
|
|
313
|
+
const payload = {
|
|
314
|
+
outputs: [output,],
|
|
315
|
+
type: flags["build-mode"] ?? "NON_RECURSIVE_FORCED_BUILD",
|
|
316
|
+
};
|
|
317
|
+
if (flags["auto-update-schema"] === true && targetType === "DATASET") {
|
|
318
|
+
payload.autoUpdateSchemaBeforeEachRecipeRun = true;
|
|
319
|
+
}
|
|
320
|
+
return payload;
|
|
321
|
+
}
|
|
322
|
+
function uploadPayload(filePath) {
|
|
323
|
+
return {
|
|
324
|
+
contentType: "multipart/form-data",
|
|
325
|
+
fileField: "file",
|
|
326
|
+
filePath,
|
|
327
|
+
fileName: path.basename(filePath),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
export function commandPlanShape(resource, action, args, flags, entry, projectKey) {
|
|
331
|
+
/** Route and ids of the trained model an `ml-task` (3 ids) or `saved-model` (2 ids) action targets. */
|
|
332
|
+
const trainedModelEndpoint = () => {
|
|
333
|
+
requireArgs(args, resource === "ml-task" ? 3 : 2, entry.usage);
|
|
334
|
+
const [first, second, third,] = args.map((arg) => encodeURIComponent(arg));
|
|
335
|
+
return resource === "ml-task"
|
|
336
|
+
? {
|
|
337
|
+
path: projectEndpoint(`/models/lab/${first}/${second}/models/${third}`),
|
|
338
|
+
identifiers: { analysisId: args[0], mlTaskId: args[1], modelId: args[2], },
|
|
339
|
+
}
|
|
340
|
+
: {
|
|
341
|
+
path: projectEndpoint(`/savedmodels/${first}/versions/${second}`),
|
|
342
|
+
identifiers: { savedModelId: args[0], versionId: args[1], },
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
const requiredProjectKey = () => {
|
|
346
|
+
if (!projectKey)
|
|
347
|
+
throw new UsageError(`Missing project key for ${resource} ${action}.`);
|
|
348
|
+
return projectKey;
|
|
349
|
+
};
|
|
350
|
+
const projectEndpoint = (suffix) => encodedProjectEndpointForPlan(requiredProjectKey(), suffix);
|
|
351
|
+
const id = args[0];
|
|
352
|
+
const codeEnvEndpoint = (suffix = "") => `/public/api/admin/code-envs/${encodeURIComponent(codeEnvLang(args[0], entry.usage))}/${encodeURIComponent(args[1])}${suffix}`;
|
|
353
|
+
const statisticsWorksheetsEndpoint = (datasetName) => projectEndpoint(`/datasets/${encodeURIComponent(datasetName)}/statistics/worksheets/`);
|
|
354
|
+
const statisticsWorksheetEndpoint = (datasetName, worksheetId) => `${statisticsWorksheetsEndpoint(datasetName)}${encodeURIComponent(worksheetId)}`;
|
|
355
|
+
switch (`${resource}.${action}`) {
|
|
356
|
+
case "sql.query": {
|
|
357
|
+
const payload = resolveSqlQueryInvocation(args, flags, planProjectKeyFromArgs(flags));
|
|
358
|
+
return {
|
|
359
|
+
method: "POST",
|
|
360
|
+
endpoint: "/public/api/sql/queries/",
|
|
361
|
+
identifiers: {
|
|
362
|
+
connection: payload.connection,
|
|
363
|
+
dataset: payload.datasetFullName,
|
|
364
|
+
},
|
|
365
|
+
payload,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
case "ml-task.train":
|
|
369
|
+
return {
|
|
370
|
+
method: "POST",
|
|
371
|
+
endpoint: projectEndpoint(`/models/lab/${encodeURIComponent(args[0])}/${encodeURIComponent(args[1])}/train`),
|
|
372
|
+
identifiers: { analysisId: args[0], mlTaskId: args[1], },
|
|
373
|
+
payload: {
|
|
374
|
+
sessionName: flags["session-name"],
|
|
375
|
+
runQueue: false,
|
|
376
|
+
},
|
|
377
|
+
wait: flags["wait"] === true,
|
|
378
|
+
};
|
|
379
|
+
case "wiki.create": {
|
|
380
|
+
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
381
|
+
const content = textInput(flags);
|
|
382
|
+
const create = {
|
|
383
|
+
method: "POST",
|
|
384
|
+
endpoint: projectEndpoint("/wiki/"),
|
|
385
|
+
payload: { projectKey, name, parent: flags["parent"] ?? null, },
|
|
386
|
+
};
|
|
387
|
+
return {
|
|
388
|
+
...create,
|
|
389
|
+
identifiers: { name, },
|
|
390
|
+
// Content is not accepted by the create endpoint; it lands in a follow-up update.
|
|
391
|
+
...(content === undefined ? {} : {
|
|
392
|
+
requests: [
|
|
393
|
+
{ sequence: 1, ...create, },
|
|
394
|
+
{ sequence: 2, method: "GET", endpoint: projectEndpoint("/wiki/{createdArticleId}"), },
|
|
395
|
+
{
|
|
396
|
+
sequence: 3,
|
|
397
|
+
method: "PUT",
|
|
398
|
+
endpoint: projectEndpoint("/wiki/{createdArticleId}"),
|
|
399
|
+
payload: { payload: content, },
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
}),
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
case "wiki.update":
|
|
406
|
+
return {
|
|
407
|
+
method: "PUT",
|
|
408
|
+
endpoint: projectEndpoint(`/wiki/${encodeURIComponent(id)}`),
|
|
409
|
+
identifiers: { article: id, },
|
|
410
|
+
payload: {
|
|
411
|
+
...jsonInput(flags),
|
|
412
|
+
name: flags["name"],
|
|
413
|
+
content: textInput(flags),
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
case "wiki.delete":
|
|
417
|
+
return {
|
|
418
|
+
method: "DELETE",
|
|
419
|
+
endpoint: projectEndpoint(`/wiki/${encodeURIComponent(id)}`),
|
|
420
|
+
identifiers: { article: id, },
|
|
421
|
+
};
|
|
422
|
+
case "dashboard.create": {
|
|
423
|
+
const data = jsonInput(flags);
|
|
424
|
+
const flagName = flags["name"];
|
|
425
|
+
const dataName = data?.["name"];
|
|
426
|
+
const name = flagName ?? (typeof dataName === "string" ? dataName : undefined);
|
|
427
|
+
if (!name) {
|
|
428
|
+
throw new UsageError(`--name or dashboard settings containing a string name are required. Usage: ${commandUsage("dashboard", "create")}`);
|
|
429
|
+
}
|
|
430
|
+
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
431
|
+
const payload = { ...(data ?? { pages: [], }), name, };
|
|
432
|
+
if (listed !== undefined)
|
|
433
|
+
payload.listed = listed;
|
|
434
|
+
return {
|
|
435
|
+
method: "POST",
|
|
436
|
+
endpoint: projectEndpoint("/dashboards/"),
|
|
437
|
+
identifiers: { name, },
|
|
438
|
+
payload,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
case "dashboard.update": {
|
|
442
|
+
const payload = { ...jsonInput(flags), };
|
|
443
|
+
if (typeof flags["name"] === "string")
|
|
444
|
+
payload.name = flags["name"];
|
|
445
|
+
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
446
|
+
if (listed !== undefined)
|
|
447
|
+
payload.listed = listed;
|
|
448
|
+
return {
|
|
449
|
+
method: "PUT",
|
|
450
|
+
endpoint: projectEndpoint(`/dashboards/${encodeURIComponent(id)}/`),
|
|
451
|
+
identifiers: { id, },
|
|
452
|
+
payload,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
case "dashboard.delete":
|
|
456
|
+
return {
|
|
457
|
+
method: "DELETE",
|
|
458
|
+
endpoint: projectEndpoint(`/dashboards/${encodeURIComponent(id)}/`),
|
|
459
|
+
identifiers: { id, },
|
|
460
|
+
};
|
|
461
|
+
case "insight.create": {
|
|
462
|
+
const data = jsonInput(flags);
|
|
463
|
+
const name = flags["name"];
|
|
464
|
+
const type = flags["type"];
|
|
465
|
+
if (!data && (!name || !type)) {
|
|
466
|
+
throw new UsageError(`--data or both --name and --type are required. Usage: ${commandUsage("insight", "create")}`);
|
|
467
|
+
}
|
|
468
|
+
const prototype = { ...data, };
|
|
469
|
+
if (name !== undefined)
|
|
470
|
+
prototype.name = name;
|
|
471
|
+
if (type !== undefined)
|
|
472
|
+
prototype.type = type;
|
|
473
|
+
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
474
|
+
if (listed !== undefined)
|
|
475
|
+
prototype.listed = listed;
|
|
476
|
+
const params = optionalJsonFlag(flags, "params");
|
|
477
|
+
if (params !== undefined)
|
|
478
|
+
prototype.params = params;
|
|
479
|
+
return {
|
|
480
|
+
method: "POST",
|
|
481
|
+
endpoint: projectEndpoint("/insights/"),
|
|
482
|
+
identifiers: { name, type, },
|
|
483
|
+
payload: {
|
|
484
|
+
insightPrototype: prototype,
|
|
485
|
+
contentType: flags["content-type"],
|
|
486
|
+
payload: textInput(flags),
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
case "insight.update":
|
|
491
|
+
return {
|
|
492
|
+
method: "POST",
|
|
493
|
+
endpoint: projectEndpoint(`/insights/${encodeURIComponent(id)}/`),
|
|
494
|
+
identifiers: { id, },
|
|
495
|
+
payload: {
|
|
496
|
+
insight: {
|
|
497
|
+
...jsonInput(flags),
|
|
498
|
+
name: flags["name"],
|
|
499
|
+
listed: parseBooleanOption(flags["listed"], "--listed"),
|
|
500
|
+
params: optionalJsonFlag(flags, "params"),
|
|
501
|
+
},
|
|
502
|
+
contentType: flags["content-type"],
|
|
503
|
+
payload: textInput(flags),
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
case "insight.delete":
|
|
507
|
+
return {
|
|
508
|
+
method: "DELETE",
|
|
509
|
+
endpoint: projectEndpoint(`/insights/${encodeURIComponent(id)}/`),
|
|
510
|
+
identifiers: { id, },
|
|
511
|
+
};
|
|
512
|
+
case "data-quality.create-rule":
|
|
513
|
+
return {
|
|
514
|
+
method: "POST",
|
|
515
|
+
endpoint: dataQualityEndpoint(projectKey, args[0], "/rules"),
|
|
516
|
+
identifiers: { dataset: args[0], },
|
|
517
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
518
|
+
};
|
|
519
|
+
case "data-quality.update-rule":
|
|
520
|
+
return {
|
|
521
|
+
method: "PUT",
|
|
522
|
+
endpoint: dataQualityEndpoint(projectKey, args[0], `/rules/${encodeURIComponent(args[1])}`),
|
|
523
|
+
identifiers: { dataset: args[0], ruleId: args[1], },
|
|
524
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
525
|
+
};
|
|
526
|
+
case "data-quality.delete-rule":
|
|
527
|
+
return {
|
|
528
|
+
method: "DELETE",
|
|
529
|
+
endpoint: dataQualityEndpoint(projectKey, args[0], `/rules/${encodeURIComponent(args[1])}${querySuffix({ ruleId: args[1], })}`),
|
|
530
|
+
identifiers: { dataset: args[0], ruleId: args[1], },
|
|
531
|
+
};
|
|
532
|
+
case "data-quality.compute":
|
|
533
|
+
return {
|
|
534
|
+
method: "POST",
|
|
535
|
+
endpoint: dataQualityEndpoint(projectKey, args[0], `/actions/compute-rules${querySuffix({
|
|
536
|
+
partition: flags["partition"] ?? "NP",
|
|
537
|
+
ruleId: flags["rule-id"],
|
|
538
|
+
})}`),
|
|
539
|
+
identifiers: { dataset: args[0], ruleId: flags["rule-id"], },
|
|
540
|
+
wait: flags["wait"] === true,
|
|
541
|
+
};
|
|
542
|
+
case "data-quality.delete-history": {
|
|
543
|
+
const partition = flags["partition"] ?? "NP";
|
|
544
|
+
return {
|
|
545
|
+
method: "DELETE",
|
|
546
|
+
endpoint: dataQualityEndpoint(projectKey, args[0], `/history/${encodeURIComponent(partition)}`),
|
|
547
|
+
identifiers: { dataset: args[0], partition, },
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
case "dataset.sync-hive-metastore":
|
|
551
|
+
return {
|
|
552
|
+
method: "POST",
|
|
553
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/actions/synchronizeHiveMetastore`),
|
|
554
|
+
identifiers: { name: id, },
|
|
555
|
+
};
|
|
556
|
+
case "dataset.update-from-hive":
|
|
557
|
+
return {
|
|
558
|
+
method: "POST",
|
|
559
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/actions/updateFromHive`),
|
|
560
|
+
identifiers: { name: id, },
|
|
561
|
+
};
|
|
562
|
+
case "metrics.dataset-run-checks": {
|
|
563
|
+
const partitions = flags["partitions"];
|
|
564
|
+
const data = unknownJsonInput(flags);
|
|
565
|
+
return {
|
|
566
|
+
method: "POST",
|
|
567
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/actions/runChecks/${partitions ? `?partitions=${encodeURIComponent(partitions)}` : ""}`),
|
|
568
|
+
identifiers: { dataset: id, },
|
|
569
|
+
...(data !== undefined ? { payload: data, } : {}),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
case "workspace.remove-object":
|
|
573
|
+
return {
|
|
574
|
+
method: "DELETE",
|
|
575
|
+
endpoint: `/public/api/workspaces/${encodeURIComponent(args[0])}/objects/${encodeURIComponent(args[1])}`,
|
|
576
|
+
identifiers: { workspaceKey: args[0], workspaceObjectId: args[1], },
|
|
577
|
+
};
|
|
578
|
+
case "discussion.update": {
|
|
579
|
+
const endpoint = projectEndpoint(`/discussions/${encodeURIComponent(args[0])}/${encodeURIComponent(args[1])}/${encodeURIComponent(args[2])}`);
|
|
580
|
+
const body = jsonInput(flags);
|
|
581
|
+
return body
|
|
582
|
+
? { method: "PUT", endpoint, identifiers: { discussionId: args[2], }, payload: body, }
|
|
583
|
+
: {
|
|
584
|
+
exact: false,
|
|
585
|
+
reason: "--topic edits the current discussion: DSS returns it first (GET), then the edited object is PUT.",
|
|
586
|
+
method: "PUT",
|
|
587
|
+
endpoint,
|
|
588
|
+
identifiers: { discussionId: args[2], },
|
|
589
|
+
requests: [
|
|
590
|
+
{ method: "GET", endpoint, },
|
|
591
|
+
{ method: "PUT", endpoint, payloadEdits: { topic: flags["topic"], }, },
|
|
592
|
+
],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
case "wiki.update-settings":
|
|
596
|
+
return {
|
|
597
|
+
method: "PUT",
|
|
598
|
+
endpoint: projectEndpoint("/wiki/"),
|
|
599
|
+
identifiers: {},
|
|
600
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
601
|
+
};
|
|
602
|
+
case "streaming-endpoint.create-managed":
|
|
603
|
+
return {
|
|
604
|
+
method: "POST",
|
|
605
|
+
endpoint: projectEndpoint("/streamingendpoints/managed"),
|
|
606
|
+
identifiers: { id, },
|
|
607
|
+
payload: {
|
|
608
|
+
id,
|
|
609
|
+
creationSettings: {
|
|
610
|
+
connectionId: requiredPlanFlag(flags, "connection", entry.usage),
|
|
611
|
+
...(typeof flags["format-option"] === "string"
|
|
612
|
+
? { formatOptionId: flags["format-option"], }
|
|
613
|
+
: {}),
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
case "streaming-endpoint.set-schema":
|
|
618
|
+
return {
|
|
619
|
+
method: "PUT",
|
|
620
|
+
endpoint: projectEndpoint(`/streamingendpoints/${encodeURIComponent(id)}/schema`),
|
|
621
|
+
identifiers: { id, },
|
|
622
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
623
|
+
};
|
|
624
|
+
case "webapp.trust":
|
|
625
|
+
return {
|
|
626
|
+
method: "POST",
|
|
627
|
+
endpoint: projectEndpoint(`/webapps/${encodeURIComponent(id)}/actions/trust${flags["for-everybody"] === true ? "?trustForEverybody=true" : ""}`),
|
|
628
|
+
identifiers: { id, },
|
|
629
|
+
};
|
|
630
|
+
case "project.push-to-git-remote":
|
|
631
|
+
return {
|
|
632
|
+
method: "POST",
|
|
633
|
+
endpoint: projectEndpoint(`/actions/push-to-git-remote?remote=${encodeURIComponent(id)}`),
|
|
634
|
+
identifiers: { remote: id, },
|
|
635
|
+
};
|
|
636
|
+
case "future.abort":
|
|
637
|
+
return {
|
|
638
|
+
method: "DELETE",
|
|
639
|
+
endpoint: `/public/api/futures/${encodeURIComponent(id)}`,
|
|
640
|
+
identifiers: { id, },
|
|
641
|
+
};
|
|
642
|
+
case "mlflow.set-inference-info":
|
|
643
|
+
return {
|
|
644
|
+
method: "POST",
|
|
645
|
+
endpoint: "/public/api/api/2.0/mlflow/extension/set-run-inference-info",
|
|
646
|
+
identifiers: { runId: id, trackingProjectKey: requiredProjectKey(), },
|
|
647
|
+
payload: inferenceInfoBody(inferenceInfoFromFlags(id, flags)),
|
|
648
|
+
};
|
|
649
|
+
case "mlflow.deploy-run": {
|
|
650
|
+
const options = deployRunOptionsFromFlags(id, args[1], flags);
|
|
651
|
+
const query = deployRunQuery({ ...options, versionId: options.versionId ?? "<timestamp>", }, requiredProjectKey());
|
|
652
|
+
return {
|
|
653
|
+
...(options.versionId === undefined
|
|
654
|
+
? {
|
|
655
|
+
exact: false,
|
|
656
|
+
reason: "Without --version-id the SDK names the version after the current time.",
|
|
657
|
+
}
|
|
658
|
+
: {}),
|
|
659
|
+
method: "POST",
|
|
660
|
+
endpoint: `/public/api/api/2.0/mlflow/extension/deploy-run?${query}`,
|
|
661
|
+
identifiers: { runId: id, savedModelId: args[1], versionId: options.versionId, },
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
case "mlflow.create-experiments-dataset":
|
|
665
|
+
return {
|
|
666
|
+
method: "POST",
|
|
667
|
+
endpoint: "/public/api/api/2.0/mlflow/extension/create-project-experiments-dataset",
|
|
668
|
+
identifiers: { dataset: id, trackingProjectKey: requiredProjectKey(), },
|
|
669
|
+
payload: experimentsDatasetBody(experimentsDatasetOptionsFromFlags(id, flags)),
|
|
670
|
+
};
|
|
671
|
+
case "mlflow.garbage-collect":
|
|
672
|
+
return {
|
|
673
|
+
method: "POST",
|
|
674
|
+
endpoint: "/public/api/api/2.0/mlflow/extension/garbage-collect",
|
|
675
|
+
identifiers: { trackingProjectKey: requiredProjectKey(), },
|
|
676
|
+
};
|
|
677
|
+
case "mlflow.clean-db":
|
|
678
|
+
return {
|
|
679
|
+
method: "DELETE",
|
|
680
|
+
endpoint: `/public/api/api/2.0/mlflow/extension/clean-db/${encodeURIComponent(requiredProjectKey())}`,
|
|
681
|
+
identifiers: { projectKey, },
|
|
682
|
+
};
|
|
683
|
+
case "flow.propagate-schema":
|
|
684
|
+
return {
|
|
685
|
+
method: "POST",
|
|
686
|
+
endpoint: projectEndpoint("/flow/tools/propagate-schema/"),
|
|
687
|
+
identifiers: { dataset: args[0], },
|
|
688
|
+
payload: schemaPropagationBody(projectKey, args[0], schemaPropagationOptionsFromFlags(flags)),
|
|
689
|
+
wait: flags["wait"] === true,
|
|
690
|
+
};
|
|
691
|
+
case "flow-zone.create": {
|
|
692
|
+
const name = flowZoneName(flags["name"]);
|
|
693
|
+
const payload = { name, color: flowZoneColor(flags["color"]) ?? "#2ab1ac", };
|
|
694
|
+
return {
|
|
695
|
+
method: "POST",
|
|
696
|
+
endpoint: projectEndpoint("/flow/zones"),
|
|
697
|
+
identifiers: { name, },
|
|
698
|
+
payload,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
case "flow-zone.update":
|
|
702
|
+
return {
|
|
703
|
+
method: "PUT",
|
|
704
|
+
endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}`),
|
|
705
|
+
identifiers: { id, },
|
|
706
|
+
payload: {
|
|
707
|
+
name: typeof flags["name"] === "string" ? flowZoneName(flags["name"]) : undefined,
|
|
708
|
+
color: flowZoneColor(flags["color"]),
|
|
709
|
+
projectKey,
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
case "flow-zone.delete":
|
|
713
|
+
return {
|
|
714
|
+
method: "DELETE",
|
|
715
|
+
endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}`),
|
|
716
|
+
identifiers: { id, },
|
|
717
|
+
};
|
|
718
|
+
case "flow-zone.move":
|
|
719
|
+
return {
|
|
720
|
+
method: "POST",
|
|
721
|
+
endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}/add-items`),
|
|
722
|
+
identifiers: { id, },
|
|
723
|
+
payload: flowZoneMoveItems(flags),
|
|
724
|
+
};
|
|
725
|
+
case "dataset.create": {
|
|
726
|
+
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
727
|
+
const connection = flags["connection"];
|
|
728
|
+
const dsType = requiredPlanFlag(flags, "type", entry.usage);
|
|
729
|
+
assertDatasetTypeCreatable(dsType);
|
|
730
|
+
if (!connection && dsType.toLowerCase() !== "uploadedfiles") {
|
|
731
|
+
throw new UsageError("--connection is required unless --type is UploadedFiles.");
|
|
732
|
+
}
|
|
733
|
+
const endpoint = projectEndpoint("/datasets/"); // throws without a project key
|
|
734
|
+
return {
|
|
735
|
+
method: "POST",
|
|
736
|
+
endpoint,
|
|
737
|
+
identifiers: { name, },
|
|
738
|
+
payload: buildDatasetCreateBody({
|
|
739
|
+
projectKey: projectKey,
|
|
740
|
+
datasetName: name,
|
|
741
|
+
connection,
|
|
742
|
+
dsType,
|
|
743
|
+
}),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
case "dataset.clone": {
|
|
747
|
+
const source = args[0];
|
|
748
|
+
const target = args[1];
|
|
749
|
+
return {
|
|
750
|
+
exact: false,
|
|
751
|
+
reason: "Apply GETs the source dataset and copies its current connection, format, and schema into the new dataset body.",
|
|
752
|
+
method: "POST",
|
|
753
|
+
endpoint: projectEndpoint("/datasets/"),
|
|
754
|
+
identifiers: { source, target, },
|
|
755
|
+
payload: {
|
|
756
|
+
sourceDataset: source,
|
|
757
|
+
targetDataset: target,
|
|
758
|
+
path: flags["path"],
|
|
759
|
+
table: flags["table"],
|
|
760
|
+
metastoreTableName: flags["metastore-table"],
|
|
761
|
+
allowSamePath: flags["allow-same-path"] === true,
|
|
762
|
+
projectKey,
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
case "dataset.rename":
|
|
767
|
+
return {
|
|
768
|
+
method: "POST",
|
|
769
|
+
endpoint: projectEndpoint("/actions/renameDataset"),
|
|
770
|
+
identifiers: { oldName: args[0], newName: args[1], },
|
|
771
|
+
payload: { oldName: args[0], newName: args[1], },
|
|
772
|
+
};
|
|
773
|
+
case "dataset.upload-file": {
|
|
774
|
+
const fileName = requiredPlanFlag(flags, "file-name", entry.usage);
|
|
775
|
+
const datasetEndpoint = projectEndpoint(`/datasets/${encodeURIComponent(args[0])}`);
|
|
776
|
+
const filesEndpoint = `${datasetEndpoint}/uploaded/files`;
|
|
777
|
+
const upload = {
|
|
778
|
+
method: "POST",
|
|
779
|
+
endpoint: filesEndpoint,
|
|
780
|
+
payload: { ...uploadPayload(args[1]), fileName, },
|
|
781
|
+
};
|
|
782
|
+
return {
|
|
783
|
+
...upload,
|
|
784
|
+
identifiers: {
|
|
785
|
+
datasetName: args[0],
|
|
786
|
+
localPath: args[1],
|
|
787
|
+
fileName,
|
|
788
|
+
},
|
|
789
|
+
requests: [
|
|
790
|
+
{ sequence: 1, method: "GET", endpoint: datasetEndpoint, },
|
|
791
|
+
{ sequence: 2, method: "GET", endpoint: filesEndpoint, },
|
|
792
|
+
{ sequence: 3, ...upload, },
|
|
793
|
+
{
|
|
794
|
+
sequence: 4,
|
|
795
|
+
method: "GET",
|
|
796
|
+
endpoint: filesEndpoint,
|
|
797
|
+
assert: {
|
|
798
|
+
filename: fileName,
|
|
799
|
+
length: "local file byte length",
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
],
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
case "dataset.delete":
|
|
806
|
+
return {
|
|
807
|
+
method: "DELETE",
|
|
808
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}`),
|
|
809
|
+
identifiers: { name: id, },
|
|
810
|
+
};
|
|
811
|
+
case "dataset.refresh-schema": {
|
|
812
|
+
const columns = schemaColumnsInput(flags, entry.usage);
|
|
813
|
+
return {
|
|
814
|
+
method: "PUT",
|
|
815
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/schema`),
|
|
816
|
+
identifiers: { name: id, },
|
|
817
|
+
payload: { columns, },
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
case "dataset.update":
|
|
821
|
+
return {
|
|
822
|
+
method: "PUT",
|
|
823
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}`),
|
|
824
|
+
identifiers: { name: id, },
|
|
825
|
+
};
|
|
826
|
+
case "dataset.metadata-set":
|
|
827
|
+
return {
|
|
828
|
+
method: "PUT",
|
|
829
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/metadata`),
|
|
830
|
+
identifiers: { name: id, },
|
|
831
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
832
|
+
};
|
|
833
|
+
case "dataset.create-managed": {
|
|
834
|
+
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
835
|
+
const connection = requiredPlanFlag(flags, "connection", entry.usage);
|
|
836
|
+
const creationSettings = { connectionId: connection, };
|
|
837
|
+
const specificSettings = {};
|
|
838
|
+
if (typeof flags["type-option-id"] === "string") {
|
|
839
|
+
creationSettings.typeOptionId = flags["type-option-id"];
|
|
840
|
+
}
|
|
841
|
+
if (typeof flags["format-option-id"] === "string") {
|
|
842
|
+
specificSettings.formatOptionId = flags["format-option-id"];
|
|
843
|
+
}
|
|
844
|
+
if (typeof flags["copy-partitioning-from"] === "string") {
|
|
845
|
+
specificSettings.partitioningOptionId = `copy:${flags["partitioning-folder"] === true ? "folder" : "dataset"}:${flags["copy-partitioning-from"]}`;
|
|
846
|
+
}
|
|
847
|
+
if (Object.keys(specificSettings).length > 0) {
|
|
848
|
+
creationSettings.specificSettings = specificSettings;
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
method: "POST",
|
|
852
|
+
endpoint: projectEndpoint("/datasets/managed"),
|
|
853
|
+
identifiers: { name, },
|
|
854
|
+
payload: { name, creationSettings, },
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
case "recipe.add-input":
|
|
858
|
+
case "recipe.remove-input": {
|
|
859
|
+
const role = flags["role"] ?? "main";
|
|
860
|
+
return {
|
|
861
|
+
method: "PUT",
|
|
862
|
+
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(args[0])}`),
|
|
863
|
+
identifiers: { recipe: args[0], dataset: args[1], role, },
|
|
864
|
+
payload: {
|
|
865
|
+
operation: action === "add-input" ? "append" : "remove",
|
|
866
|
+
dataset: args[1],
|
|
867
|
+
role,
|
|
868
|
+
projectKey,
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
case "recipe.clone": {
|
|
873
|
+
const positionalSource = args[0];
|
|
874
|
+
const fromFlag = typeof flags["from"] === "string" ? flags["from"].trim() : "";
|
|
875
|
+
const source = positionalSource ?? fromFlag;
|
|
876
|
+
if (!source) {
|
|
877
|
+
throw new UsageError(`Source recipe is required. Usage: ${entry.usage}`, "missing_required_flag");
|
|
878
|
+
}
|
|
879
|
+
if (positionalSource && fromFlag && positionalSource !== fromFlag) {
|
|
880
|
+
throw new UsageError("Positional source and --from must match when both are provided.", "invalid_enum");
|
|
881
|
+
}
|
|
882
|
+
const toFlag = typeof flags["to"] === "string" ? flags["to"].trim() : "";
|
|
883
|
+
const nameFlag = typeof flags["name"] === "string" ? flags["name"].trim() : "";
|
|
884
|
+
const target = toFlag || nameFlag;
|
|
885
|
+
if (!target) {
|
|
886
|
+
throw new UsageError(`--name or --to is required. Usage: ${entry.usage}`, "missing_required_flag");
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
method: "POST",
|
|
890
|
+
endpoint: projectEndpoint("/recipes/"),
|
|
891
|
+
identifiers: { source, target, },
|
|
892
|
+
payload: {
|
|
893
|
+
sourceRecipe: source,
|
|
894
|
+
targetRecipe: target,
|
|
895
|
+
inputRewrites: rewritePairsFromFlags(flags, "replace-input"),
|
|
896
|
+
outputRewrites: rewritePairsFromFlags(flags, "replace-output"),
|
|
897
|
+
payloadTextRewrites: rewritePairsFromFlags(flags, "replace-payload-text"),
|
|
898
|
+
outputDataset: flags["output"],
|
|
899
|
+
copyOutputSettings: flags["copy-output-settings"] === true,
|
|
900
|
+
outputPath: flags["path"],
|
|
901
|
+
metastoreTableName: flags["metastore-table"],
|
|
902
|
+
projectKey,
|
|
903
|
+
},
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
case "recipe.delete":
|
|
907
|
+
return {
|
|
908
|
+
method: "DELETE",
|
|
909
|
+
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
|
|
910
|
+
identifiers: { name: id, },
|
|
911
|
+
};
|
|
912
|
+
case "recipe.create": {
|
|
913
|
+
requiredPlanFlag(flags, "type", entry.usage);
|
|
914
|
+
const outputDataset = flags["output"];
|
|
915
|
+
const outputFolder = flags["output-folder"];
|
|
916
|
+
if (outputDataset && outputFolder) {
|
|
917
|
+
throw new UsageError("--output and --output-folder are mutually exclusive.");
|
|
918
|
+
}
|
|
919
|
+
if (!outputDataset && !outputFolder) {
|
|
920
|
+
throw new UsageError("--output or --output-folder is required.");
|
|
921
|
+
}
|
|
922
|
+
if (outputFolder && !flags["output-connection"]) {
|
|
923
|
+
throw new UsageError("--output-connection is required when using --output-folder.");
|
|
924
|
+
}
|
|
925
|
+
const endpoint = projectEndpoint("/recipes/"); // throws without a project key
|
|
926
|
+
// Same flag mapping and body construction as the handler and RecipesResource.create.
|
|
927
|
+
const { recipePrototype, creationSettings, grouping, prepareSteps, } = buildRecipeCreateRequest(recipeCreateOptionsFromFlags(flags), projectKey);
|
|
928
|
+
const edits = grouping
|
|
929
|
+
? { grouping, }
|
|
930
|
+
: prepareSteps.length > 0
|
|
931
|
+
? { steps: prepareSteps, }
|
|
932
|
+
: undefined;
|
|
933
|
+
const recipePath = projectEndpoint(`/recipes/${encodeURIComponent(recipePrototype.name)}`);
|
|
934
|
+
return {
|
|
935
|
+
method: "POST",
|
|
936
|
+
endpoint,
|
|
937
|
+
identifiers: { name: recipePrototype.name, },
|
|
938
|
+
payload: { recipePrototype, creationSettings, },
|
|
939
|
+
// Grouping keys/aggregates and prepare steps are written into the payload DSS creates.
|
|
940
|
+
...(edits
|
|
941
|
+
? {
|
|
942
|
+
requests: [
|
|
943
|
+
{ method: "POST", endpoint, payload: { recipePrototype, creationSettings, }, },
|
|
944
|
+
{ method: "GET", endpoint: recipePath, },
|
|
945
|
+
{ method: "PUT", endpoint: recipePath, payloadEdits: edits, },
|
|
946
|
+
],
|
|
947
|
+
}
|
|
948
|
+
: {}),
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
case "recipe.run":
|
|
952
|
+
return {
|
|
953
|
+
exact: false,
|
|
954
|
+
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.",
|
|
955
|
+
method: "POST",
|
|
956
|
+
endpoint: projectEndpoint("/jobs/"),
|
|
957
|
+
identifiers: { recipe: id, },
|
|
958
|
+
wait: recipeRunShouldWait(flags),
|
|
959
|
+
};
|
|
960
|
+
case "code.run": {
|
|
961
|
+
const { script, source, } = resolveCodeInputWithSource(args, flags);
|
|
962
|
+
const sourceSha256 = sha256Hex(script);
|
|
963
|
+
const scenarioBase = projectEndpoint("/scenarios/{generatedScenarioId}");
|
|
964
|
+
const envName = flags["env"];
|
|
965
|
+
const keepScenario = flags["keep"] === true;
|
|
966
|
+
const timeoutMs = parseCodeRunIntegerFlag(flags["timeout"], "--timeout") ?? 120_000;
|
|
967
|
+
const maxLogBytes = flags["full-log"] === true
|
|
968
|
+
? 0
|
|
969
|
+
: parseCodeRunIntegerFlag(flags["max-log-bytes"], "--max-log-bytes") ?? 1_048_576;
|
|
970
|
+
return {
|
|
971
|
+
method: "POST",
|
|
972
|
+
endpoint: projectEndpoint("/scenarios/"),
|
|
973
|
+
identifiers: {
|
|
974
|
+
source,
|
|
975
|
+
sourceSha256,
|
|
976
|
+
sourceBytes: Buffer.byteLength(script),
|
|
977
|
+
},
|
|
978
|
+
requests: [
|
|
979
|
+
{
|
|
980
|
+
method: "POST",
|
|
981
|
+
endpoint: projectEndpoint("/scenarios/"),
|
|
982
|
+
payload: {
|
|
983
|
+
id: "{generatedScenarioId}",
|
|
984
|
+
name: "dss code run ({generatedScenarioId})",
|
|
985
|
+
projectKey,
|
|
986
|
+
type: "custom_python",
|
|
987
|
+
params: {
|
|
988
|
+
envSelection: envName
|
|
989
|
+
? { envMode: "EXPLICIT_ENV", envName, }
|
|
990
|
+
: { envMode: "INHERIT", },
|
|
991
|
+
},
|
|
992
|
+
},
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
method: "PUT",
|
|
996
|
+
endpoint: `${scenarioBase}/payload`,
|
|
997
|
+
payload: { extension: "py", script: "<omitted>", },
|
|
998
|
+
redactedFields: ["payload.script",],
|
|
999
|
+
},
|
|
1000
|
+
{ method: "POST", endpoint: `${scenarioBase}/run/`, payload: {}, },
|
|
1001
|
+
{
|
|
1002
|
+
method: "GET",
|
|
1003
|
+
endpoint: `${scenarioBase}/get-run-for-trigger?triggerId={triggerId}&triggerRunId={triggerRunId}`,
|
|
1004
|
+
repeat: "until scenarioRun.result.outcome or timeout",
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
method: "GET",
|
|
1008
|
+
endpoint: `${scenarioBase}/{runId}/log`,
|
|
1009
|
+
boundedBytes: maxLogBytes,
|
|
1010
|
+
},
|
|
1011
|
+
...keepScenario ? [] : [{ method: "DELETE", endpoint: scenarioBase, cleanup: true, },],
|
|
1012
|
+
],
|
|
1013
|
+
wait: {
|
|
1014
|
+
timeoutMs,
|
|
1015
|
+
pollEndpoint: `${scenarioBase}/get-run-for-trigger`,
|
|
1016
|
+
},
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
case "recipe.update":
|
|
1020
|
+
return {
|
|
1021
|
+
method: "PUT",
|
|
1022
|
+
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
|
|
1023
|
+
identifiers: { name: id, },
|
|
1024
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1025
|
+
};
|
|
1026
|
+
case "recipe.update-schema": {
|
|
1027
|
+
const recipePath = `/recipes/${encodeURIComponent(id)}`;
|
|
1028
|
+
return {
|
|
1029
|
+
exact: false,
|
|
1030
|
+
reason: "DSS computes the output schemas from the live recipe; one POST is sent per output whose computed schema differs. Use recipe update-schema --dry-run to list them.",
|
|
1031
|
+
method: "POST",
|
|
1032
|
+
endpoint: projectEndpoint(`${recipePath}/actions/updateOutputSchema`),
|
|
1033
|
+
identifiers: { name: id, },
|
|
1034
|
+
requests: [
|
|
1035
|
+
{ method: "GET", endpoint: projectEndpoint(recipePath), },
|
|
1036
|
+
{ method: "GET", endpoint: projectEndpoint(`${recipePath}/schema-update`), },
|
|
1037
|
+
{
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
endpoint: projectEndpoint(`${recipePath}/actions/updateOutputSchema`),
|
|
1040
|
+
payload: { computableType: "{type}", computableId: "{id}", newSchema: "{newSchema}", },
|
|
1041
|
+
repeat: "per output whose computed schema differs",
|
|
1042
|
+
},
|
|
1043
|
+
],
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
case "recipe.set-payload": {
|
|
1047
|
+
const file = requiredPlanFlag(flags, "file", entry.usage);
|
|
1048
|
+
const backupDir = flags["no-backup"] === true
|
|
1049
|
+
? undefined
|
|
1050
|
+
: flags["backup-dir"]
|
|
1051
|
+
?? path.join(process.cwd(), ".dss-backups", "recipes");
|
|
1052
|
+
const backupPath = backupDir ? recipeBackupPath(id, backupDir) : undefined;
|
|
1053
|
+
return {
|
|
1054
|
+
method: "PUT",
|
|
1055
|
+
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
|
|
1056
|
+
identifiers: { name: id, },
|
|
1057
|
+
payload: {
|
|
1058
|
+
file,
|
|
1059
|
+
content: textInput(flags),
|
|
1060
|
+
...(backupPath ? { backupPath, } : {}),
|
|
1061
|
+
},
|
|
1062
|
+
...(backupPath
|
|
1063
|
+
? { localWrites: [{ path: backupPath, source: "remote recipe backup", before: "PUT", },], }
|
|
1064
|
+
: {}),
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
case "job.build":
|
|
1068
|
+
case "job.build-and-wait":
|
|
1069
|
+
return {
|
|
1070
|
+
method: "POST",
|
|
1071
|
+
endpoint: projectEndpoint("/jobs/"),
|
|
1072
|
+
identifiers: { target: id, },
|
|
1073
|
+
payload: jobBuildPayload(id, projectKey, flags),
|
|
1074
|
+
wait: action === "build-and-wait" || flags["wait"] === true,
|
|
1075
|
+
};
|
|
1076
|
+
case "scenario.abort":
|
|
1077
|
+
return {
|
|
1078
|
+
method: "POST",
|
|
1079
|
+
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/abort`),
|
|
1080
|
+
identifiers: { id, },
|
|
1081
|
+
};
|
|
1082
|
+
case "scenario.payload-set":
|
|
1083
|
+
return {
|
|
1084
|
+
method: "PUT",
|
|
1085
|
+
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/payload`),
|
|
1086
|
+
identifiers: { id, },
|
|
1087
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1088
|
+
};
|
|
1089
|
+
case "scenario.active-set": {
|
|
1090
|
+
const activeRaw = args[1];
|
|
1091
|
+
if (activeRaw !== "true" && activeRaw !== "false") {
|
|
1092
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
1093
|
+
}
|
|
1094
|
+
const lightEndpoint = projectEndpoint(`/scenarios/${encodeURIComponent(id)}/light`);
|
|
1095
|
+
const active = activeRaw === "true";
|
|
1096
|
+
// DSS parses the light PUT as a full Scenario, so the command echoes the
|
|
1097
|
+
// current light status with `active` overridden (see ScenariosResource.setActive).
|
|
1098
|
+
return {
|
|
1099
|
+
method: "PUT",
|
|
1100
|
+
endpoint: lightEndpoint,
|
|
1101
|
+
identifiers: { id, },
|
|
1102
|
+
payload: { active, },
|
|
1103
|
+
requests: [
|
|
1104
|
+
{ sequence: 1, method: "GET", endpoint: lightEndpoint, },
|
|
1105
|
+
{
|
|
1106
|
+
sequence: 2,
|
|
1107
|
+
method: "PUT",
|
|
1108
|
+
endpoint: lightEndpoint,
|
|
1109
|
+
payload: { "...current": true, active, },
|
|
1110
|
+
},
|
|
1111
|
+
{ sequence: 3, method: "GET", endpoint: lightEndpoint, },
|
|
1112
|
+
],
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
case "folder.create": {
|
|
1116
|
+
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
1117
|
+
const type = flags["type"];
|
|
1118
|
+
const connection = flags["connection"];
|
|
1119
|
+
const pathFlag = flags["path"];
|
|
1120
|
+
const body = {
|
|
1121
|
+
name,
|
|
1122
|
+
projectKey,
|
|
1123
|
+
type: type ?? null,
|
|
1124
|
+
params: { connection, path: pathFlag?.trim() || "/${projectKey}/${odbId}", },
|
|
1125
|
+
};
|
|
1126
|
+
// Mirrors FoldersResource.create.
|
|
1127
|
+
if (connection === undefined) {
|
|
1128
|
+
return {
|
|
1129
|
+
exact: false,
|
|
1130
|
+
reason: "Without --connection, apply reads DSS admin settings to pick the managed-folder connection (falling back to filesystem_folders); params.connection is set then.",
|
|
1131
|
+
method: "POST",
|
|
1132
|
+
endpoint: projectEndpoint("/managedfolders/"),
|
|
1133
|
+
identifiers: { name, },
|
|
1134
|
+
payload: body,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
return {
|
|
1138
|
+
method: "POST",
|
|
1139
|
+
endpoint: projectEndpoint("/managedfolders/"),
|
|
1140
|
+
identifiers: { name, },
|
|
1141
|
+
payload: body,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
case "job.abort":
|
|
1145
|
+
return {
|
|
1146
|
+
method: "POST",
|
|
1147
|
+
endpoint: projectEndpoint(`/jobs/${encodeURIComponent(id)}/abort/`),
|
|
1148
|
+
identifiers: { id, },
|
|
1149
|
+
};
|
|
1150
|
+
case "scenario.run":
|
|
1151
|
+
case "scenario.run-and-wait":
|
|
1152
|
+
return {
|
|
1153
|
+
method: "POST",
|
|
1154
|
+
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/run/`),
|
|
1155
|
+
identifiers: { id, },
|
|
1156
|
+
payload: {},
|
|
1157
|
+
wait: action === "run-and-wait" || flags["wait"] === true,
|
|
1158
|
+
};
|
|
1159
|
+
case "scenario.delete":
|
|
1160
|
+
return {
|
|
1161
|
+
method: "DELETE",
|
|
1162
|
+
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/`),
|
|
1163
|
+
identifiers: { id, },
|
|
1164
|
+
};
|
|
1165
|
+
case "scenario.create": {
|
|
1166
|
+
const type = flags["type"] ?? "step_based";
|
|
1167
|
+
return {
|
|
1168
|
+
method: "POST",
|
|
1169
|
+
endpoint: projectEndpoint("/scenarios/"),
|
|
1170
|
+
identifiers: { id: args[0], name: args[1], },
|
|
1171
|
+
// Mirrors ScenariosResource.create's default params for step-based scenarios.
|
|
1172
|
+
payload: {
|
|
1173
|
+
id: args[0],
|
|
1174
|
+
name: args[1],
|
|
1175
|
+
projectKey,
|
|
1176
|
+
type,
|
|
1177
|
+
params: type === "step_based" ? { steps: [], triggers: [], reporters: [], } : {},
|
|
1178
|
+
},
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
case "scenario.update":
|
|
1182
|
+
return {
|
|
1183
|
+
method: "PUT",
|
|
1184
|
+
endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/`),
|
|
1185
|
+
identifiers: { id, },
|
|
1186
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1187
|
+
};
|
|
1188
|
+
case "folder.update":
|
|
1189
|
+
return {
|
|
1190
|
+
method: "PUT",
|
|
1191
|
+
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(id)}`),
|
|
1192
|
+
identifiers: { folder: id, },
|
|
1193
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1194
|
+
};
|
|
1195
|
+
case "folder.delete":
|
|
1196
|
+
return {
|
|
1197
|
+
method: "DELETE",
|
|
1198
|
+
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(id)}`),
|
|
1199
|
+
identifiers: { folder: id, },
|
|
1200
|
+
};
|
|
1201
|
+
case "folder.upload":
|
|
1202
|
+
return {
|
|
1203
|
+
method: "POST",
|
|
1204
|
+
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(args[0])}/contents/${encodeURIComponent(args[1])}`),
|
|
1205
|
+
identifiers: { folder: args[0], path: args[1], localPath: args[2], },
|
|
1206
|
+
};
|
|
1207
|
+
case "folder.delete-file":
|
|
1208
|
+
return {
|
|
1209
|
+
method: "DELETE",
|
|
1210
|
+
endpoint: projectEndpoint(`/managedfolders/${encodeURIComponent(args[0])}/contents/${encodeURIComponent(args[1])}`),
|
|
1211
|
+
identifiers: { folder: args[0], path: args[1], },
|
|
1212
|
+
};
|
|
1213
|
+
case "variable.set":
|
|
1214
|
+
return {
|
|
1215
|
+
exact: false,
|
|
1216
|
+
reason: "The payload lists the requested changes; apply PUTs the full {standard, local} object (merged with the current variables unless --replace).",
|
|
1217
|
+
method: "PUT",
|
|
1218
|
+
endpoint: projectEndpoint("/variables/"),
|
|
1219
|
+
payload: {
|
|
1220
|
+
standard: optionalJsonFlag(flags, "standard"),
|
|
1221
|
+
local: optionalJsonFlag(flags, "local"),
|
|
1222
|
+
replace: flags["replace"] === true,
|
|
1223
|
+
},
|
|
1224
|
+
};
|
|
1225
|
+
case "project-deployer.create-project": {
|
|
1226
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1227
|
+
return {
|
|
1228
|
+
method: "POST",
|
|
1229
|
+
endpoint: "/public/api/project-deployer/projects",
|
|
1230
|
+
identifiers: { projectKey: payload.projectKey, id: payload.id, },
|
|
1231
|
+
payload,
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
case "project-deployer.upload-bundle":
|
|
1235
|
+
return {
|
|
1236
|
+
method: "POST",
|
|
1237
|
+
endpoint: "/public/api/project-deployer/projects/bundles",
|
|
1238
|
+
identifiers: { filePath: id, },
|
|
1239
|
+
payload: uploadPayload(id),
|
|
1240
|
+
};
|
|
1241
|
+
case "project-deployer.create-deployment": {
|
|
1242
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1243
|
+
return {
|
|
1244
|
+
method: "POST",
|
|
1245
|
+
endpoint: "/public/api/project-deployer/deployments",
|
|
1246
|
+
identifiers: { deploymentId: payload.deploymentId ?? payload.id, },
|
|
1247
|
+
payload,
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
case "project-deployer.save-deployment-settings":
|
|
1251
|
+
return {
|
|
1252
|
+
method: "PUT",
|
|
1253
|
+
endpoint: `/public/api/project-deployer/deployments/${encodeURIComponent(id)}/settings`,
|
|
1254
|
+
identifiers: { deploymentId: id, },
|
|
1255
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1256
|
+
};
|
|
1257
|
+
case "project-deployer.deploy":
|
|
1258
|
+
return {
|
|
1259
|
+
method: "POST",
|
|
1260
|
+
endpoint: `/public/api/project-deployer/deployments/${encodeURIComponent(id)}/actions/update`,
|
|
1261
|
+
identifiers: { deploymentId: id, },
|
|
1262
|
+
payload: {},
|
|
1263
|
+
};
|
|
1264
|
+
case "project-deployer.delete-deployment":
|
|
1265
|
+
return {
|
|
1266
|
+
method: "DELETE",
|
|
1267
|
+
endpoint: `/public/api/project-deployer/deployments/${encodeURIComponent(id)}`,
|
|
1268
|
+
identifiers: { deploymentId: id, },
|
|
1269
|
+
};
|
|
1270
|
+
case "project-deployer.create-infra": {
|
|
1271
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1272
|
+
return {
|
|
1273
|
+
method: "POST",
|
|
1274
|
+
endpoint: "/public/api/project-deployer/infras",
|
|
1275
|
+
identifiers: { infraId: payload.id, },
|
|
1276
|
+
payload,
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
case "project-deployer.save-infra-settings":
|
|
1280
|
+
return {
|
|
1281
|
+
method: "PUT",
|
|
1282
|
+
endpoint: `/public/api/project-deployer/infras/${encodeURIComponent(id)}/settings`,
|
|
1283
|
+
identifiers: { infraId: id, },
|
|
1284
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1285
|
+
};
|
|
1286
|
+
case "project-deployer.delete-infra":
|
|
1287
|
+
return {
|
|
1288
|
+
method: "DELETE",
|
|
1289
|
+
endpoint: `/public/api/project-deployer/infras/${encodeURIComponent(id)}`,
|
|
1290
|
+
identifiers: { infraId: id, },
|
|
1291
|
+
};
|
|
1292
|
+
case "project-deployer.delete-project":
|
|
1293
|
+
return {
|
|
1294
|
+
method: "DELETE",
|
|
1295
|
+
endpoint: `/public/api/project-deployer/projects/${encodeURIComponent(id)}`,
|
|
1296
|
+
identifiers: { publishedProjectKey: id, },
|
|
1297
|
+
};
|
|
1298
|
+
case "project-deployer.save-project-settings":
|
|
1299
|
+
return {
|
|
1300
|
+
method: "PUT",
|
|
1301
|
+
endpoint: `/public/api/project-deployer/projects/${encodeURIComponent(id)}/settings`,
|
|
1302
|
+
identifiers: { publishedProjectKey: id, },
|
|
1303
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1304
|
+
};
|
|
1305
|
+
case "project-deployer.delete-bundle":
|
|
1306
|
+
return {
|
|
1307
|
+
method: "DELETE",
|
|
1308
|
+
endpoint: `/public/api/project-deployer/projects/${encodeURIComponent(id)}/bundles/${encodeURIComponent(args[1])}`,
|
|
1309
|
+
identifiers: { publishedProjectKey: id, bundleId: args[1], },
|
|
1310
|
+
};
|
|
1311
|
+
case "bundle.create-project": {
|
|
1312
|
+
const archivePath = flags["archive-path"];
|
|
1313
|
+
const query = querySuffix({
|
|
1314
|
+
archivePath: typeof archivePath === "string" ? archivePath : undefined,
|
|
1315
|
+
projectFolderId: flags["project-folder"],
|
|
1316
|
+
});
|
|
1317
|
+
const file = flags["file"];
|
|
1318
|
+
return typeof file === "string"
|
|
1319
|
+
? {
|
|
1320
|
+
method: "POST",
|
|
1321
|
+
endpoint: `/public/api/projectsFromBundle/${query}`,
|
|
1322
|
+
identifiers: { filePath: file, },
|
|
1323
|
+
payload: uploadPayload(file),
|
|
1324
|
+
}
|
|
1325
|
+
: {
|
|
1326
|
+
method: "POST",
|
|
1327
|
+
endpoint: `/public/api/projectsFromBundle/fromArchive${query}`,
|
|
1328
|
+
identifiers: { archivePath, },
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
case "api-deployer.create-infra": {
|
|
1332
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1333
|
+
return {
|
|
1334
|
+
method: "POST",
|
|
1335
|
+
endpoint: "/public/api/api-deployer/infras",
|
|
1336
|
+
identifiers: { infraId: payload.id, },
|
|
1337
|
+
payload,
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
case "api-deployer.delete-infra":
|
|
1341
|
+
return {
|
|
1342
|
+
method: "DELETE",
|
|
1343
|
+
endpoint: `/public/api/api-deployer/infras/${encodeURIComponent(id)}`,
|
|
1344
|
+
identifiers: { infraId: id, },
|
|
1345
|
+
};
|
|
1346
|
+
case "api-deployer.create-service": {
|
|
1347
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1348
|
+
return {
|
|
1349
|
+
method: "POST",
|
|
1350
|
+
endpoint: "/public/api/api-deployer/services",
|
|
1351
|
+
identifiers: { serviceId: payload.id ?? payload.publishedServiceId, },
|
|
1352
|
+
payload,
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
case "api-deployer.delete-service":
|
|
1356
|
+
return {
|
|
1357
|
+
method: "DELETE",
|
|
1358
|
+
endpoint: `/public/api/api-deployer/services/${encodeURIComponent(id)}`,
|
|
1359
|
+
identifiers: { serviceId: id, },
|
|
1360
|
+
};
|
|
1361
|
+
case "api-deployer.publish-version":
|
|
1362
|
+
return {
|
|
1363
|
+
method: "POST",
|
|
1364
|
+
endpoint: `/public/api/api-deployer/services/${encodeURIComponent(args[0])}/versions`,
|
|
1365
|
+
identifiers: { serviceId: args[0], filePath: args[1], },
|
|
1366
|
+
payload: uploadPayload(args[1]),
|
|
1367
|
+
};
|
|
1368
|
+
case "api-deployer.delete-version":
|
|
1369
|
+
return {
|
|
1370
|
+
method: "DELETE",
|
|
1371
|
+
endpoint: `/public/api/api-deployer/services/${encodeURIComponent(args[0])}/versions/${encodeURIComponent(args[1])}`,
|
|
1372
|
+
identifiers: { serviceId: args[0], version: args[1], },
|
|
1373
|
+
};
|
|
1374
|
+
case "api-deployer.create-deployment": {
|
|
1375
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1376
|
+
return {
|
|
1377
|
+
method: "POST",
|
|
1378
|
+
endpoint: "/public/api/api-deployer/deployments",
|
|
1379
|
+
identifiers: { deploymentId: payload.deploymentId ?? payload.id, },
|
|
1380
|
+
payload,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
case "api-deployer.save-deployment-settings":
|
|
1384
|
+
return {
|
|
1385
|
+
method: "PUT",
|
|
1386
|
+
endpoint: `/public/api/api-deployer/deployments/${encodeURIComponent(id)}/settings`,
|
|
1387
|
+
identifiers: { deploymentId: id, },
|
|
1388
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1389
|
+
};
|
|
1390
|
+
case "api-deployer.deploy":
|
|
1391
|
+
return {
|
|
1392
|
+
method: "POST",
|
|
1393
|
+
endpoint: `/public/api/api-deployer/deployments/${encodeURIComponent(id)}/actions/update`,
|
|
1394
|
+
identifiers: { deploymentId: id, },
|
|
1395
|
+
payload: {},
|
|
1396
|
+
};
|
|
1397
|
+
case "api-deployer.delete-deployment":
|
|
1398
|
+
return {
|
|
1399
|
+
method: "DELETE",
|
|
1400
|
+
endpoint: `/public/api/api-deployer/deployments/${encodeURIComponent(id)}`,
|
|
1401
|
+
identifiers: { deploymentId: id, },
|
|
1402
|
+
};
|
|
1403
|
+
case "workspace.create": {
|
|
1404
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1405
|
+
return {
|
|
1406
|
+
method: "POST",
|
|
1407
|
+
endpoint: "/public/api/workspaces/",
|
|
1408
|
+
identifiers: { workspaceKey: payload.workspaceKey, },
|
|
1409
|
+
payload,
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
case "workspace.update-settings":
|
|
1413
|
+
return {
|
|
1414
|
+
method: "PUT",
|
|
1415
|
+
endpoint: `/public/api/workspaces/${encodeURIComponent(id)}`,
|
|
1416
|
+
identifiers: { workspaceKey: id, },
|
|
1417
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1418
|
+
};
|
|
1419
|
+
case "workspace.delete":
|
|
1420
|
+
return {
|
|
1421
|
+
method: "DELETE",
|
|
1422
|
+
endpoint: `/public/api/workspaces/${encodeURIComponent(id)}`,
|
|
1423
|
+
identifiers: { workspaceKey: id, },
|
|
1424
|
+
};
|
|
1425
|
+
case "workspace.add-object":
|
|
1426
|
+
return {
|
|
1427
|
+
method: "POST",
|
|
1428
|
+
endpoint: `/public/api/workspaces/${encodeURIComponent(id)}/objects`,
|
|
1429
|
+
identifiers: { workspaceKey: id, },
|
|
1430
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1431
|
+
};
|
|
1432
|
+
case "meaning.create": {
|
|
1433
|
+
const body = jsonInput(flags) ?? {};
|
|
1434
|
+
const payload = {
|
|
1435
|
+
...body,
|
|
1436
|
+
id: args[0],
|
|
1437
|
+
label: args[1],
|
|
1438
|
+
type: args[2],
|
|
1439
|
+
description: body.description ?? null,
|
|
1440
|
+
entries: body.entries ?? null,
|
|
1441
|
+
mappings: body.mappings ?? null,
|
|
1442
|
+
pattern: body.pattern ?? null,
|
|
1443
|
+
normalizationMode: body.normalizationMode ?? null,
|
|
1444
|
+
detectable: body.detectable ?? false,
|
|
1445
|
+
};
|
|
1446
|
+
return {
|
|
1447
|
+
method: "POST",
|
|
1448
|
+
endpoint: "/public/api/meanings/",
|
|
1449
|
+
identifiers: { id: args[0], },
|
|
1450
|
+
payload,
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
case "meaning.update":
|
|
1454
|
+
return {
|
|
1455
|
+
method: "PUT",
|
|
1456
|
+
endpoint: `/public/api/meanings/${encodeURIComponent(id)}`,
|
|
1457
|
+
identifiers: { id, },
|
|
1458
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1459
|
+
};
|
|
1460
|
+
case "meaning.delete":
|
|
1461
|
+
return {
|
|
1462
|
+
method: "DELETE",
|
|
1463
|
+
endpoint: `/public/api/meanings/${encodeURIComponent(id)}`,
|
|
1464
|
+
identifiers: { id, },
|
|
1465
|
+
};
|
|
1466
|
+
case "llm.list": {
|
|
1467
|
+
const params = new URLSearchParams();
|
|
1468
|
+
const purpose = flags["purpose"];
|
|
1469
|
+
if (typeof purpose === "string" && purpose.trim() !== "") {
|
|
1470
|
+
params.set("purpose", purpose.trim());
|
|
1471
|
+
}
|
|
1472
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
1473
|
+
return {
|
|
1474
|
+
method: "GET",
|
|
1475
|
+
endpoint: projectEndpoint(`/llms/${query}`),
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
case "llm.completions":
|
|
1479
|
+
case "llm.embeddings": {
|
|
1480
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1481
|
+
const llmId = stringField(payload, ["llmId",]);
|
|
1482
|
+
if (llmId === undefined || llmId.trim() === "") {
|
|
1483
|
+
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");
|
|
1484
|
+
}
|
|
1485
|
+
const queries = payload["queries"];
|
|
1486
|
+
if (!Array.isArray(queries) || queries.length === 0) {
|
|
1487
|
+
throw new UsageError(`queries is required and must be a non-empty array of query objects.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
1488
|
+
}
|
|
1489
|
+
return {
|
|
1490
|
+
method: "POST",
|
|
1491
|
+
endpoint: projectEndpoint(action === "completions" ? "/llms/completions" : "/llms/embeddings"),
|
|
1492
|
+
identifiers: { llmId, },
|
|
1493
|
+
payload,
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
case "knowledge-bank.search": {
|
|
1497
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
1498
|
+
throw new UsageError(`knowledgeBankId must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
1499
|
+
}
|
|
1500
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1501
|
+
const query = stringField(payload, ["query",]);
|
|
1502
|
+
if (query === undefined || query.trim() === "") {
|
|
1503
|
+
throw new UsageError(`query is required and must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
1504
|
+
}
|
|
1505
|
+
return {
|
|
1506
|
+
method: "POST",
|
|
1507
|
+
endpoint: projectEndpoint(`/knowledge-banks/${encodeURIComponent(id)}/search`),
|
|
1508
|
+
identifiers: { knowledgeBankId: id, },
|
|
1509
|
+
payload,
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
case "knowledge-bank.clear":
|
|
1513
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
1514
|
+
throw new UsageError(`knowledgeBankId must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
method: "POST",
|
|
1518
|
+
endpoint: projectEndpoint(`/knowledge-banks/${encodeURIComponent(id)}/clear`),
|
|
1519
|
+
identifiers: { knowledgeBankId: id, },
|
|
1520
|
+
};
|
|
1521
|
+
case "data-collection.create":
|
|
1522
|
+
return {
|
|
1523
|
+
method: "POST",
|
|
1524
|
+
endpoint: "/public/api/data-collections/",
|
|
1525
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1526
|
+
};
|
|
1527
|
+
case "data-collection.settings-set":
|
|
1528
|
+
return {
|
|
1529
|
+
method: "PUT",
|
|
1530
|
+
endpoint: `/public/api/data-collections/${encodeURIComponent(id)}`,
|
|
1531
|
+
identifiers: { dataCollectionId: id, },
|
|
1532
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1533
|
+
};
|
|
1534
|
+
case "data-collection.delete":
|
|
1535
|
+
return {
|
|
1536
|
+
method: "DELETE",
|
|
1537
|
+
endpoint: `/public/api/data-collections/${encodeURIComponent(id)}`,
|
|
1538
|
+
identifiers: { dataCollectionId: id, },
|
|
1539
|
+
};
|
|
1540
|
+
case "data-collection.add-object": {
|
|
1541
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1542
|
+
const cid = id
|
|
1543
|
+
?? (typeof payload["dataCollectionId"] === "string"
|
|
1544
|
+
? payload["dataCollectionId"]
|
|
1545
|
+
: undefined);
|
|
1546
|
+
if (!cid)
|
|
1547
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
1548
|
+
const reference = { ...payload, };
|
|
1549
|
+
delete reference["dataCollectionId"];
|
|
1550
|
+
return {
|
|
1551
|
+
method: "POST",
|
|
1552
|
+
endpoint: `/public/api/data-collections/${encodeURIComponent(cid)}/objects`,
|
|
1553
|
+
identifiers: { dataCollectionId: cid, },
|
|
1554
|
+
payload: reference,
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
case "data-collection.remove-dataset": {
|
|
1558
|
+
const projectKeyArg = args[1];
|
|
1559
|
+
const datasetName = args[2];
|
|
1560
|
+
if (!projectKeyArg || !datasetName) {
|
|
1561
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
method: "DELETE",
|
|
1565
|
+
endpoint: `/public/api/data-collections/${encodeURIComponent(id)}/objects/dataset/${encodeURIComponent(projectKeyArg)}/${encodeURIComponent(datasetName)}`,
|
|
1566
|
+
identifiers: { dataCollectionId: id, projectKey: projectKeyArg, datasetName, },
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
case "project.tags-set":
|
|
1570
|
+
return {
|
|
1571
|
+
method: "PUT",
|
|
1572
|
+
endpoint: encodedProjectEndpointForPlan(projectKey ?? requiredPlanProjectKey(flags, entry.usage), "/tags"),
|
|
1573
|
+
identifiers: { projectKey: projectKey ?? requiredPlanProjectKey(flags, entry.usage), },
|
|
1574
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1575
|
+
};
|
|
1576
|
+
case "project.metadata-set":
|
|
1577
|
+
return {
|
|
1578
|
+
method: "PUT",
|
|
1579
|
+
endpoint: encodedProjectEndpointForPlan(projectKey ?? requiredPlanProjectKey(flags, entry.usage), "/metadata"),
|
|
1580
|
+
identifiers: { projectKey: projectKey ?? requiredPlanProjectKey(flags, entry.usage), },
|
|
1581
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1582
|
+
};
|
|
1583
|
+
case "macro.run":
|
|
1584
|
+
case "macro.run-and-wait": {
|
|
1585
|
+
// The plan payload mirrors the live POST body exactly: params and
|
|
1586
|
+
// adminParams when provided via --data, {} otherwise. Validation is
|
|
1587
|
+
// identical to the live path (JSON object shape), zero network.
|
|
1588
|
+
const data = optionalJsonFlag(flags, "data");
|
|
1589
|
+
const runPayload = {};
|
|
1590
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1591
|
+
if (data["params"] !== undefined)
|
|
1592
|
+
runPayload["params"] = data["params"];
|
|
1593
|
+
if (data["adminParams"] !== undefined)
|
|
1594
|
+
runPayload["adminParams"] = data["adminParams"];
|
|
1595
|
+
}
|
|
1596
|
+
return {
|
|
1597
|
+
method: "POST",
|
|
1598
|
+
endpoint: projectEndpoint(`/runnables/${encodeURIComponent(id)}?wait=false`),
|
|
1599
|
+
identifiers: { id, },
|
|
1600
|
+
payload: runPayload,
|
|
1601
|
+
wait: action === "macro.run-and-wait" || flags["wait"] === true,
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
case "macro.abort": {
|
|
1605
|
+
const runId = args[1];
|
|
1606
|
+
if (!runId)
|
|
1607
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
1608
|
+
return {
|
|
1609
|
+
method: "POST",
|
|
1610
|
+
endpoint: projectEndpoint(`/runnables/${encodeURIComponent(id)}/abort/${encodeURIComponent(runId)}`),
|
|
1611
|
+
identifiers: { id, runId, },
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
case "recipe.metadata-set":
|
|
1615
|
+
return {
|
|
1616
|
+
method: "PUT",
|
|
1617
|
+
endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}/metadata`),
|
|
1618
|
+
identifiers: { recipeName: id, },
|
|
1619
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1620
|
+
};
|
|
1621
|
+
case "user.create":
|
|
1622
|
+
return {
|
|
1623
|
+
method: "POST",
|
|
1624
|
+
endpoint: "/public/api/admin/users",
|
|
1625
|
+
payload: redactedUserPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
1626
|
+
};
|
|
1627
|
+
case "group.create":
|
|
1628
|
+
return {
|
|
1629
|
+
method: "POST",
|
|
1630
|
+
endpoint: "/public/api/admin/groups",
|
|
1631
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1632
|
+
};
|
|
1633
|
+
case "saved-model.create-external": {
|
|
1634
|
+
const config = optionalJsonFlag(flags, "configuration") ?? optionalJsonFlag(flags, "data");
|
|
1635
|
+
return {
|
|
1636
|
+
method: "POST",
|
|
1637
|
+
endpoint: projectEndpoint("/savedmodels/"),
|
|
1638
|
+
identifiers: { name: id, },
|
|
1639
|
+
payload: {
|
|
1640
|
+
savedModelType: requiredPlanFlag(flags, "type", entry.usage),
|
|
1641
|
+
name: id,
|
|
1642
|
+
...(typeof flags["prediction-type"] === "string"
|
|
1643
|
+
? { predictionType: flags["prediction-type"], }
|
|
1644
|
+
: {}),
|
|
1645
|
+
...(config ? { proxyModelConfiguration: config, } : {}),
|
|
1646
|
+
},
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
case "saved-model.update-settings":
|
|
1650
|
+
return {
|
|
1651
|
+
method: "PUT",
|
|
1652
|
+
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}`),
|
|
1653
|
+
identifiers: { savedModelId: id, },
|
|
1654
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1655
|
+
};
|
|
1656
|
+
case "saved-model.delete-versions": {
|
|
1657
|
+
const versionsRaw = args[1];
|
|
1658
|
+
if (typeof versionsRaw !== "string" || versionsRaw.trim() === "") {
|
|
1659
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
method: "POST",
|
|
1663
|
+
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/actions/delete-versions`),
|
|
1664
|
+
identifiers: { savedModelId: id, },
|
|
1665
|
+
payload: {
|
|
1666
|
+
versions: versionsRaw.split(",").map((v) => v.trim()).filter((v) => v.length > 0),
|
|
1667
|
+
removeIntermediate: parseBooleanOption(flags["remove-intermediate"], "--remove-intermediate") ?? true,
|
|
1668
|
+
},
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
case "saved-model.import-mlflow-version":
|
|
1672
|
+
return {
|
|
1673
|
+
method: "POST",
|
|
1674
|
+
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
|
|
1675
|
+
identifiers: { savedModelId: id, versionId: args[1], },
|
|
1676
|
+
payload: {
|
|
1677
|
+
source: { kind: "local-archive", archive: requiredPlanFlag(flags, "archive", entry.usage), },
|
|
1678
|
+
...(typeof flags["code-env"] === "string"
|
|
1679
|
+
? { codeEnvName: flags["code-env"], }
|
|
1680
|
+
: {}),
|
|
1681
|
+
// Execute always sends the query parameter with the
|
|
1682
|
+
// external-caller default NONE; the plan mirrors it.
|
|
1683
|
+
containerExecConfigName: typeof flags["container-exec-config"] === "string"
|
|
1684
|
+
? flags["container-exec-config"]
|
|
1685
|
+
: "NONE",
|
|
1686
|
+
setActive: parseBooleanOption(flags["set-active"], "--set-active") ?? true,
|
|
1687
|
+
...(flags["binary-classification-threshold"] !== undefined
|
|
1688
|
+
? {
|
|
1689
|
+
binaryClassificationThreshold: num(flags["binary-classification-threshold"], "--binary-classification-threshold"),
|
|
1690
|
+
}
|
|
1691
|
+
: {}),
|
|
1692
|
+
},
|
|
1693
|
+
};
|
|
1694
|
+
case "saved-model.import-mlflow-version-from-folder":
|
|
1695
|
+
return {
|
|
1696
|
+
exact: false,
|
|
1697
|
+
reason: "Apply sends these values as query parameters (with codeEnvName and binaryClassificationThreshold defaults) on an empty multipart body; the payload shows them structured.",
|
|
1698
|
+
method: "POST",
|
|
1699
|
+
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
|
|
1700
|
+
identifiers: { savedModelId: id, versionId: args[1], },
|
|
1701
|
+
payload: {
|
|
1702
|
+
source: {
|
|
1703
|
+
kind: "managed-folder",
|
|
1704
|
+
folderRef: requiredPlanFlag(flags, "folder", entry.usage),
|
|
1705
|
+
path: flags["path"],
|
|
1706
|
+
},
|
|
1707
|
+
...(typeof flags["code-env"] === "string" ? { codeEnvName: flags["code-env"], } : {}),
|
|
1708
|
+
// Execute always sends the query parameter with the
|
|
1709
|
+
// external-caller default NONE; the plan mirrors it.
|
|
1710
|
+
containerExecConfigName: typeof flags["container-exec-config"] === "string"
|
|
1711
|
+
? flags["container-exec-config"]
|
|
1712
|
+
: "NONE",
|
|
1713
|
+
setActive: parseBooleanOption(flags["set-active"], "--set-active") ?? true,
|
|
1714
|
+
},
|
|
1715
|
+
};
|
|
1716
|
+
case "saved-model.external-metadata-put": {
|
|
1717
|
+
// DSS requires containerExecConfigName on this endpoint; for an
|
|
1718
|
+
// external API caller it resolves as LOCAL-CONFIG -> NONE, so the
|
|
1719
|
+
// plan mirrors the NONE default unless --container-exec-config
|
|
1720
|
+
// overrides it. Same contract as execute.
|
|
1721
|
+
const containerExecConfigName = typeof flags["container-exec-config"] === "string"
|
|
1722
|
+
? flags["container-exec-config"]
|
|
1723
|
+
: "NONE";
|
|
1724
|
+
return {
|
|
1725
|
+
method: "PUT",
|
|
1726
|
+
endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/metadata?containerExecConfigName=${encodeURIComponent(containerExecConfigName)}`),
|
|
1727
|
+
identifiers: { savedModelId: id, versionId: args[1], },
|
|
1728
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
case "saved-model.evaluate-version": {
|
|
1732
|
+
const payload = {
|
|
1733
|
+
datasetRef: requiredPlanFlag(flags, "dataset", entry.usage),
|
|
1734
|
+
// Execute always sends an explicit containerExecConfigName
|
|
1735
|
+
// (official external-caller semantics: LOCAL-CONFIG -> NONE);
|
|
1736
|
+
// the plan mirrors that default and any explicit override.
|
|
1737
|
+
containerExecConfigName: typeof flags["container-exec-config"] === "string"
|
|
1738
|
+
? flags["container-exec-config"]
|
|
1739
|
+
: "NONE",
|
|
1740
|
+
};
|
|
1741
|
+
// --sampling is a JSON flag value; the plan carries the parsed
|
|
1742
|
+
// object under the same key as the execute body (samplingParam).
|
|
1743
|
+
if (flags["sampling"] !== undefined) {
|
|
1744
|
+
payload.samplingParam = json(flags["sampling"], "--sampling");
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
method: "POST",
|
|
1748
|
+
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}`,
|
|
1749
|
+
identifiers: { savedModelId: id, versionId: args[1], },
|
|
1750
|
+
payload,
|
|
1751
|
+
};
|
|
1752
|
+
}
|
|
1753
|
+
case "ml-task.set-user-meta":
|
|
1754
|
+
case "saved-model.set-user-meta": {
|
|
1755
|
+
const modelEndpoint = trainedModelEndpoint();
|
|
1756
|
+
return {
|
|
1757
|
+
method: "PUT",
|
|
1758
|
+
endpoint: `${modelEndpoint.path}/user-meta`,
|
|
1759
|
+
identifiers: modelEndpoint.identifiers,
|
|
1760
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
case "ml-task.compute-diagnostics":
|
|
1764
|
+
case "saved-model.compute-diagnostics": {
|
|
1765
|
+
const modelEndpoint = trainedModelEndpoint();
|
|
1766
|
+
const { kind, features, computation, } = diagnosticsComputationFromFlags(flags);
|
|
1767
|
+
const payload = modelDiagnosticsBody(kind, features, computation);
|
|
1768
|
+
return {
|
|
1769
|
+
method: "POST",
|
|
1770
|
+
endpoint: `${modelEndpoint.path}/${kind}`,
|
|
1771
|
+
identifiers: modelEndpoint.identifiers,
|
|
1772
|
+
...(payload !== undefined ? { payload, } : {}),
|
|
1773
|
+
wait: flags["wait"] === true,
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
case "ml-task.reguess": {
|
|
1777
|
+
const query = mlTaskReguessQuery(mlTaskReguessOptionsFromFlags(flags));
|
|
1778
|
+
return {
|
|
1779
|
+
method: "POST",
|
|
1780
|
+
endpoint: projectEndpoint(`/models/lab/${encodeURIComponent(id)}/${encodeURIComponent(args[1])}/guess${query.size > 0 ? `?${query}` : ""}`),
|
|
1781
|
+
identifiers: { analysisId: id, mlTaskId: args[1], },
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
case "ml-task.reguess-forecasting":
|
|
1785
|
+
return {
|
|
1786
|
+
method: "POST",
|
|
1787
|
+
endpoint: projectEndpoint(`/models/lab/${encodeURIComponent(id)}/${encodeURIComponent(args[1])}/reguess-with-forecasting-params`),
|
|
1788
|
+
identifiers: { analysisId: id, mlTaskId: args[1], },
|
|
1789
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1790
|
+
};
|
|
1791
|
+
case "ml-task.create-for-dataset":
|
|
1792
|
+
return {
|
|
1793
|
+
method: "POST",
|
|
1794
|
+
endpoint: projectEndpoint("/models/lab/"),
|
|
1795
|
+
identifiers: { inputDataset: id, },
|
|
1796
|
+
payload: {
|
|
1797
|
+
inputDataset: id,
|
|
1798
|
+
...mlTaskCreateBody(mlTaskFieldsFromFlags(flags, entry.usage)),
|
|
1799
|
+
},
|
|
1800
|
+
};
|
|
1801
|
+
case "analysis.update":
|
|
1802
|
+
return {
|
|
1803
|
+
method: "PUT",
|
|
1804
|
+
endpoint: projectEndpoint(`/lab/${encodeURIComponent(id)}/`),
|
|
1805
|
+
identifiers: { analysisId: id, },
|
|
1806
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1807
|
+
};
|
|
1808
|
+
case "user.update":
|
|
1809
|
+
return {
|
|
1810
|
+
method: "PUT",
|
|
1811
|
+
endpoint: `/public/api/admin/users/${encodeURIComponent(id)}`,
|
|
1812
|
+
identifiers: { login: id, },
|
|
1813
|
+
payload: redactedUserPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
1814
|
+
};
|
|
1815
|
+
case "user.delete":
|
|
1816
|
+
case "group.delete":
|
|
1817
|
+
return {
|
|
1818
|
+
method: "DELETE",
|
|
1819
|
+
endpoint: resource === "user"
|
|
1820
|
+
? `/public/api/admin/users/${encodeURIComponent(id)}`
|
|
1821
|
+
: `/public/api/admin/groups/${encodeURIComponent(id)}`,
|
|
1822
|
+
identifiers: resource === "user" ? { login: id, } : { name: id, },
|
|
1823
|
+
};
|
|
1824
|
+
case "user.resync":
|
|
1825
|
+
return {
|
|
1826
|
+
method: "POST",
|
|
1827
|
+
endpoint: `/public/api/admin/users/${encodeURIComponent(id)}/actions/resync`,
|
|
1828
|
+
identifiers: { login: id, },
|
|
1829
|
+
};
|
|
1830
|
+
case "user.resync-multi": {
|
|
1831
|
+
const logins = requiredPlanCsv(flags, "logins", entry.usage);
|
|
1832
|
+
return {
|
|
1833
|
+
method: "POST",
|
|
1834
|
+
endpoint: "/public/api/admin/users/actions/resync-multi",
|
|
1835
|
+
identifiers: { count: logins.length, },
|
|
1836
|
+
payload: logins,
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
case "user.external-users":
|
|
1840
|
+
return {
|
|
1841
|
+
method: "GET",
|
|
1842
|
+
endpoint: "/public/api/admin/users/actions/external-users",
|
|
1843
|
+
};
|
|
1844
|
+
case "user.external-groups":
|
|
1845
|
+
return {
|
|
1846
|
+
method: "GET",
|
|
1847
|
+
endpoint: "/public/api/admin/users/actions/external-groups",
|
|
1848
|
+
};
|
|
1849
|
+
case "connection.test":
|
|
1850
|
+
return {
|
|
1851
|
+
method: "GET",
|
|
1852
|
+
endpoint: `/public/api/connections/${encodeURIComponent(id)}/test`,
|
|
1853
|
+
identifiers: { connectionName: id, },
|
|
1854
|
+
};
|
|
1855
|
+
case "user.provision":
|
|
1856
|
+
return {
|
|
1857
|
+
method: "POST",
|
|
1858
|
+
endpoint: "/public/api/admin/users/actions/provision",
|
|
1859
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1860
|
+
};
|
|
1861
|
+
case "group.update":
|
|
1862
|
+
return {
|
|
1863
|
+
method: "PUT",
|
|
1864
|
+
endpoint: `/public/api/admin/groups/${encodeURIComponent(id)}`,
|
|
1865
|
+
identifiers: { name: id, },
|
|
1866
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
1867
|
+
};
|
|
1868
|
+
case "connection.create":
|
|
1869
|
+
return {
|
|
1870
|
+
method: "POST",
|
|
1871
|
+
endpoint: "/public/api/admin/connections",
|
|
1872
|
+
payload: redactedConnectionPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
1873
|
+
};
|
|
1874
|
+
case "connection.update":
|
|
1875
|
+
return {
|
|
1876
|
+
method: "PUT",
|
|
1877
|
+
endpoint: `/public/api/admin/connections/${encodeURIComponent(id)}`,
|
|
1878
|
+
identifiers: { connectionName: id, },
|
|
1879
|
+
payload: redactedConnectionPayload(requiredPlanJsonInput(flags, entry.usage)),
|
|
1880
|
+
};
|
|
1881
|
+
case "connection.delete":
|
|
1882
|
+
return {
|
|
1883
|
+
method: "DELETE",
|
|
1884
|
+
endpoint: `/public/api/admin/connections/${encodeURIComponent(id)}`,
|
|
1885
|
+
identifiers: { connectionName: id, },
|
|
1886
|
+
};
|
|
1887
|
+
case "connection.prepare-import": {
|
|
1888
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1889
|
+
if (!projectKey)
|
|
1890
|
+
throw new UsageError(`Missing project key for ${resource} ${action}.`);
|
|
1891
|
+
return {
|
|
1892
|
+
method: "POST",
|
|
1893
|
+
endpoint: encodedProjectEndpointForPlan(projectKey, "/datasets/tables-import/actions/prepare-from-keys"),
|
|
1894
|
+
identifiers: { projectKey, },
|
|
1895
|
+
payload,
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
case "connection.execute-import": {
|
|
1899
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
1900
|
+
if (!projectKey)
|
|
1901
|
+
throw new UsageError(`Missing project key for ${resource} ${action}.`);
|
|
1902
|
+
return {
|
|
1903
|
+
method: "POST",
|
|
1904
|
+
endpoint: encodedProjectEndpointForPlan(projectKey, "/datasets/tables-import/actions/execute-from-candidates"),
|
|
1905
|
+
identifiers: { projectKey, },
|
|
1906
|
+
payload,
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
case "plugin.install-from-zip":
|
|
1910
|
+
return {
|
|
1911
|
+
method: "POST",
|
|
1912
|
+
endpoint: "/public/api/plugins/actions/installFromZip",
|
|
1913
|
+
payload: { file: requiredPlanFlag(flags, "file", entry.usage), upload: "multipart", },
|
|
1914
|
+
};
|
|
1915
|
+
case "plugin.install-from-store":
|
|
1916
|
+
return {
|
|
1917
|
+
method: "POST",
|
|
1918
|
+
endpoint: "/public/api/plugins/actions/installFromStore",
|
|
1919
|
+
identifiers: { pluginId: id, },
|
|
1920
|
+
payload: { pluginId: id, },
|
|
1921
|
+
};
|
|
1922
|
+
case "plugin.install-from-git": {
|
|
1923
|
+
const payload = pluginGitPlanPayload(flags);
|
|
1924
|
+
return {
|
|
1925
|
+
method: "POST",
|
|
1926
|
+
endpoint: "/public/api/plugins/actions/installFromGit",
|
|
1927
|
+
payload,
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
case "plugin.update-from-zip":
|
|
1931
|
+
return {
|
|
1932
|
+
method: "POST",
|
|
1933
|
+
endpoint: pluginActionEndpoint(id, "updateFromZip"),
|
|
1934
|
+
identifiers: { pluginId: id, },
|
|
1935
|
+
payload: { file: requiredPlanFlag(flags, "file", entry.usage), upload: "multipart", },
|
|
1936
|
+
};
|
|
1937
|
+
case "plugin.update-from-store":
|
|
1938
|
+
return {
|
|
1939
|
+
method: "POST",
|
|
1940
|
+
endpoint: pluginActionEndpoint(id, "updateFromStore"),
|
|
1941
|
+
identifiers: { pluginId: id, },
|
|
1942
|
+
};
|
|
1943
|
+
case "plugin.update-from-git":
|
|
1944
|
+
return {
|
|
1945
|
+
method: "POST",
|
|
1946
|
+
endpoint: pluginActionEndpoint(id, "updateFromGit"),
|
|
1947
|
+
identifiers: { pluginId: id, },
|
|
1948
|
+
payload: pluginGitPlanPayload(flags),
|
|
1949
|
+
};
|
|
1950
|
+
case "plugin.settings-set": {
|
|
1951
|
+
const scope = optionalPlanProjectScope(flags);
|
|
1952
|
+
return {
|
|
1953
|
+
method: "POST",
|
|
1954
|
+
endpoint: `${pluginRootEndpoint(id, "/settings")}${scope}`,
|
|
1955
|
+
identifiers: { pluginId: id, },
|
|
1956
|
+
payload: { configKeys: settingsConfigKeys(flags), },
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
case "plugin.code-env-create": {
|
|
1960
|
+
const conda = parseBooleanOption(flags["conda"], "--conda") ?? false;
|
|
1961
|
+
return {
|
|
1962
|
+
method: "POST",
|
|
1963
|
+
endpoint: `${pluginRootEndpoint(id, "/code-env/actions/create")}`,
|
|
1964
|
+
identifiers: { pluginId: id, },
|
|
1965
|
+
payload: {
|
|
1966
|
+
deploymentMode: "PLUGIN_MANAGED",
|
|
1967
|
+
conda,
|
|
1968
|
+
pythonInterpreter: flags["python-interpreter"] ?? null,
|
|
1969
|
+
},
|
|
1970
|
+
wait: flags["wait"] === true,
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
case "plugin.code-env-update":
|
|
1974
|
+
return {
|
|
1975
|
+
method: "POST",
|
|
1976
|
+
endpoint: `${pluginRootEndpoint(id, "/code-env/actions/update")}`,
|
|
1977
|
+
identifiers: { pluginId: id, },
|
|
1978
|
+
wait: flags["wait"] === true,
|
|
1979
|
+
};
|
|
1980
|
+
case "plugin.move-to-dev":
|
|
1981
|
+
return {
|
|
1982
|
+
method: "POST",
|
|
1983
|
+
endpoint: pluginActionEndpoint(id, "moveToDev"),
|
|
1984
|
+
identifiers: { pluginId: id, },
|
|
1985
|
+
};
|
|
1986
|
+
case "plugin.delete":
|
|
1987
|
+
return {
|
|
1988
|
+
method: "POST",
|
|
1989
|
+
endpoint: pluginActionEndpoint(id, "delete"),
|
|
1990
|
+
identifiers: { pluginId: id, },
|
|
1991
|
+
payload: { force: parseBooleanOption(flags["force"], "--force") ?? false, },
|
|
1992
|
+
};
|
|
1993
|
+
case "plugin.create-dev": {
|
|
1994
|
+
const creationMode = requiredPlanFlag(flags, "creation-mode", entry.usage);
|
|
1995
|
+
const needsGit = creationMode !== "EMPTY";
|
|
1996
|
+
// Mirrors PluginsResource.createDev: git fields are always present, null when unused.
|
|
1997
|
+
const payload = {
|
|
1998
|
+
pluginId: id,
|
|
1999
|
+
creationMode,
|
|
2000
|
+
gitRepository: needsGit && typeof flags["repository"] === "string"
|
|
2001
|
+
? validatedPlanRepositoryUrl(flags["repository"], "repository", "--repository URL")
|
|
2002
|
+
: null,
|
|
2003
|
+
gitCheckout: needsGit && typeof flags["checkout"] === "string" ? flags["checkout"] : null,
|
|
2004
|
+
gitSubpath: creationMode === "GIT_EXPORT" && typeof flags["path-in-repository"] === "string"
|
|
2005
|
+
? flags["path-in-repository"]
|
|
2006
|
+
: null,
|
|
2007
|
+
};
|
|
2008
|
+
return {
|
|
2009
|
+
method: "POST",
|
|
2010
|
+
endpoint: "/public/api/plugins/actions/createDev",
|
|
2011
|
+
identifiers: { pluginId: id, creationMode, },
|
|
2012
|
+
payload,
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
case "plugin.set-git-remote": {
|
|
2016
|
+
const repository = requiredPlanFlag(flags, "repository", entry.usage);
|
|
2017
|
+
return {
|
|
2018
|
+
method: "POST",
|
|
2019
|
+
endpoint: `${pluginRootEndpoint(id, "/gitRemote")}`,
|
|
2020
|
+
identifiers: { pluginId: id, },
|
|
2021
|
+
payload: { repositoryUrl: validatedPlanRepositoryUrl(repository, "repository", entry.usage), },
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
case "plugin.delete-git-remote":
|
|
2025
|
+
return {
|
|
2026
|
+
method: "DELETE",
|
|
2027
|
+
endpoint: pluginRootEndpoint(id, "/gitRemote"),
|
|
2028
|
+
identifiers: { pluginId: id, },
|
|
2029
|
+
};
|
|
2030
|
+
case "plugin.push":
|
|
2031
|
+
case "plugin.pull":
|
|
2032
|
+
case "plugin.fetch":
|
|
2033
|
+
case "plugin.reset-local":
|
|
2034
|
+
case "plugin.reset-remote": {
|
|
2035
|
+
const actionMap = {
|
|
2036
|
+
"plugin.push": "push",
|
|
2037
|
+
"plugin.pull": "pullRebase",
|
|
2038
|
+
"plugin.fetch": "fetch",
|
|
2039
|
+
"plugin.reset-local": "resetToLocalHeadState",
|
|
2040
|
+
"plugin.reset-remote": "resetToRemoteHeadState",
|
|
2041
|
+
};
|
|
2042
|
+
return {
|
|
2043
|
+
method: "POST",
|
|
2044
|
+
endpoint: pluginActionEndpoint(id, actionMap[`${resource}.${action}`]),
|
|
2045
|
+
identifiers: { pluginId: id, },
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
case "plugin.contents-put":
|
|
2049
|
+
return {
|
|
2050
|
+
method: "POST",
|
|
2051
|
+
endpoint: pluginContentsEndpoint(id, args[1] ?? ""),
|
|
2052
|
+
identifiers: { pluginId: id, path: args[1], },
|
|
2053
|
+
payload: { contentSource: contentSourceKind(flags), },
|
|
2054
|
+
};
|
|
2055
|
+
case "plugin.contents-delete":
|
|
2056
|
+
return {
|
|
2057
|
+
method: "DELETE",
|
|
2058
|
+
endpoint: pluginContentsEndpoint(id, args[1] ?? ""),
|
|
2059
|
+
identifiers: { pluginId: id, path: args[1], },
|
|
2060
|
+
};
|
|
2061
|
+
case "plugin.folder-add":
|
|
2062
|
+
return {
|
|
2063
|
+
method: "POST",
|
|
2064
|
+
endpoint: pluginFoldersEndpoint(id, args[1] ?? ""),
|
|
2065
|
+
identifiers: { pluginId: id, path: args[1], },
|
|
2066
|
+
};
|
|
2067
|
+
case "plugin.rename": {
|
|
2068
|
+
if (!args[2])
|
|
2069
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2070
|
+
return {
|
|
2071
|
+
method: "POST",
|
|
2072
|
+
endpoint: `${pluginRootEndpoint(id, "/contents-actions/rename")}`,
|
|
2073
|
+
identifiers: { pluginId: id, path: args[1], },
|
|
2074
|
+
payload: { oldPath: `/${validatePluginPath(args[1] ?? "")}`, newName: args[2], },
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
case "plugin.move": {
|
|
2078
|
+
const destination = args[2];
|
|
2079
|
+
if (!destination)
|
|
2080
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
2081
|
+
return {
|
|
2082
|
+
method: "POST",
|
|
2083
|
+
endpoint: `${pluginRootEndpoint(id, "/contents-actions/move")}`,
|
|
2084
|
+
identifiers: { pluginId: id, path: args[1], },
|
|
2085
|
+
payload: {
|
|
2086
|
+
oldPath: `/${validatePluginPath(args[1] ?? "")}`,
|
|
2087
|
+
newPath: validatePluginDestinationPath(destination),
|
|
2088
|
+
},
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
case "business-app.save-settings":
|
|
2092
|
+
return {
|
|
2093
|
+
method: "PUT",
|
|
2094
|
+
endpoint: `/public/api/business-apps/${encodeURIComponent(id)}/settings`,
|
|
2095
|
+
identifiers: { id, },
|
|
2096
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2097
|
+
};
|
|
2098
|
+
case "business-app.create-instance":
|
|
2099
|
+
return {
|
|
2100
|
+
method: "POST",
|
|
2101
|
+
endpoint: `/public/api/business-apps/${encodeURIComponent(id)}/instances`,
|
|
2102
|
+
identifiers: { id, },
|
|
2103
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2104
|
+
};
|
|
2105
|
+
case "business-app.upgrade-instance":
|
|
2106
|
+
return {
|
|
2107
|
+
method: "POST",
|
|
2108
|
+
endpoint: `/public/api/business-apps/${encodeURIComponent(args[0])}/instances/${encodeURIComponent(args[1])}/upgrade`,
|
|
2109
|
+
identifiers: { id: args[0], projectKey: args[1], },
|
|
2110
|
+
payload: {},
|
|
2111
|
+
};
|
|
2112
|
+
case "business-app.install-from-archive":
|
|
2113
|
+
return {
|
|
2114
|
+
method: "POST",
|
|
2115
|
+
endpoint: "/public/api/business-apps/install-from-archive",
|
|
2116
|
+
identifiers: { filePath: id, },
|
|
2117
|
+
payload: uploadPayload(id),
|
|
2118
|
+
};
|
|
2119
|
+
case "app.save-instance-manifest": {
|
|
2120
|
+
const targetProjectKey = requiredPlanProjectKey(flags, entry.usage);
|
|
2121
|
+
return {
|
|
2122
|
+
method: "PUT",
|
|
2123
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/app-manifest`,
|
|
2124
|
+
identifiers: { projectKey: targetProjectKey, },
|
|
2125
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
case "app.create-instance": {
|
|
2129
|
+
const payload = requiredPlanJsonInput(flags, entry.usage);
|
|
2130
|
+
if (payload["targetProjectKey"] === undefined) {
|
|
2131
|
+
// Generated-at-apply mode: --plan never allocates or reserves a
|
|
2132
|
+
// random key. The payload stays exactly as supplied, the runtime
|
|
2133
|
+
// generates the key (and defaults the display name to it) during
|
|
2134
|
+
// apply, and no absence probe runs for a generated key, so the
|
|
2135
|
+
// plan carries no preflight requests and no fake concrete GET
|
|
2136
|
+
// path: `{targetProjectKey}` resolves at apply time.
|
|
2137
|
+
return {
|
|
2138
|
+
exact: false,
|
|
2139
|
+
reason: "targetProjectKey (and targetProjectName when omitted) are generated during apply; the sent body adds them.",
|
|
2140
|
+
method: "POST",
|
|
2141
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
2142
|
+
identifiers: {
|
|
2143
|
+
appId: id,
|
|
2144
|
+
targetProjectKeyGeneratedDuringApply: true,
|
|
2145
|
+
...(payload["targetProjectName"] === undefined
|
|
2146
|
+
? { targetProjectNameGeneratedDuringApply: true, }
|
|
2147
|
+
: {}),
|
|
2148
|
+
preflightExecuted: false,
|
|
2149
|
+
preflightWillRunDuringApply: false,
|
|
2150
|
+
preflightRequests: [],
|
|
2151
|
+
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
2152
|
+
incarnationObservationRequests: [
|
|
2153
|
+
{
|
|
2154
|
+
method: "GET",
|
|
2155
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
2156
|
+
when: flags["wait"] === true
|
|
2157
|
+
? "after-terminal-future-target"
|
|
2158
|
+
: "conditional-inline-hasResult-target",
|
|
2159
|
+
intent: "After DSS reports inline or terminal creation success for the generated key, observe creationTag for later cleanup binding.",
|
|
2160
|
+
},
|
|
2161
|
+
],
|
|
2162
|
+
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.",
|
|
2163
|
+
},
|
|
2164
|
+
payload,
|
|
2165
|
+
wait: flags["wait"] === true,
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
const rawTargetProjectKey = stringField(payload, ["targetProjectKey",]);
|
|
2169
|
+
if (!rawTargetProjectKey || rawTargetProjectKey.trim() === "") {
|
|
2170
|
+
throw new UsageError("Instance creation payload must include a non-empty targetProjectKey.", "validation_failed", `Usage: ${entry.usage}`);
|
|
2171
|
+
}
|
|
2172
|
+
// The runtime trims and rewrites body.targetProjectKey; the plan
|
|
2173
|
+
// advertises the normalized identifier and payload.
|
|
2174
|
+
const targetProjectKey = rawTargetProjectKey.trim();
|
|
2175
|
+
const normalizedPayload = { ...payload, targetProjectKey, };
|
|
2176
|
+
return {
|
|
2177
|
+
method: "POST",
|
|
2178
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
2179
|
+
identifiers: {
|
|
2180
|
+
appId: id,
|
|
2181
|
+
targetProjectKey,
|
|
2182
|
+
preflightExecuted: false,
|
|
2183
|
+
preflightWillRunDuringApply: true,
|
|
2184
|
+
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
2185
|
+
incarnationObservationRequests: [
|
|
2186
|
+
{
|
|
2187
|
+
method: "GET",
|
|
2188
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2189
|
+
when: flags["wait"] === true
|
|
2190
|
+
? "after-terminal-future-target"
|
|
2191
|
+
: "conditional-inline-hasResult-target",
|
|
2192
|
+
intent: "After DSS reports inline or terminal creation success for the requested key, observe creationTag for later cleanup binding.",
|
|
2193
|
+
},
|
|
2194
|
+
],
|
|
2195
|
+
preflightRequests: [
|
|
2196
|
+
{
|
|
2197
|
+
method: "GET",
|
|
2198
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2199
|
+
when: "before-create",
|
|
2200
|
+
intent: "Require the target project to be absent before creating the instance.",
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
method: "GET",
|
|
2204
|
+
endpoint: "/public/api/projects/",
|
|
2205
|
+
when: "conditional",
|
|
2206
|
+
intent: "Fallback list probe issued only when the direct project GET is forbidden (403). Presence proves a collision; absence cannot prove availability.",
|
|
2207
|
+
},
|
|
2208
|
+
{
|
|
2209
|
+
method: "GET",
|
|
2210
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
2211
|
+
when: "conditional",
|
|
2212
|
+
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.",
|
|
2213
|
+
},
|
|
2214
|
+
],
|
|
2215
|
+
note: "Strict preflight: the instance POST runs only after the payload targetProjectKey is confirmed absent. Creation never writes into an existing or unprovable project.",
|
|
2216
|
+
},
|
|
2217
|
+
payload: normalizedPayload,
|
|
2218
|
+
wait: flags["wait"] === true,
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
case "app.set-manifest-version": {
|
|
2222
|
+
const targetProjectKey = requiredPlanProjectKey(flags, entry.usage);
|
|
2223
|
+
const payloadPatch = {};
|
|
2224
|
+
const version = flags["manifest-version"];
|
|
2225
|
+
if (version !== undefined) {
|
|
2226
|
+
if (version.trim() === "") {
|
|
2227
|
+
throw new UsageError("App manifest version must be a non-empty string.", "validation_failed", `Usage: ${entry.usage}`);
|
|
2228
|
+
}
|
|
2229
|
+
payloadPatch.version = version;
|
|
2230
|
+
}
|
|
2231
|
+
const versionNotes = flags["version-notes"];
|
|
2232
|
+
if (versionNotes !== undefined)
|
|
2233
|
+
payloadPatch.versionNotes = versionNotes;
|
|
2234
|
+
if (version === undefined && versionNotes === undefined) {
|
|
2235
|
+
throw new UsageError("At least one of --manifest-version or --version-notes is required.", "usage_error", `Usage: ${entry.usage}`);
|
|
2236
|
+
}
|
|
2237
|
+
const expectHash = flags["expect-hash"];
|
|
2238
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
2239
|
+
throw new UsageError("Expected manifest hash must be a 64-character SHA-256 hex digest.", "validation_failed", `Usage: ${entry.usage}`);
|
|
2240
|
+
}
|
|
2241
|
+
return {
|
|
2242
|
+
method: "PUT",
|
|
2243
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/app-manifest`,
|
|
2244
|
+
identifiers: {
|
|
2245
|
+
projectKey: targetProjectKey,
|
|
2246
|
+
payloadPatch,
|
|
2247
|
+
...(expectHash !== undefined ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
2248
|
+
concurrencyControl: APP_MANIFEST_CONCURRENCY_CONTROL,
|
|
2249
|
+
staleReadCheck: expectHash === undefined
|
|
2250
|
+
? "none"
|
|
2251
|
+
: "client-side-expect-hash-compare-before-put",
|
|
2252
|
+
note: expectHash === undefined
|
|
2253
|
+
? "Unconditional PUT: no stale-read check is armed because --expect-hash was not supplied."
|
|
2254
|
+
: "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.",
|
|
2255
|
+
},
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
case "app.create-successor-instance": {
|
|
2259
|
+
const sourceProjectKey = requiredPlanFlag(flags, "from", entry.usage);
|
|
2260
|
+
const targetProjectKey = optionalPlanFlag(flags, "to", entry.usage);
|
|
2261
|
+
if (targetProjectKey !== undefined && sourceProjectKey === targetProjectKey) {
|
|
2262
|
+
throw new UsageError("--from and --to must be different project keys.", "validation_failed", `Usage: ${entry.usage}`);
|
|
2263
|
+
}
|
|
2264
|
+
const targetProjectName = flags["name"];
|
|
2265
|
+
const copyPermissions = parseBooleanOption(flags["copy-permissions"], "--copy-permissions")
|
|
2266
|
+
?? false;
|
|
2267
|
+
if (targetProjectKey === undefined) {
|
|
2268
|
+
// Generated-at-apply mode: --plan never allocates a key and no
|
|
2269
|
+
// target exists yet, so the plan makes no target-absence claim
|
|
2270
|
+
// and advertises no target probe. The key is generated once
|
|
2271
|
+
// during apply, immediately before the single instance POST;
|
|
2272
|
+
// `{targetProjectKey}` resolves only after the terminal future
|
|
2273
|
+
// names it. Source and template gates still run before the POST.
|
|
2274
|
+
return {
|
|
2275
|
+
method: "POST",
|
|
2276
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
2277
|
+
identifiers: {
|
|
2278
|
+
appId: id,
|
|
2279
|
+
sourceProjectKey,
|
|
2280
|
+
targetProjectKeyGeneratedDuringApply: true,
|
|
2281
|
+
targetPreflight: "not-applicable-generated-key",
|
|
2282
|
+
preflightExecuted: false,
|
|
2283
|
+
preflightWillRunDuringApply: true,
|
|
2284
|
+
...(targetProjectName !== undefined
|
|
2285
|
+
? { targetProjectName, }
|
|
2286
|
+
: { targetProjectNameGeneratedDuringApply: true, }),
|
|
2287
|
+
copyPermissions,
|
|
2288
|
+
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
2289
|
+
incarnationObservationRequests: [
|
|
2290
|
+
{
|
|
2291
|
+
method: "GET",
|
|
2292
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
2293
|
+
when: "after-terminal-future-target",
|
|
2294
|
+
intent: "After the terminal future names the generated successor key, observe creationTag and bind later target checks and cleanup to that hash.",
|
|
2295
|
+
},
|
|
2296
|
+
],
|
|
2297
|
+
preflightRequests: [
|
|
2298
|
+
{
|
|
2299
|
+
method: "GET",
|
|
2300
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
2301
|
+
when: "before-create",
|
|
2302
|
+
intent: "Verify the --from project is a registered instance of the app.",
|
|
2303
|
+
},
|
|
2304
|
+
{
|
|
2305
|
+
method: "GET",
|
|
2306
|
+
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/app-manifest`,
|
|
2307
|
+
when: "before-create",
|
|
2308
|
+
intent: "Verify the --from project is an APP_INSTANCE project.",
|
|
2309
|
+
},
|
|
2310
|
+
],
|
|
2311
|
+
...(copyPermissions
|
|
2312
|
+
? {
|
|
2313
|
+
permissionConcurrencyControl: "client-side-non-atomic-stale-identity-and-hash-checks",
|
|
2314
|
+
permissionRequests: [
|
|
2315
|
+
{
|
|
2316
|
+
method: "GET",
|
|
2317
|
+
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
2318
|
+
intent: "Snapshot the predecessor instance ACL before creation.",
|
|
2319
|
+
},
|
|
2320
|
+
{
|
|
2321
|
+
method: "GET",
|
|
2322
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
|
|
2323
|
+
intent: "Read the generated successor ACL to decide whether the copy is a no-op.",
|
|
2324
|
+
},
|
|
2325
|
+
{
|
|
2326
|
+
method: "GET",
|
|
2327
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
2328
|
+
intent: "Recheck generated successor creationTag after reading its ACL; stop if the project key was reused.",
|
|
2329
|
+
},
|
|
2330
|
+
{
|
|
2331
|
+
method: "GET",
|
|
2332
|
+
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
2333
|
+
intent: "Recheck the predecessor ACL immediately before the write and stop if its hash drifted.",
|
|
2334
|
+
},
|
|
2335
|
+
{
|
|
2336
|
+
method: "GET",
|
|
2337
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
2338
|
+
intent: "Recheck generated successor creationTag immediately before the unconditional permission PUT.",
|
|
2339
|
+
},
|
|
2340
|
+
{
|
|
2341
|
+
method: "PUT",
|
|
2342
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
|
|
2343
|
+
intent: "Apply the predecessor ACL snapshot to the generated successor instance.",
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
method: "GET",
|
|
2347
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
|
|
2348
|
+
intent: "Verify the generated successor ACL hash equals the predecessor snapshot hash.",
|
|
2349
|
+
},
|
|
2350
|
+
{
|
|
2351
|
+
method: "GET",
|
|
2352
|
+
endpointTemplate: "/public/api/projects/{targetProjectKey}/",
|
|
2353
|
+
intent: "Detect generated successor project-key reuse across the permission write and verification read.",
|
|
2354
|
+
},
|
|
2355
|
+
],
|
|
2356
|
+
}
|
|
2357
|
+
: {}),
|
|
2358
|
+
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.",
|
|
2359
|
+
},
|
|
2360
|
+
payload: targetProjectName !== undefined ? { targetProjectName, } : {},
|
|
2361
|
+
wait: true,
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
return {
|
|
2365
|
+
method: "POST",
|
|
2366
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
|
|
2367
|
+
identifiers: {
|
|
2368
|
+
appId: id,
|
|
2369
|
+
sourceProjectKey,
|
|
2370
|
+
targetProjectKey,
|
|
2371
|
+
preflightExecuted: false,
|
|
2372
|
+
preflightWillRunDuringApply: true,
|
|
2373
|
+
...(targetProjectName !== undefined ? { targetProjectName, } : {}),
|
|
2374
|
+
copyPermissions,
|
|
2375
|
+
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
2376
|
+
postFutureRequests: [
|
|
2377
|
+
{
|
|
2378
|
+
method: "GET",
|
|
2379
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2380
|
+
intent: "After the terminal future names the successor key, observe creationTag and bind later target checks and cleanup to that hash.",
|
|
2381
|
+
},
|
|
2382
|
+
],
|
|
2383
|
+
...(copyPermissions
|
|
2384
|
+
? {
|
|
2385
|
+
permissionConcurrencyControl: "client-side-non-atomic-stale-identity-and-hash-checks",
|
|
2386
|
+
}
|
|
2387
|
+
: {}),
|
|
2388
|
+
preflightRequests: [
|
|
2389
|
+
{
|
|
2390
|
+
method: "GET",
|
|
2391
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
2392
|
+
when: "before-create",
|
|
2393
|
+
intent: "Verify the --from project is a registered instance of the app.",
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
method: "GET",
|
|
2397
|
+
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/app-manifest`,
|
|
2398
|
+
when: "before-create",
|
|
2399
|
+
intent: "Verify the --from project is an APP_INSTANCE project.",
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
method: "GET",
|
|
2403
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2404
|
+
when: "before-create",
|
|
2405
|
+
intent: "Require the --to target project to be absent before creating the successor.",
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
method: "GET",
|
|
2409
|
+
endpoint: "/public/api/projects/",
|
|
2410
|
+
when: "conditional",
|
|
2411
|
+
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.",
|
|
2412
|
+
},
|
|
2413
|
+
],
|
|
2414
|
+
...(copyPermissions
|
|
2415
|
+
? {
|
|
2416
|
+
permissionRequests: [
|
|
2417
|
+
{
|
|
2418
|
+
method: "GET",
|
|
2419
|
+
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
2420
|
+
intent: "Snapshot the predecessor instance ACL before creation.",
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
method: "GET",
|
|
2424
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`,
|
|
2425
|
+
intent: "Read the successor ACL to decide whether the copy is a no-op.",
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
method: "GET",
|
|
2429
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2430
|
+
intent: "Recheck successor creationTag after reading its ACL; stop if the project key was reused.",
|
|
2431
|
+
},
|
|
2432
|
+
{
|
|
2433
|
+
method: "GET",
|
|
2434
|
+
endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
|
|
2435
|
+
intent: "Recheck the predecessor ACL immediately before the write and stop if its hash drifted.",
|
|
2436
|
+
},
|
|
2437
|
+
{
|
|
2438
|
+
method: "GET",
|
|
2439
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2440
|
+
intent: "Recheck successor creationTag immediately before the unconditional permission PUT.",
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
method: "PUT",
|
|
2444
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`,
|
|
2445
|
+
intent: "Apply the predecessor ACL snapshot to the successor instance.",
|
|
2446
|
+
},
|
|
2447
|
+
{
|
|
2448
|
+
method: "GET",
|
|
2449
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`,
|
|
2450
|
+
intent: "Verify the successor ACL hash equals the predecessor snapshot hash.",
|
|
2451
|
+
},
|
|
2452
|
+
{
|
|
2453
|
+
method: "GET",
|
|
2454
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`,
|
|
2455
|
+
intent: "Detect successor project-key reuse across the permission write and verification read.",
|
|
2456
|
+
},
|
|
2457
|
+
],
|
|
2458
|
+
}
|
|
2459
|
+
: {}),
|
|
2460
|
+
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.",
|
|
2461
|
+
},
|
|
2462
|
+
payload: {
|
|
2463
|
+
targetProjectKey,
|
|
2464
|
+
targetProjectName: targetProjectName ?? targetProjectKey,
|
|
2465
|
+
},
|
|
2466
|
+
wait: true,
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
case "app.delete-instance": {
|
|
2470
|
+
const targetProjectKey = requiredPlanFlag(flags, "project-key", entry.usage);
|
|
2471
|
+
const futureId = flags["future-id"] === undefined
|
|
2472
|
+
? undefined
|
|
2473
|
+
: requiredPlanFlag(flags, "future-id", entry.usage);
|
|
2474
|
+
const expectedProjectIncarnation = flags["expect-project-incarnation"] === undefined
|
|
2475
|
+
? undefined
|
|
2476
|
+
: requiredPlanFlag(flags, "expect-project-incarnation", entry.usage);
|
|
2477
|
+
if (expectedProjectIncarnation !== undefined
|
|
2478
|
+
&& !/^[0-9a-f]{64}$/.test(expectedProjectIncarnation)) {
|
|
2479
|
+
throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
|
|
2480
|
+
}
|
|
2481
|
+
const unconfirmedCreation = parseBooleanOption(flags["unconfirmed-creation"], "--unconfirmed-creation") ?? false;
|
|
2482
|
+
const manifestProbe = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/app-manifest`;
|
|
2483
|
+
const projectDetailsProbe = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`;
|
|
2484
|
+
const typeValidationRequests = [
|
|
2485
|
+
{
|
|
2486
|
+
method: "GET",
|
|
2487
|
+
endpoint: manifestProbe,
|
|
2488
|
+
when: "before-delete",
|
|
2489
|
+
intent: "Verify the target is an APP_INSTANCE project before deleting.",
|
|
2490
|
+
},
|
|
2491
|
+
{
|
|
2492
|
+
method: "GET",
|
|
2493
|
+
endpoint: projectDetailsProbe,
|
|
2494
|
+
when: expectedProjectIncarnation === undefined
|
|
2495
|
+
? "conditional-type-check-before-delete"
|
|
2496
|
+
: "incarnation-and-conditional-type-check-before-delete",
|
|
2497
|
+
intent: expectedProjectIncarnation === undefined
|
|
2498
|
+
? "Fallback probe issued only when the app-manifest response omits projectAppType (live DSS does)."
|
|
2499
|
+
: "Recompute the current project-incarnation hash from creationTag after the manifest probe; the same response supplies projectAppType when the manifest omits it.",
|
|
2500
|
+
},
|
|
2501
|
+
];
|
|
2502
|
+
const preflightRequests = typeValidationRequests;
|
|
2503
|
+
if (unconfirmedCreation) {
|
|
2504
|
+
return {
|
|
2505
|
+
identifiers: {
|
|
2506
|
+
projectKey: targetProjectKey,
|
|
2507
|
+
...(futureId !== undefined ? { futureId, } : {}),
|
|
2508
|
+
unconfirmedCreation: true,
|
|
2509
|
+
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.",
|
|
2510
|
+
},
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
if (futureId === undefined) {
|
|
2514
|
+
return {
|
|
2515
|
+
method: "DELETE",
|
|
2516
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}`,
|
|
2517
|
+
identifiers: {
|
|
2518
|
+
projectKey: targetProjectKey,
|
|
2519
|
+
...(expectedProjectIncarnation === undefined
|
|
2520
|
+
? {}
|
|
2521
|
+
: {
|
|
2522
|
+
projectIncarnationGate: {
|
|
2523
|
+
required: false,
|
|
2524
|
+
provided: true,
|
|
2525
|
+
expectedHash: expectedProjectIncarnation,
|
|
2526
|
+
},
|
|
2527
|
+
incarnationControl: "client-side-non-atomic-stale-identity-check",
|
|
2528
|
+
}),
|
|
2529
|
+
preflightRequests,
|
|
2530
|
+
note: expectedProjectIncarnation === undefined
|
|
2531
|
+
? "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."
|
|
2532
|
+
: "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.",
|
|
2533
|
+
},
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
return {
|
|
2537
|
+
method: "DELETE",
|
|
2538
|
+
endpoint: `/public/api/projects/${encodeURIComponent(targetProjectKey)}`,
|
|
2539
|
+
identifiers: {
|
|
2540
|
+
projectKey: targetProjectKey,
|
|
2541
|
+
futureId,
|
|
2542
|
+
projectIncarnationGate: {
|
|
2543
|
+
required: true,
|
|
2544
|
+
provided: expectedProjectIncarnation !== undefined,
|
|
2545
|
+
...(expectedProjectIncarnation === undefined
|
|
2546
|
+
? {}
|
|
2547
|
+
: { expectedHash: expectedProjectIncarnation, }),
|
|
2548
|
+
},
|
|
2549
|
+
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
2550
|
+
preflightRequests: [
|
|
2551
|
+
{
|
|
2552
|
+
method: "GET",
|
|
2553
|
+
endpoint: manifestProbe,
|
|
2554
|
+
when: "before-future-wait",
|
|
2555
|
+
intent: "Verify the target is an APP_INSTANCE project before the supplied future is touched, so an invalid target cannot affect it.",
|
|
2556
|
+
},
|
|
2557
|
+
{
|
|
2558
|
+
method: "GET",
|
|
2559
|
+
endpoint: projectDetailsProbe,
|
|
2560
|
+
when: "conditional-before-wait",
|
|
2561
|
+
intent: "Fallback probe issued only when the pre-wait app-manifest response omits projectAppType (live DSS does).",
|
|
2562
|
+
},
|
|
2563
|
+
],
|
|
2564
|
+
futureGate: [
|
|
2565
|
+
{
|
|
2566
|
+
method: "GET",
|
|
2567
|
+
endpoint: `/public/api/futures/${encodeURIComponent(futureId)}?peek=false`,
|
|
2568
|
+
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.",
|
|
2569
|
+
},
|
|
2570
|
+
],
|
|
2571
|
+
postFutureValidationRequests: expectedProjectIncarnation === undefined
|
|
2572
|
+
? []
|
|
2573
|
+
: typeValidationRequests,
|
|
2574
|
+
note: expectedProjectIncarnation === undefined
|
|
2575
|
+
? "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."
|
|
2576
|
+
: "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.",
|
|
2577
|
+
},
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
case "app.permissions-restore": {
|
|
2581
|
+
const file = requiredPlanFlag(flags, "file", entry.usage);
|
|
2582
|
+
const targetProjectKey = requiredPlanProjectKey(flags, entry.usage);
|
|
2583
|
+
const permissionsEndpoint = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/permissions`;
|
|
2584
|
+
const projectDetailsEndpoint = `/public/api/projects/${encodeURIComponent(targetProjectKey)}/`;
|
|
2585
|
+
return {
|
|
2586
|
+
method: "PUT",
|
|
2587
|
+
endpoint: permissionsEndpoint,
|
|
2588
|
+
identifiers: {
|
|
2589
|
+
file,
|
|
2590
|
+
projectKey: targetProjectKey,
|
|
2591
|
+
localPreflight: [
|
|
2592
|
+
"Read and hash-verify the owner-only snapshot file.",
|
|
2593
|
+
"Require its project key and canonical DSS URL to match this invocation.",
|
|
2594
|
+
],
|
|
2595
|
+
preflightRequests: [
|
|
2596
|
+
{
|
|
2597
|
+
method: "GET",
|
|
2598
|
+
endpoint: projectDetailsEndpoint,
|
|
2599
|
+
intent: "Require the snapshot project-incarnation hash to match creationTag.",
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
method: "GET",
|
|
2603
|
+
endpoint: permissionsEndpoint,
|
|
2604
|
+
intent: "Read current permissions; an equal hash makes the PUT unnecessary.",
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
method: "GET",
|
|
2608
|
+
endpoint: projectDetailsEndpoint,
|
|
2609
|
+
intent: "Recheck the project incarnation after the permission read and immediately before any PUT.",
|
|
2610
|
+
},
|
|
2611
|
+
],
|
|
2612
|
+
conditionalWrite: {
|
|
2613
|
+
method: "PUT",
|
|
2614
|
+
endpoint: permissionsEndpoint,
|
|
2615
|
+
when: "permissions-differ-and-dry-run-is-false",
|
|
2616
|
+
},
|
|
2617
|
+
verificationRequests: [
|
|
2618
|
+
{
|
|
2619
|
+
method: "GET",
|
|
2620
|
+
endpoint: permissionsEndpoint,
|
|
2621
|
+
intent: "Verify DSS persisted the desired permission hash.",
|
|
2622
|
+
},
|
|
2623
|
+
{
|
|
2624
|
+
method: "GET",
|
|
2625
|
+
endpoint: projectDetailsEndpoint,
|
|
2626
|
+
intent: "Detect project-key reuse across the permission write.",
|
|
2627
|
+
},
|
|
2628
|
+
],
|
|
2629
|
+
incarnationControl: "client-side-non-atomic-stale-identity-check",
|
|
2630
|
+
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.",
|
|
2631
|
+
},
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
case "statistics.create-worksheet":
|
|
2635
|
+
return {
|
|
2636
|
+
method: "POST",
|
|
2637
|
+
endpoint: statisticsWorksheetsEndpoint(args[0]),
|
|
2638
|
+
identifiers: { dataset: args[0], },
|
|
2639
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2640
|
+
};
|
|
2641
|
+
case "statistics.update-worksheet":
|
|
2642
|
+
return {
|
|
2643
|
+
method: "PUT",
|
|
2644
|
+
endpoint: statisticsWorksheetEndpoint(args[0], args[1]),
|
|
2645
|
+
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
2646
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2647
|
+
};
|
|
2648
|
+
case "statistics.delete-worksheet":
|
|
2649
|
+
return {
|
|
2650
|
+
method: "DELETE",
|
|
2651
|
+
endpoint: statisticsWorksheetEndpoint(args[0], args[1]),
|
|
2652
|
+
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
2653
|
+
};
|
|
2654
|
+
case "statistics.run-worksheet":
|
|
2655
|
+
return {
|
|
2656
|
+
method: "POST",
|
|
2657
|
+
endpoint: `${statisticsWorksheetEndpoint(args[0], args[1])}/actions/run-card`,
|
|
2658
|
+
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
2659
|
+
};
|
|
2660
|
+
case "statistics.run-card":
|
|
2661
|
+
return {
|
|
2662
|
+
method: "POST",
|
|
2663
|
+
endpoint: `${statisticsWorksheetEndpoint(args[0], args[1])}/actions/run-card`,
|
|
2664
|
+
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
2665
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2666
|
+
};
|
|
2667
|
+
case "statistics.run-computation":
|
|
2668
|
+
return {
|
|
2669
|
+
method: "POST",
|
|
2670
|
+
endpoint: `${statisticsWorksheetEndpoint(args[0], args[1])}/actions/run-computation`,
|
|
2671
|
+
identifiers: { dataset: args[0], worksheetId: args[1], },
|
|
2672
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2673
|
+
};
|
|
2674
|
+
case "webapp.create":
|
|
2675
|
+
return {
|
|
2676
|
+
method: "POST",
|
|
2677
|
+
endpoint: projectEndpoint("/webapps/"),
|
|
2678
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
2679
|
+
};
|
|
2680
|
+
case "webapp.update-settings": {
|
|
2681
|
+
const patch = requiredPlanJsonInput(flags, entry.usage);
|
|
2682
|
+
const expectHash = flags["expect-hash"];
|
|
2683
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
2684
|
+
throw new UsageError("Expected webapp hash must be a 64-character SHA-256 hex digest.");
|
|
2685
|
+
}
|
|
2686
|
+
const endpoint = projectEndpoint(`/webapps/${encodeURIComponent(id)}`);
|
|
2687
|
+
return {
|
|
2688
|
+
exact: false,
|
|
2689
|
+
reason: "Apply GETs the current settings, deep-merges the patch, then PUTs the full object.",
|
|
2690
|
+
method: "PUT",
|
|
2691
|
+
endpoint,
|
|
2692
|
+
identifiers: {
|
|
2693
|
+
webappId: id,
|
|
2694
|
+
patchHash: stableHash(patch),
|
|
2695
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
2696
|
+
},
|
|
2697
|
+
payload: { patch: "<omitted>", patchHash: stableHash(patch), },
|
|
2698
|
+
requests: [
|
|
2699
|
+
{ method: "GET", endpoint, purpose: "read full settings and verify expectHash", },
|
|
2700
|
+
{
|
|
2701
|
+
method: "PUT",
|
|
2702
|
+
endpoint,
|
|
2703
|
+
condition: "settings read and expected hash matched",
|
|
2704
|
+
payload: "<merged full settings>",
|
|
2705
|
+
},
|
|
2706
|
+
],
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
case "webapp.stop-backend":
|
|
2710
|
+
return {
|
|
2711
|
+
method: "PUT",
|
|
2712
|
+
endpoint: projectEndpoint(`/webapps/${encodeURIComponent(id)}/backend/actions/stop`),
|
|
2713
|
+
identifiers: { webappId: id, },
|
|
2714
|
+
payload: {},
|
|
2715
|
+
};
|
|
2716
|
+
case "webapp.restart-backend": {
|
|
2717
|
+
const endpoint = projectEndpoint(`/webapps/${encodeURIComponent(id)}/backend/actions/restart`);
|
|
2718
|
+
return {
|
|
2719
|
+
method: "PUT",
|
|
2720
|
+
endpoint,
|
|
2721
|
+
identifiers: { webappId: id, },
|
|
2722
|
+
payload: {},
|
|
2723
|
+
wait: {
|
|
2724
|
+
requested: flags["wait"] === true,
|
|
2725
|
+
timeoutMs: num(flags["timeout"], "--timeout"),
|
|
2726
|
+
pollIntervalMs: num(flags["poll-interval"], "--poll-interval"),
|
|
2727
|
+
},
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
case "api-service.create":
|
|
2731
|
+
return {
|
|
2732
|
+
method: "POST",
|
|
2733
|
+
endpoint: projectEndpoint(`/apiservices/${encodeURIComponent(id)}`),
|
|
2734
|
+
identifiers: { serviceId: id, },
|
|
2735
|
+
payload: {},
|
|
2736
|
+
};
|
|
2737
|
+
case "api-service.save-settings": {
|
|
2738
|
+
const settings = requiredPlanJsonInput(flags, entry.usage);
|
|
2739
|
+
const expectHash = flags["expect-hash"];
|
|
2740
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
2741
|
+
throw new UsageError("Expected API service settings hash must be a 64-character SHA-256 hex digest.");
|
|
2742
|
+
}
|
|
2743
|
+
const endpoint = projectEndpoint(`/apiservices/${encodeURIComponent(id)}/settings`);
|
|
2744
|
+
const payload = {
|
|
2745
|
+
settings: "<omitted>",
|
|
2746
|
+
settingsHash: stableHash(settings),
|
|
2747
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
2748
|
+
};
|
|
2749
|
+
return {
|
|
2750
|
+
method: "PUT",
|
|
2751
|
+
endpoint,
|
|
2752
|
+
identifiers: { serviceId: id, },
|
|
2753
|
+
payload,
|
|
2754
|
+
requests: [
|
|
2755
|
+
...(expectHash
|
|
2756
|
+
? [{ method: "GET", endpoint, purpose: "verify expectHash", },]
|
|
2757
|
+
: []),
|
|
2758
|
+
{
|
|
2759
|
+
method: "PUT",
|
|
2760
|
+
endpoint,
|
|
2761
|
+
condition: expectHash ? "hash matched" : "unconditional",
|
|
2762
|
+
payload,
|
|
2763
|
+
},
|
|
2764
|
+
],
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
case "api-service.add-prediction-endpoint": {
|
|
2768
|
+
const endpoint = projectEndpoint(`/apiservices/${encodeURIComponent(id)}/settings`);
|
|
2769
|
+
return {
|
|
2770
|
+
exact: false,
|
|
2771
|
+
reason: "Apply GETs the full settings, appends one endpoint, then PUTs the full object.",
|
|
2772
|
+
method: "PUT",
|
|
2773
|
+
endpoint,
|
|
2774
|
+
identifiers: { serviceId: id, endpointId: args[1], savedModelId: args[2], },
|
|
2775
|
+
payload: { id: args[1], type: "STD_PREDICTION", modelRef: args[2], },
|
|
2776
|
+
requests: [
|
|
2777
|
+
{ method: "GET", endpoint, purpose: "read full settings", },
|
|
2778
|
+
{ method: "PUT", endpoint, payload: "<merged full settings>", },
|
|
2779
|
+
],
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
case "api-service.create-package":
|
|
2783
|
+
case "api-service.delete-package":
|
|
2784
|
+
case "api-service.publish-package": {
|
|
2785
|
+
const serviceId = args[0];
|
|
2786
|
+
const packageId = args[1];
|
|
2787
|
+
const packageEndpoint = projectEndpoint(`/apiservices/${encodeURIComponent(serviceId)}/packages/${encodeURIComponent(packageId)}`);
|
|
2788
|
+
if (action === "delete-package") {
|
|
2789
|
+
return {
|
|
2790
|
+
method: "DELETE",
|
|
2791
|
+
endpoint: packageEndpoint,
|
|
2792
|
+
identifiers: { serviceId, packageId, },
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
if (action === "publish-package") {
|
|
2796
|
+
return {
|
|
2797
|
+
method: "POST",
|
|
2798
|
+
endpoint: `${packageEndpoint}/publish${querySuffix({
|
|
2799
|
+
publishedServiceId: flags["published-service-id"],
|
|
2800
|
+
})}`,
|
|
2801
|
+
identifiers: { serviceId, packageId, },
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
return {
|
|
2805
|
+
method: "POST",
|
|
2806
|
+
endpoint: packageEndpoint + querySuffix({
|
|
2807
|
+
releaseNotes: flags["release-notes"],
|
|
2808
|
+
}),
|
|
2809
|
+
identifiers: { serviceId, packageId, },
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
case "bundle.export": {
|
|
2813
|
+
const evaluateProjectStandardsChecks = parseBooleanOption(flags["evaluate-standards-checks"], "--evaluate-standards-checks") ?? true;
|
|
2814
|
+
return {
|
|
2815
|
+
method: "PUT",
|
|
2816
|
+
endpoint: projectEndpoint("/bundles/exported/" + encodeURIComponent(id)) + querySuffix({
|
|
2817
|
+
releaseNotes: flags["release-notes"],
|
|
2818
|
+
evaluateProjectStandardsChecks,
|
|
2819
|
+
}),
|
|
2820
|
+
identifiers: { bundleId: id, },
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
case "bundle.publish":
|
|
2824
|
+
return {
|
|
2825
|
+
method: "POST",
|
|
2826
|
+
endpoint: projectEndpoint("/bundles/" + encodeURIComponent(id) + "/publish") + querySuffix({
|
|
2827
|
+
publishedProjectKey: flags["published-project-key"],
|
|
2828
|
+
}),
|
|
2829
|
+
identifiers: { bundleId: id, },
|
|
2830
|
+
payload: {},
|
|
2831
|
+
};
|
|
2832
|
+
case "bundle.activate": {
|
|
2833
|
+
const rawScenarios = flags["scenarios"];
|
|
2834
|
+
let scenariosToEnable;
|
|
2835
|
+
if (rawScenarios !== undefined && rawScenarios !== false) {
|
|
2836
|
+
const parsed = json(rawScenarios);
|
|
2837
|
+
if (typeof parsed !== "object"
|
|
2838
|
+
|| parsed === null
|
|
2839
|
+
|| Array.isArray(parsed)
|
|
2840
|
+
|| !Object.values(parsed).every((value) => typeof value === "boolean")) {
|
|
2841
|
+
throw new UsageError("--scenarios must be a JSON object mapping scenario IDs to true|false.");
|
|
2842
|
+
}
|
|
2843
|
+
scenariosToEnable = parsed;
|
|
2844
|
+
}
|
|
2845
|
+
return {
|
|
2846
|
+
method: "POST",
|
|
2847
|
+
endpoint: projectEndpoint(`/bundles/imported/${encodeURIComponent(id)}/actions/activate`),
|
|
2848
|
+
identifiers: { bundleId: id, },
|
|
2849
|
+
payload: scenariosToEnable === undefined
|
|
2850
|
+
? {}
|
|
2851
|
+
: { scenariosActiveOnActivation: scenariosToEnable, },
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
case "bundle.preload":
|
|
2855
|
+
return {
|
|
2856
|
+
method: "POST",
|
|
2857
|
+
endpoint: projectEndpoint(`/bundles/imported/${encodeURIComponent(id)}/actions/preload`),
|
|
2858
|
+
identifiers: { bundleId: id, },
|
|
2859
|
+
payload: {},
|
|
2860
|
+
};
|
|
2861
|
+
case "project-library.create-file":
|
|
2862
|
+
case "project-library.create-folder": {
|
|
2863
|
+
const libraryPath = validateLibraryPath(id);
|
|
2864
|
+
const kind = action === "create-file" ? "contents" : "folders";
|
|
2865
|
+
return {
|
|
2866
|
+
exact: false,
|
|
2867
|
+
reason: "Apply performs a live absence check before POST; --if-not-exists may stop after that read, and create never overwrites an existing item.",
|
|
2868
|
+
method: "POST",
|
|
2869
|
+
endpoint: projectEndpoint(`/libraries/${kind}/${encodeLibraryPath(libraryPath)}`),
|
|
2870
|
+
identifiers: { path: libraryPath, },
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
case "project-library.put": {
|
|
2874
|
+
const libraryPath = validateLibraryPath(id);
|
|
2875
|
+
const endpoint = projectEndpoint(`/libraries/contents/${encodeLibraryPath(libraryPath)}`);
|
|
2876
|
+
const payload = projectLibraryPutPayload(flags);
|
|
2877
|
+
return {
|
|
2878
|
+
method: "POST",
|
|
2879
|
+
endpoint,
|
|
2880
|
+
identifiers: {
|
|
2881
|
+
path: libraryPath,
|
|
2882
|
+
concurrencyControl: payload.expectSha256
|
|
2883
|
+
? PROJECT_LIBRARY_CONCURRENCY_CONTROL
|
|
2884
|
+
: "none",
|
|
2885
|
+
},
|
|
2886
|
+
payload,
|
|
2887
|
+
...(payload.expectSha256
|
|
2888
|
+
? {
|
|
2889
|
+
requests: [
|
|
2890
|
+
{
|
|
2891
|
+
method: "GET",
|
|
2892
|
+
endpoint: `${endpoint}?dataEncoding=base64`,
|
|
2893
|
+
purpose: "verify expectSha256",
|
|
2894
|
+
},
|
|
2895
|
+
{ method: "POST", endpoint, condition: "hash matched", payload, },
|
|
2896
|
+
],
|
|
2897
|
+
}
|
|
2898
|
+
: {}),
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
case "project-library.delete": {
|
|
2902
|
+
const libraryPath = validateLibraryPath(id);
|
|
2903
|
+
return {
|
|
2904
|
+
method: "DELETE",
|
|
2905
|
+
endpoint: projectEndpoint(`/libraries/contents/${encodeLibraryPath(libraryPath)}`),
|
|
2906
|
+
identifiers: { path: libraryPath, },
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
case "project-library.rename": {
|
|
2910
|
+
const libraryPath = validateLibraryPath(id);
|
|
2911
|
+
const newName = validateLibraryName(args[1]);
|
|
2912
|
+
return {
|
|
2913
|
+
method: "POST",
|
|
2914
|
+
endpoint: projectEndpoint("/libraries/contents-actions/rename/"),
|
|
2915
|
+
identifiers: { path: libraryPath, newName, },
|
|
2916
|
+
payload: { oldPath: `/${libraryPath}`, newName, },
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
case "project-library.move": {
|
|
2920
|
+
const libraryPath = validateLibraryPath(id);
|
|
2921
|
+
const destinationFolder = validateLibraryDestinationPath(args[1]);
|
|
2922
|
+
return {
|
|
2923
|
+
method: "POST",
|
|
2924
|
+
endpoint: projectEndpoint("/libraries/contents-actions/move"),
|
|
2925
|
+
identifiers: { path: libraryPath, destinationFolder, },
|
|
2926
|
+
payload: { oldPath: `/${libraryPath}`, newPath: destinationFolder, },
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
case "code-env.create": {
|
|
2930
|
+
const wait = codeEnvWait(flags);
|
|
2931
|
+
return {
|
|
2932
|
+
method: "POST",
|
|
2933
|
+
endpoint: `${codeEnvEndpoint()}?wait=${wait}`,
|
|
2934
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
2935
|
+
payload: {
|
|
2936
|
+
...codeEnvParams(flags),
|
|
2937
|
+
deploymentMode: requiredPlanFlag(flags, "deployment-mode", entry.usage),
|
|
2938
|
+
},
|
|
2939
|
+
wait,
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
case "code-env.set-definition": {
|
|
2943
|
+
const definition = requiredPlanJsonInput(flags, entry.usage);
|
|
2944
|
+
const expectHash = flags["expect-hash"];
|
|
2945
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
2946
|
+
throw new UsageError("--expect-hash must be a 64-character SHA-256 hex digest.");
|
|
2947
|
+
}
|
|
2948
|
+
const endpoint = codeEnvEndpoint();
|
|
2949
|
+
const definitionHash = stableHash(definition);
|
|
2950
|
+
const normalizedExpectHash = expectHash?.toLowerCase();
|
|
2951
|
+
return {
|
|
2952
|
+
exact: false,
|
|
2953
|
+
reason: "Apply PUTs the supplied definition object unchanged; this plan omits that body and reports only its hash.",
|
|
2954
|
+
method: "PUT",
|
|
2955
|
+
endpoint,
|
|
2956
|
+
identifiers: {
|
|
2957
|
+
lang: codeEnvLang(args[0], entry.usage),
|
|
2958
|
+
name: args[1],
|
|
2959
|
+
definitionHash,
|
|
2960
|
+
...(normalizedExpectHash ? { expectHash: normalizedExpectHash, } : {}),
|
|
2961
|
+
},
|
|
2962
|
+
payload: "<omitted>",
|
|
2963
|
+
...(normalizedExpectHash
|
|
2964
|
+
? {
|
|
2965
|
+
requests: [
|
|
2966
|
+
{ method: "GET", endpoint, purpose: "verify expectHash", },
|
|
2967
|
+
{
|
|
2968
|
+
method: "PUT",
|
|
2969
|
+
endpoint,
|
|
2970
|
+
condition: "hash matched",
|
|
2971
|
+
payload: "<omitted>",
|
|
2972
|
+
redactedFields: ["payload",],
|
|
2973
|
+
},
|
|
2974
|
+
],
|
|
2975
|
+
}
|
|
2976
|
+
: {}),
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
case "code-env.set-packages": {
|
|
2980
|
+
const installCorePackages = parseBooleanOption(flags["install-core-packages"], "--install-core-packages");
|
|
2981
|
+
const expectHash = flags["expect-hash"];
|
|
2982
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
2983
|
+
throw new UsageError("--expect-hash must be a 64-character SHA-256 hex digest.");
|
|
2984
|
+
}
|
|
2985
|
+
const endpoint = codeEnvEndpoint();
|
|
2986
|
+
return {
|
|
2987
|
+
exact: false,
|
|
2988
|
+
reason: "Apply GETs the full definition, merges only package fields, then PUTs the full result.",
|
|
2989
|
+
method: "PUT",
|
|
2990
|
+
endpoint,
|
|
2991
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
2992
|
+
payload: {
|
|
2993
|
+
packages: codeEnvPackageList(flags),
|
|
2994
|
+
...(installCorePackages !== undefined ? { installCorePackages, } : {}),
|
|
2995
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
2996
|
+
},
|
|
2997
|
+
requests: [
|
|
2998
|
+
{ method: "GET", endpoint, purpose: "read full definition and verify expectHash", },
|
|
2999
|
+
{
|
|
3000
|
+
method: "PUT",
|
|
3001
|
+
endpoint,
|
|
3002
|
+
condition: "definition read and hash matched",
|
|
3003
|
+
payload: "<merged full definition>",
|
|
3004
|
+
},
|
|
3005
|
+
],
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
case "code-env.update-packages": {
|
|
3009
|
+
const versionToUpdate = typeof flags["env-version"] === "string"
|
|
3010
|
+
? flags["env-version"]
|
|
3011
|
+
: typeof flags["version"] === "string"
|
|
3012
|
+
? flags["version"]
|
|
3013
|
+
: undefined;
|
|
3014
|
+
const wait = codeEnvWait(flags);
|
|
3015
|
+
return {
|
|
3016
|
+
method: "POST",
|
|
3017
|
+
endpoint: `${codeEnvEndpoint("/packages")}${querySuffix({
|
|
3018
|
+
forceRebuildEnv: flags["force-rebuild"] === true,
|
|
3019
|
+
versionToUpdate,
|
|
3020
|
+
wait,
|
|
3021
|
+
})}`,
|
|
3022
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3023
|
+
wait,
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
case "code-env.update-images": {
|
|
3027
|
+
const wait = codeEnvWait(flags);
|
|
3028
|
+
return {
|
|
3029
|
+
method: "POST",
|
|
3030
|
+
endpoint: `${codeEnvEndpoint("/images")}${querySuffix({ envVersion: flags["env-version"], wait, })}`,
|
|
3031
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3032
|
+
wait,
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
case "code-env.set-jupyter": {
|
|
3036
|
+
const active = parseBooleanOption(flags["active"], "--active");
|
|
3037
|
+
if (active === undefined) {
|
|
3038
|
+
throw new UsageError(`--active is required. Usage: ${commandUsage("code-env", "set-jupyter")}`);
|
|
3039
|
+
}
|
|
3040
|
+
const wait = codeEnvWait(flags);
|
|
3041
|
+
return {
|
|
3042
|
+
method: "POST",
|
|
3043
|
+
endpoint: `${codeEnvEndpoint("/jupyter")}${querySuffix({ active, wait, })}`,
|
|
3044
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3045
|
+
wait,
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
case "code-env.delete": {
|
|
3049
|
+
const wait = codeEnvWait(flags);
|
|
3050
|
+
return {
|
|
3051
|
+
method: "DELETE",
|
|
3052
|
+
endpoint: `${codeEnvEndpoint()}?wait=${wait}`,
|
|
3053
|
+
identifiers: { lang: codeEnvLang(args[0], entry.usage), name: args[1], },
|
|
3054
|
+
wait,
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
case "notebook.save-jupyter": {
|
|
3058
|
+
const content = requiredPlanJsonInput(flags, entry.usage);
|
|
3059
|
+
const expectHash = flags["expect-hash"];
|
|
3060
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3061
|
+
throw new UsageError("Expected notebook hash must be a 64-character SHA-256 hex digest.");
|
|
3062
|
+
}
|
|
3063
|
+
const endpoint = projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}`);
|
|
3064
|
+
return {
|
|
3065
|
+
exact: false,
|
|
3066
|
+
reason: "A fresh GET selects POST when absent or PUT when present; a confirming GET supplies the persisted hash.",
|
|
3067
|
+
endpoint,
|
|
3068
|
+
identifiers: {
|
|
3069
|
+
name: id,
|
|
3070
|
+
contentHash: stableHash(content),
|
|
3071
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3072
|
+
},
|
|
3073
|
+
requests: [
|
|
3074
|
+
{ method: "GET", endpoint, purpose: "resolve create/update and check --expect-hash", },
|
|
3075
|
+
{
|
|
3076
|
+
method: "POST|PUT",
|
|
3077
|
+
endpoint,
|
|
3078
|
+
condition: "POST after not_found; otherwise PUT",
|
|
3079
|
+
payload: "<omitted>",
|
|
3080
|
+
redactedFields: ["payload",],
|
|
3081
|
+
},
|
|
3082
|
+
{ method: "GET", endpoint, purpose: "confirm persisted hash", },
|
|
3083
|
+
],
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
case "notebook.delete-jupyter":
|
|
3087
|
+
return {
|
|
3088
|
+
method: "DELETE",
|
|
3089
|
+
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}`),
|
|
3090
|
+
identifiers: { name: id, },
|
|
3091
|
+
};
|
|
3092
|
+
case "notebook.clear-jupyter-outputs":
|
|
3093
|
+
return {
|
|
3094
|
+
method: "DELETE",
|
|
3095
|
+
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(id)}/outputs`),
|
|
3096
|
+
identifiers: { name: id, },
|
|
3097
|
+
};
|
|
3098
|
+
case "notebook.unload-jupyter":
|
|
3099
|
+
if (flags["all"] === true) {
|
|
3100
|
+
if (args.length > 0)
|
|
3101
|
+
throw new UsageError(entry.usage);
|
|
3102
|
+
return {
|
|
3103
|
+
exact: false,
|
|
3104
|
+
reason: "DSS has no unload-all endpoint; apply lists active notebooks and sessions, then issues one verified session DELETE per result.",
|
|
3105
|
+
identifiers: { all: true, },
|
|
3106
|
+
requests: [
|
|
3107
|
+
{ method: "GET", endpoint: projectEndpoint("/jupyter-notebooks/?active=true"), },
|
|
3108
|
+
{
|
|
3109
|
+
method: "GET",
|
|
3110
|
+
endpoint: projectEndpoint("/jupyter-notebooks/{name}/sessions"),
|
|
3111
|
+
forEach: "active notebook",
|
|
3112
|
+
},
|
|
3113
|
+
{
|
|
3114
|
+
method: "DELETE",
|
|
3115
|
+
endpoint: projectEndpoint("/jupyter-notebooks/{name}/sessions/{sessionId}"),
|
|
3116
|
+
forEach: "listed session",
|
|
3117
|
+
},
|
|
3118
|
+
],
|
|
3119
|
+
};
|
|
3120
|
+
}
|
|
3121
|
+
requireArgs(args, 2, entry.usage);
|
|
3122
|
+
return {
|
|
3123
|
+
method: "DELETE",
|
|
3124
|
+
endpoint: projectEndpoint(`/jupyter-notebooks/${encodeURIComponent(args[0])}/sessions/${encodeURIComponent(args[1])}`),
|
|
3125
|
+
identifiers: { name: args[0], sessionId: args[1], },
|
|
3126
|
+
};
|
|
3127
|
+
case "notebook.save-sql": {
|
|
3128
|
+
const content = requiredPlanJsonInput(flags, entry.usage);
|
|
3129
|
+
const expectHash = flags["expect-hash"];
|
|
3130
|
+
if (expectHash !== undefined && !/^[a-fA-F0-9]{64}$/.test(expectHash)) {
|
|
3131
|
+
throw new UsageError("Expected notebook hash must be a 64-character SHA-256 hex digest.");
|
|
3132
|
+
}
|
|
3133
|
+
const endpoint = projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}`);
|
|
3134
|
+
return {
|
|
3135
|
+
exact: false,
|
|
3136
|
+
reason: "A fresh GET selects collection POST when absent or entity PUT when present; a confirming GET supplies the persisted hash.",
|
|
3137
|
+
endpoint,
|
|
3138
|
+
identifiers: {
|
|
3139
|
+
id,
|
|
3140
|
+
contentHash: stableHash(content),
|
|
3141
|
+
...(expectHash ? { expectHash: expectHash.toLowerCase(), } : {}),
|
|
3142
|
+
},
|
|
3143
|
+
requests: [
|
|
3144
|
+
{ method: "GET", endpoint, purpose: "resolve create/update and check --expect-hash", },
|
|
3145
|
+
{
|
|
3146
|
+
method: "POST",
|
|
3147
|
+
endpoint: projectEndpoint("/sql-notebooks/"),
|
|
3148
|
+
condition: "after not_found",
|
|
3149
|
+
payload: "<omitted; includes id and projectKey>",
|
|
3150
|
+
redactedFields: ["payload",],
|
|
3151
|
+
},
|
|
3152
|
+
{
|
|
3153
|
+
method: "PUT",
|
|
3154
|
+
endpoint,
|
|
3155
|
+
condition: "when present",
|
|
3156
|
+
payload: "<omitted>",
|
|
3157
|
+
redactedFields: ["payload",],
|
|
3158
|
+
},
|
|
3159
|
+
{ method: "GET", endpoint, purpose: "confirm persisted hash", },
|
|
3160
|
+
],
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
case "notebook.delete-sql":
|
|
3164
|
+
return {
|
|
3165
|
+
method: "DELETE",
|
|
3166
|
+
endpoint: projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}`),
|
|
3167
|
+
identifiers: { id, },
|
|
3168
|
+
};
|
|
3169
|
+
case "notebook.clear-sql-history":
|
|
3170
|
+
return {
|
|
3171
|
+
method: "POST",
|
|
3172
|
+
endpoint: projectEndpoint(`/sql-notebooks/${encodeURIComponent(id)}/history/clear`),
|
|
3173
|
+
identifiers: { id, },
|
|
3174
|
+
payload: {
|
|
3175
|
+
cellId: flags["cell-id"],
|
|
3176
|
+
numRunsToRetain: num(flags["retain"], "--retain"),
|
|
3177
|
+
},
|
|
3178
|
+
};
|
|
3179
|
+
case "project.create": {
|
|
3180
|
+
const settings = jsonInput(flags) ?? null;
|
|
3181
|
+
return {
|
|
3182
|
+
method: "POST",
|
|
3183
|
+
endpoint: "/public/api/projects/",
|
|
3184
|
+
identifiers: { projectKey: args[0], name: args[1], },
|
|
3185
|
+
payload: {
|
|
3186
|
+
projectKey: args[0],
|
|
3187
|
+
name: args[1],
|
|
3188
|
+
owner: flags["owner"] ?? null,
|
|
3189
|
+
settings,
|
|
3190
|
+
description: null,
|
|
3191
|
+
permissions: [],
|
|
3192
|
+
tags: [],
|
|
3193
|
+
},
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
case "project.delete": {
|
|
3197
|
+
const expectedProjectIncarnation = flags["expect-project-incarnation"] === undefined
|
|
3198
|
+
? undefined
|
|
3199
|
+
: requiredPlanFlag(flags, "expect-project-incarnation", entry.usage);
|
|
3200
|
+
if (expectedProjectIncarnation !== undefined
|
|
3201
|
+
&& !/^[0-9a-f]{64}$/.test(expectedProjectIncarnation)) {
|
|
3202
|
+
throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
|
|
3203
|
+
}
|
|
3204
|
+
const endpoint = `/public/api/projects/${encodeURIComponent(id)}${querySuffix({
|
|
3205
|
+
clearManagedDatasets: flags["drop-data"] === true,
|
|
3206
|
+
clearOutputManagedFolders: false,
|
|
3207
|
+
clearJobAndScenarioLogs: true,
|
|
3208
|
+
wait: true,
|
|
3209
|
+
})}`;
|
|
3210
|
+
const guarded = flags["if-exists"] === true
|
|
3211
|
+
|| executionMode(flags).dryRun
|
|
3212
|
+
|| expectedProjectIncarnation !== undefined;
|
|
3213
|
+
if (!guarded) {
|
|
3214
|
+
return {
|
|
3215
|
+
method: "DELETE",
|
|
3216
|
+
endpoint,
|
|
3217
|
+
identifiers: { projectKey: id, },
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
const projectProbe = `/public/api/projects/${encodeURIComponent(id)}/`;
|
|
3221
|
+
const incarnationGate = expectedProjectIncarnation === undefined
|
|
3222
|
+
? { required: false, provided: false, }
|
|
3223
|
+
: {
|
|
3224
|
+
required: true,
|
|
3225
|
+
provided: true,
|
|
3226
|
+
expectedHash: expectedProjectIncarnation,
|
|
3227
|
+
};
|
|
3228
|
+
if (executionMode(flags).dryRun) {
|
|
3229
|
+
return {
|
|
3230
|
+
method: "GET",
|
|
3231
|
+
endpoint: projectProbe,
|
|
3232
|
+
identifiers: {
|
|
3233
|
+
projectKey: id,
|
|
3234
|
+
dryRun: true,
|
|
3235
|
+
ifExists: flags["if-exists"] === true,
|
|
3236
|
+
projectIncarnationGate: incarnationGate,
|
|
3237
|
+
note: "Read-only guarded-delete preflight. No DELETE is issued; a supplied creationTag hash must match.",
|
|
3238
|
+
},
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
return {
|
|
3242
|
+
method: "DELETE",
|
|
3243
|
+
endpoint,
|
|
3244
|
+
identifiers: {
|
|
3245
|
+
projectKey: id,
|
|
3246
|
+
ifExists: flags["if-exists"] === true,
|
|
3247
|
+
projectIncarnationGate: incarnationGate,
|
|
3248
|
+
incarnationControl: expectedProjectIncarnation === undefined
|
|
3249
|
+
? "none"
|
|
3250
|
+
: "client-side-non-atomic-stale-identity-check",
|
|
3251
|
+
preflightRequests: [{
|
|
3252
|
+
method: "GET",
|
|
3253
|
+
endpoint: projectProbe,
|
|
3254
|
+
intent: expectedProjectIncarnation === undefined
|
|
3255
|
+
? "Confirm existence before a convergent delete."
|
|
3256
|
+
: "Recompute the current project-incarnation hash from creationTag before DELETE.",
|
|
3257
|
+
},],
|
|
3258
|
+
note: expectedProjectIncarnation === undefined
|
|
3259
|
+
? "Convergent delete: a 404 preflight is an already-absent success only with --if-exists."
|
|
3260
|
+
: "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.",
|
|
3261
|
+
},
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
case "project.duplicate": {
|
|
3265
|
+
const options = jsonInput(flags);
|
|
3266
|
+
return {
|
|
3267
|
+
method: "POST",
|
|
3268
|
+
endpoint: `/public/api/projects/${encodeURIComponent(args[0])}/duplicate/`,
|
|
3269
|
+
identifiers: { sourceKey: args[0], targetKey: args[1], targetName: args[2], },
|
|
3270
|
+
payload: {
|
|
3271
|
+
targetProjectName: args[2],
|
|
3272
|
+
targetProjectKey: args[1],
|
|
3273
|
+
duplicationMode: options?.duplicationMode ?? "MINIMAL",
|
|
3274
|
+
exportAnalysisModels: options?.exportAnalysisModels ?? true,
|
|
3275
|
+
exportSavedModels: options?.exportSavedModels ?? true,
|
|
3276
|
+
exportGitRepository: options?.exportGitRepository ?? null,
|
|
3277
|
+
exportInsightsData: options?.exportInsightsData ?? true,
|
|
3278
|
+
remapping: options?.remapping ?? {},
|
|
3279
|
+
...(options?.targetProjectFolderId !== undefined
|
|
3280
|
+
? { targetProjectFolderId: options.targetProjectFolderId, }
|
|
3281
|
+
: {}),
|
|
3282
|
+
},
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
case "project.import": {
|
|
3286
|
+
const settings = jsonInput(flags) ?? {};
|
|
3287
|
+
const rawTarget = flags["target-project-key"];
|
|
3288
|
+
const targetProjectKey = rawTarget?.trim();
|
|
3289
|
+
if (rawTarget !== undefined && targetProjectKey === "") {
|
|
3290
|
+
throw new UsageError(`--target-project-key must not be empty. Usage: ${entry.usage}`);
|
|
3291
|
+
}
|
|
3292
|
+
const settingsTarget = settings.targetProjectKey;
|
|
3293
|
+
if (settingsTarget !== undefined
|
|
3294
|
+
&& (typeof settingsTarget !== "string" || settingsTarget.trim() === "")) {
|
|
3295
|
+
throw new UsageError(`targetProjectKey in import settings must be a non-empty string. Usage: ${entry.usage}`);
|
|
3296
|
+
}
|
|
3297
|
+
if (targetProjectKey !== undefined
|
|
3298
|
+
&& settingsTarget !== undefined
|
|
3299
|
+
&& targetProjectKey !== settingsTarget.trim()) {
|
|
3300
|
+
throw new UsageError(`--target-project-key conflicts with targetProjectKey in import settings. Usage: ${entry.usage}`);
|
|
3301
|
+
}
|
|
3302
|
+
const processPayload = targetProjectKey === undefined
|
|
3303
|
+
? settings
|
|
3304
|
+
: { ...settings, targetProjectKey, };
|
|
3305
|
+
const finalizedPayload = Object.keys(processPayload).length === 0
|
|
3306
|
+
? { _: "_", }
|
|
3307
|
+
: processPayload;
|
|
3308
|
+
const upload = {
|
|
3309
|
+
method: "POST",
|
|
3310
|
+
endpoint: "/public/api/projects/import/upload",
|
|
3311
|
+
payload: {
|
|
3312
|
+
contentType: "multipart/form-data",
|
|
3313
|
+
fileField: "file",
|
|
3314
|
+
filePath: id,
|
|
3315
|
+
fileName: "tmp-import.zip",
|
|
3316
|
+
},
|
|
3317
|
+
};
|
|
3318
|
+
return {
|
|
3319
|
+
...upload,
|
|
3320
|
+
identifiers: {
|
|
3321
|
+
filePath: id,
|
|
3322
|
+
...(targetProjectKey ? { targetProjectKey, } : {}),
|
|
3323
|
+
archivePreflight: {
|
|
3324
|
+
local: true,
|
|
3325
|
+
required: true,
|
|
3326
|
+
checks: [
|
|
3327
|
+
"zip-integrity",
|
|
3328
|
+
"safe-unique-members",
|
|
3329
|
+
"manifest",
|
|
3330
|
+
"flow-references",
|
|
3331
|
+
"orphan-members",
|
|
3332
|
+
],
|
|
3333
|
+
},
|
|
3334
|
+
successVerification: {
|
|
3335
|
+
usedProjectKeyRequired: true,
|
|
3336
|
+
projectReadRequired: true,
|
|
3337
|
+
projectIncarnationRequired: true,
|
|
3338
|
+
remappingReported: true,
|
|
3339
|
+
},
|
|
3340
|
+
...(flags["record-cleanup"] === undefined
|
|
3341
|
+
? {}
|
|
3342
|
+
: {
|
|
3343
|
+
cleanupBinding: "actual used project key plus verified creationTag incarnation hash",
|
|
3344
|
+
}),
|
|
3345
|
+
},
|
|
3346
|
+
requests: [
|
|
3347
|
+
{ sequence: 1, ...upload, },
|
|
3348
|
+
{
|
|
3349
|
+
sequence: 2,
|
|
3350
|
+
method: "POST",
|
|
3351
|
+
endpoint: "/public/api/projects/import/{importId}/process",
|
|
3352
|
+
pathBindings: { importId: "requests[0].response.id", },
|
|
3353
|
+
payload: finalizedPayload,
|
|
3354
|
+
},
|
|
3355
|
+
],
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
case "project.settings-set":
|
|
3359
|
+
return {
|
|
3360
|
+
method: "PUT",
|
|
3361
|
+
endpoint: projectEndpoint("/settings"),
|
|
3362
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3363
|
+
};
|
|
3364
|
+
case "project.permissions-set":
|
|
3365
|
+
return {
|
|
3366
|
+
method: "PUT",
|
|
3367
|
+
endpoint: projectEndpoint("/permissions"),
|
|
3368
|
+
payload: requiredPlanJsonInput(flags, entry.usage),
|
|
3369
|
+
};
|
|
3370
|
+
case "continuous-activity.start":
|
|
3371
|
+
return {
|
|
3372
|
+
method: "POST",
|
|
3373
|
+
endpoint: projectEndpoint(`/continuous-activities/${encodeURIComponent(id)}/start`),
|
|
3374
|
+
identifiers: { recipeId: id, },
|
|
3375
|
+
payload: jsonInput(flags) ?? {},
|
|
3376
|
+
};
|
|
3377
|
+
case "continuous-activity.stop":
|
|
3378
|
+
return {
|
|
3379
|
+
method: "POST",
|
|
3380
|
+
endpoint: projectEndpoint(`/continuous-activities/${encodeURIComponent(id)}/stop`),
|
|
3381
|
+
identifiers: { recipeId: id, },
|
|
3382
|
+
};
|
|
3383
|
+
case "metrics.dataset-compute":
|
|
3384
|
+
return {
|
|
3385
|
+
method: "POST",
|
|
3386
|
+
endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/actions/computeMetrics?partition=`),
|
|
3387
|
+
identifiers: { dataset: id, },
|
|
3388
|
+
};
|
|
3389
|
+
case "flow-zone.organize":
|
|
3390
|
+
return {
|
|
3391
|
+
method: "POST",
|
|
3392
|
+
endpoint: projectEndpoint("/flow/zones"),
|
|
3393
|
+
payload: jsonInput(flags),
|
|
3394
|
+
};
|
|
3395
|
+
/* ---- project-git: mutations only; reads are never planned ---- */
|
|
3396
|
+
case "project-git.set-remote": {
|
|
3397
|
+
const remote = flags["name"] ?? "origin";
|
|
3398
|
+
return {
|
|
3399
|
+
method: "POST",
|
|
3400
|
+
endpoint: projectGitEndpoint(projectKey, `/remotes/${encodeURIComponent(remote)}`),
|
|
3401
|
+
identifiers: { remote, },
|
|
3402
|
+
payload: { url: requiredPlanRepositoryUrl(flags, "repository", entry.usage), },
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
case "project-git.remove-remote": {
|
|
3406
|
+
const remote = flags["name"] ?? "origin";
|
|
3407
|
+
return {
|
|
3408
|
+
method: "DELETE",
|
|
3409
|
+
endpoint: projectGitEndpoint(projectKey, `/remotes/${encodeURIComponent(remote)}`),
|
|
3410
|
+
identifiers: { remote, },
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
case "project-git.create-branch":
|
|
3414
|
+
return {
|
|
3415
|
+
method: "POST",
|
|
3416
|
+
endpoint: projectGitEndpoint(projectKey, "/branches/"),
|
|
3417
|
+
identifiers: { name: id, },
|
|
3418
|
+
payload: {
|
|
3419
|
+
name: id,
|
|
3420
|
+
commit: flags["commit"] ?? null,
|
|
3421
|
+
duplicateProject: parseBooleanOption(flags["duplicate-project"], "--duplicate-project") ?? false,
|
|
3422
|
+
targetProjectKey: flags["target-project-key"] ?? null,
|
|
3423
|
+
targetProjectFolderId: flags["target-project-folder-id"] ?? null,
|
|
3424
|
+
},
|
|
3425
|
+
};
|
|
3426
|
+
case "project-git.delete-branch": {
|
|
3427
|
+
const remote = parseBooleanOption(flags["remote"], "--remote") ?? false;
|
|
3428
|
+
return {
|
|
3429
|
+
method: "POST",
|
|
3430
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/deleteBranch"),
|
|
3431
|
+
identifiers: { name: id, remote, },
|
|
3432
|
+
payload: {
|
|
3433
|
+
name: id,
|
|
3434
|
+
remote,
|
|
3435
|
+
deleteRemotely: parseBooleanOption(flags["delete-remotely"], "--delete-remotely") ?? false,
|
|
3436
|
+
forceDelete: parseBooleanOption(flags["force-delete"], "--force-delete") ?? false,
|
|
3437
|
+
},
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
case "project-git.switch":
|
|
3441
|
+
return {
|
|
3442
|
+
method: "POST",
|
|
3443
|
+
endpoint: projectGitEndpoint(projectKey, `/actions/switchBranch${querySuffix({ branchName: id, })}`),
|
|
3444
|
+
identifiers: { branch: id, },
|
|
3445
|
+
};
|
|
3446
|
+
case "project-git.create-tag":
|
|
3447
|
+
return {
|
|
3448
|
+
method: "POST",
|
|
3449
|
+
endpoint: projectGitEndpoint(projectKey, "/tags/"),
|
|
3450
|
+
identifiers: { name: id, },
|
|
3451
|
+
payload: {
|
|
3452
|
+
name: id,
|
|
3453
|
+
reference: flags["reference"] ?? "HEAD",
|
|
3454
|
+
message: flags["message"] ?? "",
|
|
3455
|
+
},
|
|
3456
|
+
};
|
|
3457
|
+
case "project-git.delete-tag":
|
|
3458
|
+
return {
|
|
3459
|
+
method: "POST",
|
|
3460
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/deleteTag"),
|
|
3461
|
+
identifiers: { name: id, },
|
|
3462
|
+
payload: { name: id, },
|
|
3463
|
+
};
|
|
3464
|
+
case "project-git.fetch":
|
|
3465
|
+
return {
|
|
3466
|
+
method: "POST",
|
|
3467
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/fetch"),
|
|
3468
|
+
};
|
|
3469
|
+
case "project-git.pull":
|
|
3470
|
+
return {
|
|
3471
|
+
method: "POST",
|
|
3472
|
+
endpoint: projectGitEndpoint(projectKey, `/actions/pullRebase${querySuffix({
|
|
3473
|
+
branchName: flags["branch"],
|
|
3474
|
+
})}`),
|
|
3475
|
+
...(flags["branch"] !== undefined
|
|
3476
|
+
? { identifiers: { branch: flags["branch"], }, }
|
|
3477
|
+
: {}),
|
|
3478
|
+
};
|
|
3479
|
+
case "project-git.push":
|
|
3480
|
+
return {
|
|
3481
|
+
method: "POST",
|
|
3482
|
+
endpoint: projectGitEndpoint(projectKey, `/actions/push${querySuffix({
|
|
3483
|
+
branchName: flags["branch"],
|
|
3484
|
+
})}`),
|
|
3485
|
+
...(flags["branch"] !== undefined
|
|
3486
|
+
? { identifiers: { branch: flags["branch"], }, }
|
|
3487
|
+
: {}),
|
|
3488
|
+
};
|
|
3489
|
+
case "project-git.commit": {
|
|
3490
|
+
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
3491
|
+
return {
|
|
3492
|
+
method: "POST",
|
|
3493
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/commit"),
|
|
3494
|
+
identifiers: { message, },
|
|
3495
|
+
payload: { message, },
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
case "project-git.revert-to-revision":
|
|
3499
|
+
return {
|
|
3500
|
+
method: "POST",
|
|
3501
|
+
endpoint: projectGitEndpoint(projectKey, `/actions/revertToRevision${querySuffix({ commit: id, })}`),
|
|
3502
|
+
identifiers: { commit: id, },
|
|
3503
|
+
};
|
|
3504
|
+
case "project-git.revert-commit":
|
|
3505
|
+
return {
|
|
3506
|
+
method: "POST",
|
|
3507
|
+
endpoint: projectGitEndpoint(projectKey, `/actions/revertCommit${querySuffix({ commit: id, })}`),
|
|
3508
|
+
identifiers: { commit: id, },
|
|
3509
|
+
};
|
|
3510
|
+
case "project-git.reset-to-head":
|
|
3511
|
+
return {
|
|
3512
|
+
method: "POST",
|
|
3513
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/resetToLocalHeadState"),
|
|
3514
|
+
};
|
|
3515
|
+
case "project-git.reset-to-upstream":
|
|
3516
|
+
return {
|
|
3517
|
+
method: "POST",
|
|
3518
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/resetToRemoteHeadState"),
|
|
3519
|
+
};
|
|
3520
|
+
case "project-git.drop-and-rebuild": {
|
|
3521
|
+
if (parseBooleanOption(flags["i-know-what-i-am-doing"], "--i-know-what-i-am-doing") !== true) {
|
|
3522
|
+
throw new UsageError(`--i-know-what-i-am-doing is required to acknowledge the irreversible Git history loss. Usage: ${entry.usage}`);
|
|
3523
|
+
}
|
|
3524
|
+
return {
|
|
3525
|
+
method: "POST",
|
|
3526
|
+
endpoint: projectGitEndpoint(projectKey, `/actions/dropAndRebuild${querySuffix({ iKnowWhatIAmDoing: true, })}`),
|
|
3527
|
+
};
|
|
3528
|
+
}
|
|
3529
|
+
case "project-git.add-library": {
|
|
3530
|
+
const targetPath = id;
|
|
3531
|
+
return {
|
|
3532
|
+
method: "POST",
|
|
3533
|
+
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/"),
|
|
3534
|
+
identifiers: { localTargetPath: targetPath, },
|
|
3535
|
+
payload: {
|
|
3536
|
+
repository: requiredPlanRepositoryUrl(flags, "repository", entry.usage),
|
|
3537
|
+
login: flags["login"] ?? null,
|
|
3538
|
+
password: flags["password-env"] !== undefined ? "***" : null,
|
|
3539
|
+
pathInGitRepository: flags["path-in-repository"] ?? "",
|
|
3540
|
+
localTargetPath: targetPath,
|
|
3541
|
+
checkout: requiredPlanFlag(flags, "checkout", entry.usage),
|
|
3542
|
+
addToPythonPath: parseBooleanOption(flags["no-add-to-python-path"], "--no-add-to-python-path") !== true,
|
|
3543
|
+
},
|
|
3544
|
+
wait: projectGitFutureWait(),
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
case "project-git.set-library": {
|
|
3548
|
+
const targetPath = validateGitReferencePath(id);
|
|
3549
|
+
return {
|
|
3550
|
+
method: "PUT",
|
|
3551
|
+
endpoint: projectGitEndpoint(projectKey, `/lib-git-refs/${encodeGitReferencePath(targetPath)}`),
|
|
3552
|
+
identifiers: { library: targetPath, },
|
|
3553
|
+
payload: {
|
|
3554
|
+
repository: requiredPlanRepositoryUrl(flags, "repository", entry.usage),
|
|
3555
|
+
login: flags["login"] ?? null,
|
|
3556
|
+
password: flags["password-env"] !== undefined ? "***" : null,
|
|
3557
|
+
pathInGitRepository: flags["path-in-repository"] ?? "",
|
|
3558
|
+
checkout: requiredPlanFlag(flags, "checkout", entry.usage),
|
|
3559
|
+
},
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
case "project-git.remove-library": {
|
|
3563
|
+
const targetPath = validateGitReferencePath(id);
|
|
3564
|
+
return {
|
|
3565
|
+
method: "DELETE",
|
|
3566
|
+
endpoint: projectGitEndpoint(projectKey, `/lib-git-refs/${encodeGitReferencePath(targetPath)}${querySuffix({
|
|
3567
|
+
deleteDirectory: parseBooleanOption(flags["delete-directory"], "--delete-directory") ?? false,
|
|
3568
|
+
})}`),
|
|
3569
|
+
identifiers: { library: targetPath, },
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
case "project-git.reset-library": {
|
|
3573
|
+
const targetPath = validateGitReferencePath(id);
|
|
3574
|
+
return {
|
|
3575
|
+
method: "POST",
|
|
3576
|
+
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/action/reset"),
|
|
3577
|
+
identifiers: { library: targetPath, },
|
|
3578
|
+
payload: { gitRef: targetPath, },
|
|
3579
|
+
wait: projectGitFutureWait(),
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
case "project-git.push-library": {
|
|
3583
|
+
const targetPath = validateGitReferencePath(id);
|
|
3584
|
+
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
3585
|
+
return {
|
|
3586
|
+
method: "POST",
|
|
3587
|
+
endpoint: projectGitEndpoint(projectKey, "/lib-git-refs/action/push"),
|
|
3588
|
+
identifiers: { library: targetPath, message, },
|
|
3589
|
+
payload: { gitRef: targetPath, commitMessage: message, },
|
|
3590
|
+
wait: projectGitFutureWait(),
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
case "project-git.push-all-libraries": {
|
|
3594
|
+
const message = requiredPlanFlag(flags, "message", entry.usage);
|
|
3595
|
+
return {
|
|
3596
|
+
method: "POST",
|
|
3597
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/git-refs/push-all"),
|
|
3598
|
+
identifiers: { message, },
|
|
3599
|
+
payload: { commitMessage: message, },
|
|
3600
|
+
wait: projectGitFutureWait(),
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3603
|
+
case "project-git.reset-all-libraries":
|
|
3604
|
+
return {
|
|
3605
|
+
method: "POST",
|
|
3606
|
+
endpoint: projectGitEndpoint(projectKey, "/actions/git-refs/reset-all"),
|
|
3607
|
+
wait: projectGitFutureWait(),
|
|
3608
|
+
};
|
|
3609
|
+
case "project-git.future-abort":
|
|
3610
|
+
return {
|
|
3611
|
+
method: "DELETE",
|
|
3612
|
+
endpoint: projectGitFutureEndpoint(id),
|
|
3613
|
+
identifiers: { jobId: id, },
|
|
3614
|
+
};
|
|
3615
|
+
case "project-folder.settings-set": {
|
|
3616
|
+
const payload = requiredPlanJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${entry.usage}`);
|
|
3617
|
+
return {
|
|
3618
|
+
method: "PUT",
|
|
3619
|
+
endpoint: `${projectFolderEndpoint(id)}/settings`,
|
|
3620
|
+
identifiers: { folderId: id, },
|
|
3621
|
+
payload,
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
case "project-folder.move": {
|
|
3625
|
+
const destination = args[1];
|
|
3626
|
+
if (!destination)
|
|
3627
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
3628
|
+
return {
|
|
3629
|
+
method: "POST",
|
|
3630
|
+
endpoint: `${projectFolderEndpoint(id)}/move${querySuffix({ destination, })}`,
|
|
3631
|
+
identifiers: { folderId: id, destination, },
|
|
3632
|
+
};
|
|
3633
|
+
}
|
|
3634
|
+
case "project-folder.delete":
|
|
3635
|
+
return {
|
|
3636
|
+
method: "DELETE",
|
|
3637
|
+
endpoint: projectFolderEndpoint(id),
|
|
3638
|
+
identifiers: { folderId: id, },
|
|
3639
|
+
};
|
|
3640
|
+
case "project-folder.create-child": {
|
|
3641
|
+
const name = requiredPlanFlag(flags, "name", entry.usage);
|
|
3642
|
+
return {
|
|
3643
|
+
method: "POST",
|
|
3644
|
+
endpoint: `${projectFolderEndpoint(id)}/children${querySuffix({ name, })}`,
|
|
3645
|
+
identifiers: { parentFolderId: id, name, },
|
|
3646
|
+
};
|
|
3647
|
+
}
|
|
3648
|
+
case "project-folder.move-project": {
|
|
3649
|
+
const projectKeyArg = args[1];
|
|
3650
|
+
const destination = args[2];
|
|
3651
|
+
if (!projectKeyArg || !destination) {
|
|
3652
|
+
throw new UsageError(`Usage: ${entry.usage}`);
|
|
3653
|
+
}
|
|
3654
|
+
return {
|
|
3655
|
+
method: "POST",
|
|
3656
|
+
endpoint: `${projectFolderEndpoint(id)}/projects/${encodeURIComponent(projectKeyArg)}/move${querySuffix({ destination, })}`,
|
|
3657
|
+
identifiers: { folderId: id, projectKey: projectKeyArg, destination, },
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
default:
|
|
3661
|
+
return {
|
|
3662
|
+
exact: false,
|
|
3663
|
+
reason: `No exact offline request shape is defined for ${resource}.${action}; no endpoint was guessed.`,
|
|
3664
|
+
identifiers: id ? { id, } : undefined,
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
export function buildMutationPlan(resource, action, meta, args, flags) {
|
|
3669
|
+
const entry = buildRegistryEntry(resource, action, meta);
|
|
3670
|
+
if (!entry.mutatesDss && entry.sideEffect !== "write") {
|
|
3671
|
+
throw new UsageError(`--plan is only supported for mutating commands. Usage: ${meta.usage}`);
|
|
3672
|
+
}
|
|
3673
|
+
const requiredPositionals = entry.positionalArguments.filter((positional) => positional.required);
|
|
3674
|
+
requireArgs(args, requiredPositionals.length, meta.usage);
|
|
3675
|
+
const projectKey = projectKeyForPlan(entry, flags);
|
|
3676
|
+
const shape = commandPlanShape(resource, action, args, flags, entry, projectKey);
|
|
3677
|
+
return planResult(resource, action, {
|
|
3678
|
+
...shape,
|
|
3679
|
+
asyncKind: entry.async,
|
|
3680
|
+
exitCodesOnFailure: exitCodesOnFailure(entry),
|
|
3681
|
+
idempotency: entry.idempotency,
|
|
3682
|
+
plannedAndDryRun: executionMode(flags).dryRun,
|
|
3683
|
+
});
|
|
3684
|
+
}
|