dataiku-sdk 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +107 -17
- package/README.md +144 -17
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +39 -4
- package/dist/packages/types/src/index.js +31 -2
- package/dist/src/cli/commands/api-service.js +62 -16
- package/dist/src/cli/commands/bundle.js +49 -15
- package/dist/src/cli/commands/code-env.js +210 -49
- package/dist/src/cli/commands/code.d.ts +19 -0
- package/dist/src/cli/commands/code.js +87 -15
- package/dist/src/cli/commands/dataset.js +8 -3
- package/dist/src/cli/commands/flow-zone.js +33 -5
- package/dist/src/cli/commands/notebook.js +72 -41
- package/dist/src/cli/commands/project-git.js +8 -5
- package/dist/src/cli/commands/project-library.d.ts +21 -0
- package/dist/src/cli/commands/project-library.js +121 -50
- package/dist/src/cli/commands/project.js +16 -2
- package/dist/src/cli/commands/recipe.js +37 -12
- package/dist/src/cli/commands/sql.d.ts +10 -0
- package/dist/src/cli/commands/sql.js +39 -17
- package/dist/src/cli/commands/webapp.js +67 -13
- package/dist/src/cli/contract.d.ts +15 -0
- package/dist/src/cli/contract.js +870 -107
- package/dist/src/cli/env.d.ts +9 -0
- package/dist/src/cli/env.js +20 -2
- package/dist/src/cli/flags.js +9 -0
- package/dist/src/cli/helpers/cleanup.js +104 -3
- package/dist/src/cli/helpers/flow-zone.d.ts +3 -0
- package/dist/src/cli/helpers/flow-zone.js +33 -3
- package/dist/src/cli/helpers/recipe.d.ts +20 -0
- package/dist/src/cli/helpers/recipe.js +158 -3
- package/dist/src/cli/output.d.ts +2 -0
- package/dist/src/cli/output.js +33 -6
- package/dist/src/cli/runtime.d.ts +35 -1
- package/dist/src/cli/runtime.js +125 -18
- package/dist/src/cli/types.d.ts +7 -0
- package/dist/src/cli.js +135 -28
- package/dist/src/client.d.ts +60 -0
- package/dist/src/client.js +237 -42
- package/dist/src/errors.d.ts +8 -2
- package/dist/src/errors.js +40 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.js +2 -1
- package/dist/src/resources/api-services.d.ts +48 -6
- package/dist/src/resources/api-services.js +54 -8
- package/dist/src/resources/bundles.d.ts +51 -6
- package/dist/src/resources/bundles.js +39 -9
- package/dist/src/resources/code-envs.d.ts +25 -3
- package/dist/src/resources/code-envs.js +104 -2
- package/dist/src/resources/datasets.d.ts +20 -0
- package/dist/src/resources/datasets.js +232 -52
- package/dist/src/resources/jobs.d.ts +35 -10
- package/dist/src/resources/jobs.js +92 -17
- package/dist/src/resources/notebooks.d.ts +77 -7
- package/dist/src/resources/notebooks.js +143 -19
- package/dist/src/resources/project-git.d.ts +12 -0
- package/dist/src/resources/project-git.js +39 -8
- package/dist/src/resources/project-library.d.ts +122 -7
- package/dist/src/resources/project-library.js +494 -25
- package/dist/src/resources/projects.d.ts +5 -2
- package/dist/src/resources/projects.js +18 -2
- package/dist/src/resources/scenarios.d.ts +30 -0
- package/dist/src/resources/scenarios.js +73 -27
- package/dist/src/resources/sql.d.ts +6 -0
- package/dist/src/resources/sql.js +22 -2
- package/dist/src/resources/webapps.d.ts +35 -4
- package/dist/src/resources/webapps.js +54 -5
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.d.ts +9 -0
- package/dist/src/skill.js +37 -4
- package/dist/src/utils/flow-analysis.d.ts +53 -0
- package/dist/src/utils/flow-analysis.js +382 -0
- package/dist/src/utils/project-archive.d.ts +41 -2
- package/dist/src/utils/project-archive.js +159 -16
- package/package.json +5 -5
- package/packages/types/dist/index.d.ts +39 -4
- package/packages/types/dist/index.js +31 -2
- package/plugin.json +2 -2
- package/skills/dataiku-dss/SKILL.md +75 -48
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
import { readFileSync, } from "node:fs";
|
|
2
|
+
import { ScenarioScriptRunWithCleanupFailureError, } from "../../resources/scenarios.js";
|
|
2
3
|
import { num, readStdinText, stripUtf8Bom, } from "../coerce.js";
|
|
4
|
+
import { enqueueCliWarning, } from "../output.js";
|
|
3
5
|
import { UsageError, } from "../usage.js";
|
|
4
|
-
const CODE_RUN_USAGE = "dss code run (--file PATH | --stdin) [--env ENV] [--timeout MS] [--keep] [--full-log] [--max-log-bytes N] [--project-key KEY]";
|
|
5
|
-
function
|
|
6
|
+
const CODE_RUN_USAGE = "dss code run (--file PATH | --stdin) [--env ENV] [--timeout MS (default 120000)] [--keep] [--full-log] [--max-log-bytes N (default 1048576)] [--project-key KEY]";
|
|
7
|
+
function previewFlagValue(value) {
|
|
8
|
+
const text = typeof value === "string" ? value : String(value);
|
|
9
|
+
return text.length <= 32 ? text : `${text.slice(0, 32)}…`;
|
|
10
|
+
}
|
|
11
|
+
/** Read a local Python source file, mapping read failures to usage validation. */
|
|
12
|
+
function readCodeSourceFile(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(filePath, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
throw new UsageError(`Could not read --file ${filePath}: ${error instanceof Error ? error.message : String(error)}.`, "not_found", "Verify the file path and that it is readable.");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Canonical validation of `code run` input: rejects positional args and
|
|
22
|
+
* conflicting sources, reads the chosen source, strips a UTF-8 BOM, and
|
|
23
|
+
* rejects empty scripts. Returns the script plus source metadata so plan or
|
|
24
|
+
* replay payloads can hash the script and reference the source without
|
|
25
|
+
* embedding its contents.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveCodeInputWithSource(args, flags) {
|
|
6
28
|
if (args.length > 0) {
|
|
7
29
|
throw new UsageError(`code run takes no positional arguments; pass the script via --file PATH or --stdin. Usage: ${CODE_RUN_USAGE}`);
|
|
8
30
|
}
|
|
9
31
|
const sources = [];
|
|
10
32
|
if (typeof flags["file"] === "string") {
|
|
11
|
-
sources.push({
|
|
33
|
+
sources.push({
|
|
34
|
+
kind: "file",
|
|
35
|
+
label: "--file",
|
|
36
|
+
path: flags["file"],
|
|
37
|
+
read: () => readCodeSourceFile(flags["file"]),
|
|
38
|
+
});
|
|
12
39
|
}
|
|
13
40
|
if (flags["stdin"] === true) {
|
|
14
|
-
sources.push({ label: "--stdin", read: readStdinText, });
|
|
41
|
+
sources.push({ kind: "stdin", label: "--stdin", read: readStdinText, });
|
|
15
42
|
}
|
|
16
43
|
if (sources.length === 0) {
|
|
17
44
|
throw new UsageError(`Python source is required: pass --file PATH or --stdin. Usage: ${CODE_RUN_USAGE}`);
|
|
@@ -19,23 +46,63 @@ function resolveCodeInput(args, flags) {
|
|
|
19
46
|
if (sources.length > 1) {
|
|
20
47
|
throw new UsageError(`Choose exactly one Python source: --file or --stdin. Usage: ${CODE_RUN_USAGE}`);
|
|
21
48
|
}
|
|
22
|
-
const
|
|
49
|
+
const source = sources[0];
|
|
50
|
+
const script = stripUtf8Bom(source.read());
|
|
23
51
|
if (script.trim().length === 0) {
|
|
24
|
-
throw new UsageError(`Python source from ${
|
|
52
|
+
throw new UsageError(`Python source from ${source.label} must not be empty. Usage: ${CODE_RUN_USAGE}`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
script,
|
|
56
|
+
source: source.kind === "file" ? { kind: "file", path: source.path, } : { kind: "stdin", },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function resolveCodeInput(args, flags) {
|
|
60
|
+
return resolveCodeInputWithSource(args, flags).script;
|
|
61
|
+
}
|
|
62
|
+
/** Parse an integer flag value, rejecting negative and fractional values before any DSS request. */
|
|
63
|
+
export function parseCodeRunIntegerFlag(value, flagName) {
|
|
64
|
+
const parsed = num(value, flagName);
|
|
65
|
+
if (parsed === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
68
|
+
const valuePreview = previewFlagValue(value);
|
|
69
|
+
throw new UsageError(`Invalid value for ${flagName}: expected a non-negative integer, got ${valuePreview}.`, "invalid_flag_value", undefined, { flag: flagName, value: valuePreview, });
|
|
25
70
|
}
|
|
26
|
-
return
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
function reportCleanupFailure(cleanupFailure) {
|
|
74
|
+
enqueueCliWarning({
|
|
75
|
+
type: "code_run_cleanup_failed",
|
|
76
|
+
scenarioId: cleanupFailure.scenarioId,
|
|
77
|
+
error: cleanupFailure.error,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function unwrapCodeRunError(error) {
|
|
81
|
+
if (error instanceof ScenarioScriptRunWithCleanupFailureError) {
|
|
82
|
+
reportCleanupFailure(error.cleanupFailure);
|
|
83
|
+
return error.cause;
|
|
84
|
+
}
|
|
85
|
+
return error;
|
|
27
86
|
}
|
|
28
87
|
export const codeCommands = {
|
|
29
88
|
run: {
|
|
30
89
|
handler: async (c, a, f) => {
|
|
90
|
+
const timeoutMs = parseCodeRunIntegerFlag(f["timeout"], "--timeout");
|
|
91
|
+
const maxLogBytes = parseCodeRunIntegerFlag(f["max-log-bytes"], "--max-log-bytes");
|
|
31
92
|
const script = resolveCodeInput(a, f);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
93
|
+
let run;
|
|
94
|
+
try {
|
|
95
|
+
run = await c.scenarios.runScript(script, {
|
|
96
|
+
envName: f["env"],
|
|
97
|
+
projectKey: f["project-key"],
|
|
98
|
+
timeoutMs,
|
|
99
|
+
keepScenario: f["keep"] === true,
|
|
100
|
+
maxLogBytes,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw unwrapCodeRunError(error);
|
|
105
|
+
}
|
|
39
106
|
const result = {
|
|
40
107
|
outcome: run.outcome,
|
|
41
108
|
success: run.success,
|
|
@@ -45,14 +112,19 @@ export const codeCommands = {
|
|
|
45
112
|
output: run.output ?? "",
|
|
46
113
|
logTruncated: run.logTruncated,
|
|
47
114
|
maxLogBytes: run.maxLogBytes,
|
|
115
|
+
cleanup: run.cleanup,
|
|
116
|
+
...(run.timedOut === true ? { timedOut: true, timeoutMs: run.timeoutMs, } : {}),
|
|
48
117
|
};
|
|
49
118
|
if (f["full-log"] === true || run.output === undefined) {
|
|
50
119
|
result.log = run.log;
|
|
51
120
|
}
|
|
121
|
+
if (run.cleanup.status === "failed") {
|
|
122
|
+
reportCleanupFailure({ scenarioId: run.scenarioId, error: run.cleanup.error ?? "unknown", });
|
|
123
|
+
}
|
|
52
124
|
return result;
|
|
53
125
|
},
|
|
54
126
|
usage: CODE_RUN_USAGE,
|
|
55
|
-
description: "Run
|
|
127
|
+
description: "Run Python through a throwaway custom-python scenario and return captured output plus outcome. --timeout caps waiting; --max-log-bytes caps logs; --full-log returns the capped raw log. Exits 4 unless outcome is SUCCESS.",
|
|
56
128
|
examples: [
|
|
57
129
|
"dss code run --file inspect.py",
|
|
58
130
|
"dss code run --file inspect.py --env py39_pandas",
|
|
@@ -314,6 +314,7 @@ export const datasetCommands = {
|
|
|
314
314
|
outputPath: f["output"],
|
|
315
315
|
projectKey: f["project-key"],
|
|
316
316
|
limit: num(f["limit"], "--limit"),
|
|
317
|
+
rawData: f["raw-data"] === true,
|
|
317
318
|
});
|
|
318
319
|
if (result.truncated) {
|
|
319
320
|
enqueueCliWarning({
|
|
@@ -337,9 +338,13 @@ export const datasetCommands = {
|
|
|
337
338
|
}
|
|
338
339
|
return result;
|
|
339
340
|
},
|
|
340
|
-
usage: "dss dataset download <name> [--output PATH] [--limit N] [--project-key KEY]",
|
|
341
|
-
description: "Download up to --limit rows (default 100k) as CSV
|
|
342
|
-
examples: [
|
|
341
|
+
usage: "dss dataset download <name> [--output PATH] [--limit N] [--raw-data] [--project-key KEY]",
|
|
342
|
+
description: "Download up to --limit rows (default 100k) as CSV and return { path, rows, truncated, limit }. Formula-like cells are neutralized for spreadsheets; --raw-data preserves exact bytes. Warnings report truncation and the default output path.",
|
|
343
|
+
examples: [
|
|
344
|
+
"dss dataset download orders",
|
|
345
|
+
"dss dataset download orders --output ./data/",
|
|
346
|
+
"dss dataset download orders --raw-data",
|
|
347
|
+
],
|
|
343
348
|
},
|
|
344
349
|
create: {
|
|
345
350
|
handler: async (c, _a, f) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../../errors.js";
|
|
1
2
|
import { deepMerge, } from "../../utils/deep-merge.js";
|
|
2
|
-
import { ensureFlowZonePlanTarget, findFlowZoneForPlan, flowZoneColor, flowZoneContains, flowZoneCurrentPosition, flowZoneDetailSummary, flowZoneId, flowZoneMoveItems, flowZoneName, flowZoneOrganizeStep, flowZonePlanItemKeys, flowZonePruneItems, flowZoneSamePosition, flowZoneSummary, readFlowZoneOrganizePlan, resolveFlowZoneIdFromFlags, throwFlowZoneValidationError, validateFlowZoneOrganizeObjects, } from "../helpers/flow-zone.js";
|
|
3
|
+
import { ensureFlowZonePlanTarget, findFlowZoneForPlan, flowZoneColor, flowZoneContains, flowZoneCurrentPosition, flowZoneDetailSummary, flowZoneId, flowZoneMoveDelta, flowZoneMoveItems, flowZoneName, flowZoneOrganizeStep, flowZonePlanFromZones, flowZonePlanItemKeys, flowZonePruneItems, flowZoneSamePosition, flowZoneSummary, readFlowZoneOrganizePlan, resolveFlowZoneIdFromFlags, throwFlowZoneValidationError, validateFlowZoneOrganizeObjects, } from "../helpers/flow-zone.js";
|
|
3
4
|
import { readIfExists, skipResult, } from "../output.js";
|
|
4
5
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
5
6
|
export const flowZoneCommands = {
|
|
@@ -166,6 +167,19 @@ export const flowZoneCommands = {
|
|
|
166
167
|
"dss flow-zone move ZONE_ID --object SAVED_MODEL:model_id",
|
|
167
168
|
],
|
|
168
169
|
},
|
|
170
|
+
plan: {
|
|
171
|
+
handler: async (c, _a, f) => {
|
|
172
|
+
const pk = f["project-key"];
|
|
173
|
+
const [zones, topologyFingerprint,] = await Promise.all([
|
|
174
|
+
c.flowZones.list(pk),
|
|
175
|
+
c.projects.topologyFingerprint(pk),
|
|
176
|
+
]);
|
|
177
|
+
return flowZonePlanFromZones(zones, topologyFingerprint);
|
|
178
|
+
},
|
|
179
|
+
usage: "dss flow-zone plan [--project-key KEY]",
|
|
180
|
+
description: "Export current visual flow-zone organization as an organize-compatible plan.",
|
|
181
|
+
examples: ["dss flow-zone plan --project-key MYPROJ",],
|
|
182
|
+
},
|
|
169
183
|
organize: {
|
|
170
184
|
handler: async (c, _a, f) => {
|
|
171
185
|
const usage = "dss flow-zone organize (--data JSON|--data-file PATH|--file PATH|--stdin) [--sync] [--validate-objects] [--dry-run] [--project-key KEY]";
|
|
@@ -173,7 +187,13 @@ export const flowZoneCommands = {
|
|
|
173
187
|
const plan = readFlowZoneOrganizePlan(f, usage);
|
|
174
188
|
const sync = f["sync"] === true;
|
|
175
189
|
const validateObjects = f["validate-objects"] === true;
|
|
176
|
-
const zones = await
|
|
190
|
+
const [zones, topologyFingerprint,] = await Promise.all([
|
|
191
|
+
c.flowZones.list(pk),
|
|
192
|
+
c.projects.topologyFingerprint(pk),
|
|
193
|
+
]);
|
|
194
|
+
if (plan.topologyFingerprint && plan.topologyFingerprint !== topologyFingerprint) {
|
|
195
|
+
throw new ClientValidationError("Flow topology changed after this visual organization plan was generated.", "assertion_failed", "Regenerate the plan with dss flow-zone plan, then review it before applying.", { expected: plan.topologyFingerprint, actual: topologyFingerprint, });
|
|
196
|
+
}
|
|
177
197
|
const plannedItemKeys = flowZonePlanItemKeys(plan);
|
|
178
198
|
const planned = plan.zones.map((zonePlan) => flowZoneOrganizeStep(zonePlan, findFlowZoneForPlan(zones, zonePlan), sync, plannedItemKeys));
|
|
179
199
|
const validation = validateObjects
|
|
@@ -194,6 +214,7 @@ export const flowZoneCommands = {
|
|
|
194
214
|
projectKey: pk,
|
|
195
215
|
sync,
|
|
196
216
|
validateObjects,
|
|
217
|
+
topologyFingerprint,
|
|
197
218
|
zoneCount: plan.zones.length,
|
|
198
219
|
itemCount,
|
|
199
220
|
pruneItemCount,
|
|
@@ -209,6 +230,7 @@ export const flowZoneCommands = {
|
|
|
209
230
|
for (const zonePlan of plan.zones) {
|
|
210
231
|
let zone = findFlowZoneForPlan(currentZones, zonePlan);
|
|
211
232
|
ensureFlowZonePlanTarget(zonePlan, zone);
|
|
233
|
+
const moveItems = flowZoneMoveDelta(zone, zonePlan.items);
|
|
212
234
|
const pruneItems = sync ? flowZonePruneItems(zone, plannedItemKeys) : [];
|
|
213
235
|
if (!zone) {
|
|
214
236
|
zone = await c.flowZones.create({
|
|
@@ -238,15 +260,19 @@ export const flowZoneCommands = {
|
|
|
238
260
|
updated.push(zone);
|
|
239
261
|
}
|
|
240
262
|
}
|
|
241
|
-
if (
|
|
242
|
-
await c.flowZones.moveItems(zone.id,
|
|
243
|
-
moved.push({ zoneId: zone.id, name: zone.name, items:
|
|
263
|
+
if (moveItems.length > 0) {
|
|
264
|
+
await c.flowZones.moveItems(zone.id, moveItems, pk);
|
|
265
|
+
moved.push({ zoneId: zone.id, name: zone.name, items: moveItems, });
|
|
244
266
|
}
|
|
245
267
|
if (pruneItems.length > 0) {
|
|
246
268
|
await c.flowZones.moveItems("default", pruneItems, pk);
|
|
247
269
|
pruned.push({ zoneId: "default", fromZoneId: zone.id, name: zone.name, items: pruneItems, });
|
|
248
270
|
}
|
|
249
271
|
}
|
|
272
|
+
const topologyFingerprintAfter = await c.projects.topologyFingerprint(pk);
|
|
273
|
+
if (topologyFingerprintAfter !== topologyFingerprint) {
|
|
274
|
+
throw new ClientValidationError("Visual flow-zone changes modified flow topology.", "assertion_failed", "Inspect the flow before applying any further organization changes.", { before: topologyFingerprint, after: topologyFingerprintAfter, });
|
|
275
|
+
}
|
|
250
276
|
return {
|
|
251
277
|
organized: true,
|
|
252
278
|
action: "organize",
|
|
@@ -254,6 +280,8 @@ export const flowZoneCommands = {
|
|
|
254
280
|
projectKey: pk,
|
|
255
281
|
sync,
|
|
256
282
|
validateObjects,
|
|
283
|
+
topologyFingerprint,
|
|
284
|
+
topologyUnchanged: true,
|
|
257
285
|
zoneCount: plan.zones.length,
|
|
258
286
|
itemCount,
|
|
259
287
|
pruneItemCount,
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { jsonInput, num, } from "../coerce.js";
|
|
2
|
-
import { encodedProjectEndpoint,
|
|
3
|
-
import { requireArgs, UsageError, } from "../usage.js";
|
|
1
|
+
import { jsonInput, num, parseBooleanOption, } from "../coerce.js";
|
|
2
|
+
import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
|
|
3
|
+
import { requireArgs, requireNoArgs, UsageError, } from "../usage.js";
|
|
4
4
|
export const notebookCommands = {
|
|
5
5
|
"list-jupyter": {
|
|
6
|
-
handler: (c, _a, f) => c.notebooks.listJupyter(f["project-key"]
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
handler: (c, _a, f) => c.notebooks.listJupyter(f["project-key"], {
|
|
7
|
+
active: parseBooleanOption(f["active"], "--active"),
|
|
8
|
+
}),
|
|
9
|
+
usage: "dss notebook list-jupyter [--active true|false] [--project-key KEY]",
|
|
10
|
+
description: "List Jupyter notebooks. Pass --active to use the official ?active= filter: true lists currently running notebooks, false lists non-running ones.",
|
|
11
|
+
examples: [
|
|
12
|
+
"dss notebook list-jupyter",
|
|
13
|
+
"dss notebook list-jupyter --active true",
|
|
14
|
+
],
|
|
10
15
|
},
|
|
11
16
|
"get-jupyter": {
|
|
12
17
|
handler: (c, a, f) => {
|
|
@@ -41,22 +46,19 @@ export const notebookCommands = {
|
|
|
41
46
|
requireArgs(a, 1, "dss notebook clear-jupyter-outputs <name>");
|
|
42
47
|
const pk = f["project-key"];
|
|
43
48
|
if (f["dry-run"] === true) {
|
|
49
|
+
// The real mutation is a single server-side DELETE of the
|
|
50
|
+
// outputs endpoint, so the dry run can only report the current
|
|
51
|
+
// content (read-only) and the exact request that would run; the
|
|
52
|
+
// stripped `next` shape would misdescribe the operation.
|
|
44
53
|
const current = await c.notebooks.getJupyter(a[0], pk);
|
|
45
|
-
const next = {
|
|
46
|
-
...current,
|
|
47
|
-
cells: current.cells.map((cell) => {
|
|
48
|
-
if (cell.cell_type !== "code")
|
|
49
|
-
return cell;
|
|
50
|
-
return { ...cell, outputs: [], execution_count: 0, };
|
|
51
|
-
}),
|
|
52
|
-
};
|
|
53
54
|
return {
|
|
54
55
|
dryRun: true,
|
|
55
56
|
action: "clear-jupyter-outputs",
|
|
56
57
|
resource: "jupyter-notebook",
|
|
57
58
|
name: a[0],
|
|
58
59
|
current,
|
|
59
|
-
|
|
60
|
+
endpoint: encodedProjectEndpoint(c, pk, `/jupyter-notebooks/${encodeURIComponent(a[0])}/outputs`),
|
|
61
|
+
method: "DELETE",
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
await c.notebooks.clearJupyterOutputs(a[0], pk);
|
|
@@ -77,8 +79,33 @@ export const notebookCommands = {
|
|
|
77
79
|
},
|
|
78
80
|
"unload-jupyter": {
|
|
79
81
|
handler: async (c, a, f) => {
|
|
80
|
-
requireArgs(a, 2, "dss notebook unload-jupyter <name> <sessionId>");
|
|
81
82
|
const pk = f["project-key"];
|
|
83
|
+
if (f["all"] === true) {
|
|
84
|
+
requireNoArgs(a, "dss notebook unload-jupyter --all [--dry-run] [--project-key KEY]");
|
|
85
|
+
if (f["dry-run"] === true) {
|
|
86
|
+
const planned = [];
|
|
87
|
+
for (const notebook of await c.notebooks.listJupyter(pk, { active: true, })) {
|
|
88
|
+
for (const session of await c.notebooks.listJupyterSessions(notebook.name, pk)) {
|
|
89
|
+
planned.push({
|
|
90
|
+
name: notebook.name,
|
|
91
|
+
sessionId: session.sessionId,
|
|
92
|
+
endpoint: encodedProjectEndpoint(c, pk, `/jupyter-notebooks/${encodeURIComponent(notebook.name)}/sessions/${encodeURIComponent(session.sessionId)}`),
|
|
93
|
+
method: "DELETE",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
dryRun: true,
|
|
99
|
+
action: "unload-jupyter",
|
|
100
|
+
resource: "jupyter-notebook",
|
|
101
|
+
all: true,
|
|
102
|
+
planned,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const unloaded = await c.notebooks.unloadJupyterAll(pk);
|
|
106
|
+
return { unloaded, resource: "jupyter-notebook", all: true, };
|
|
107
|
+
}
|
|
108
|
+
requireArgs(a, 2, "dss notebook unload-jupyter <name> <sessionId>");
|
|
82
109
|
if (f["dry-run"] === true) {
|
|
83
110
|
const sessions = await c.notebooks.listJupyterSessions(a[0], pk);
|
|
84
111
|
const current = sessions.find((session) => session.sessionId === a[1]);
|
|
@@ -96,9 +123,12 @@ export const notebookCommands = {
|
|
|
96
123
|
await c.notebooks.unloadJupyter(a[0], a[1], pk);
|
|
97
124
|
return { unloaded: a[0], sessionId: a[1], resource: "jupyter-notebook", };
|
|
98
125
|
},
|
|
99
|
-
usage: "dss notebook unload-jupyter <name> <sessionId
|
|
100
|
-
description: "Unload a Jupyter notebook kernel session.",
|
|
101
|
-
examples: [
|
|
126
|
+
usage: "dss notebook unload-jupyter (<name> <sessionId>|--all) [--dry-run] [--project-key KEY]",
|
|
127
|
+
description: "Unload a Jupyter notebook kernel session, or every session of every running notebook with --all.",
|
|
128
|
+
examples: [
|
|
129
|
+
"dss notebook unload-jupyter my_notebook SESSION_ID --dry-run",
|
|
130
|
+
"dss notebook unload-jupyter --all --dry-run",
|
|
131
|
+
],
|
|
102
132
|
},
|
|
103
133
|
"list-sql": {
|
|
104
134
|
handler: (c, _a, f) => c.notebooks.listSql(f["project-key"]),
|
|
@@ -162,18 +192,18 @@ export const notebookCommands = {
|
|
|
162
192
|
next: data,
|
|
163
193
|
};
|
|
164
194
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
const result = await c.notebooks.saveOrCreateJupyter(a[0], data, pk, {
|
|
196
|
+
expectHash: f["expect-hash"],
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
saved: a[0],
|
|
200
|
+
resource: "jupyter-notebook",
|
|
201
|
+
created: result.created,
|
|
202
|
+
hash: result.hash,
|
|
203
|
+
};
|
|
174
204
|
},
|
|
175
|
-
usage: "dss notebook save-jupyter <name> (--data '{...}' | --data-file PATH | --stdin) [--dry-run] [--project-key KEY]",
|
|
176
|
-
description: "Save content to a Jupyter notebook, creating it if missing.",
|
|
205
|
+
usage: "dss notebook save-jupyter <name> (--data '{...}' | --data-file PATH | --stdin) [--expect-hash SHA256] [--dry-run] [--project-key KEY]",
|
|
206
|
+
description: "Save content to a Jupyter notebook, creating it if missing. Reports created versus updated and the persisted content hash; --expect-hash aborts with a stale-read error when the stored content hash changed.",
|
|
177
207
|
examples: [
|
|
178
208
|
"dss notebook save-jupyter my_notebook --data-file notebook.json --dry-run",
|
|
179
209
|
"cat notebook.json | dss notebook save-jupyter my_notebook --stdin",
|
|
@@ -198,17 +228,18 @@ export const notebookCommands = {
|
|
|
198
228
|
next: data,
|
|
199
229
|
};
|
|
200
230
|
}
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
const result = await c.notebooks.saveOrCreateSql(a[0], data, pk, {
|
|
232
|
+
expectHash: f["expect-hash"],
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
saved: a[0],
|
|
236
|
+
resource: "sql-notebook",
|
|
237
|
+
created: result.created,
|
|
238
|
+
hash: result.hash,
|
|
239
|
+
};
|
|
209
240
|
},
|
|
210
|
-
usage: "dss notebook save-sql <id> (--data '{...}' | --data-file PATH | --stdin) [--dry-run] [--project-key KEY]",
|
|
211
|
-
description: "Save content to a SQL notebook, creating it if missing.",
|
|
241
|
+
usage: "dss notebook save-sql <id> (--data '{...}' | --data-file PATH | --stdin) [--expect-hash SHA256] [--dry-run] [--project-key KEY]",
|
|
242
|
+
description: "Save content to a SQL notebook, creating it if missing. Reports created versus updated and the persisted content hash; --expect-hash rejects a stale read before writing.",
|
|
212
243
|
examples: ["dss notebook save-sql my_sql_notebook --data-file content.json --dry-run",],
|
|
213
244
|
},
|
|
214
245
|
"clear-sql-history": {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateGitReferencePath, } from "../../resources/project-git.js";
|
|
1
2
|
import { num, parseBooleanOption, requiredStringFlag, } from "../coerce.js";
|
|
2
3
|
import { CommandResultFailure, } from "../output.js";
|
|
3
4
|
import { requireArgs, requireNoArgs, UsageError, } from "../usage.js";
|
|
@@ -448,8 +449,9 @@ export const projectGitCommands = {
|
|
|
448
449
|
"set-library": {
|
|
449
450
|
handler: async (c, a, f) => {
|
|
450
451
|
requireArgs(a, 1, SET_LIBRARY_USAGE);
|
|
452
|
+
const targetPath = validateGitReferencePath(a[0]);
|
|
451
453
|
const password = libraryPassword(f);
|
|
452
|
-
return sanitizeProjectGitOutput(await c.projectGit.setLibrary(requiredStringFlag(f, "project-key", SET_LIBRARY_USAGE),
|
|
454
|
+
return sanitizeProjectGitOutput(await c.projectGit.setLibrary(requiredStringFlag(f, "project-key", SET_LIBRARY_USAGE), targetPath, {
|
|
453
455
|
repository: requiredStringFlag(f, "repository", SET_LIBRARY_USAGE),
|
|
454
456
|
pathInGitRepository: optionalStringFlag(f, "path-in-repository"),
|
|
455
457
|
checkout: requiredStringFlag(f, "checkout", SET_LIBRARY_USAGE),
|
|
@@ -466,10 +468,11 @@ export const projectGitCommands = {
|
|
|
466
468
|
"remove-library": {
|
|
467
469
|
handler: async (c, a, f) => {
|
|
468
470
|
requireArgs(a, 1, REMOVE_LIBRARY_USAGE);
|
|
471
|
+
const targetPath = validateGitReferencePath(a[0]);
|
|
469
472
|
const deleteDirectory = parseBooleanOption(f["delete-directory"], "--delete-directory")
|
|
470
473
|
?? false;
|
|
471
|
-
await c.projectGit.removeLibrary(requiredStringFlag(f, "project-key", REMOVE_LIBRARY_USAGE),
|
|
472
|
-
return { removed:
|
|
474
|
+
await c.projectGit.removeLibrary(requiredStringFlag(f, "project-key", REMOVE_LIBRARY_USAGE), targetPath, deleteDirectory);
|
|
475
|
+
return { removed: targetPath, resource: "project-git-library", deleteDirectory, };
|
|
473
476
|
},
|
|
474
477
|
usage: REMOVE_LIBRARY_USAGE,
|
|
475
478
|
description: "Detach an external Git library. --delete-directory also removes its local directory from the project library.",
|
|
@@ -478,7 +481,7 @@ export const projectGitCommands = {
|
|
|
478
481
|
"reset-library": {
|
|
479
482
|
handler: (c, a, f) => {
|
|
480
483
|
requireArgs(a, 1, RESET_LIBRARY_USAGE);
|
|
481
|
-
return c.projectGit.resetLibrary(requiredStringFlag(f, "project-key", RESET_LIBRARY_USAGE), a[0]);
|
|
484
|
+
return c.projectGit.resetLibrary(requiredStringFlag(f, "project-key", RESET_LIBRARY_USAGE), validateGitReferencePath(a[0]));
|
|
482
485
|
},
|
|
483
486
|
usage: RESET_LIBRARY_USAGE,
|
|
484
487
|
description: "Reset one attached library to its remote state, discarding local edits. Returns the DSS future that performs the reset.",
|
|
@@ -487,7 +490,7 @@ export const projectGitCommands = {
|
|
|
487
490
|
"push-library": {
|
|
488
491
|
handler: (c, a, f) => {
|
|
489
492
|
requireArgs(a, 1, PUSH_LIBRARY_USAGE);
|
|
490
|
-
return c.projectGit.pushLibrary(requiredStringFlag(f, "project-key", PUSH_LIBRARY_USAGE), a[0], requiredStringFlag(f, "message", PUSH_LIBRARY_USAGE));
|
|
493
|
+
return c.projectGit.pushLibrary(requiredStringFlag(f, "project-key", PUSH_LIBRARY_USAGE), validateGitReferencePath(a[0]), requiredStringFlag(f, "message", PUSH_LIBRARY_USAGE));
|
|
491
494
|
},
|
|
492
495
|
usage: PUSH_LIBRARY_USAGE,
|
|
493
496
|
description: "Commit and push one attached library. Returns the DSS future that performs the push.",
|
|
@@ -1,2 +1,23 @@
|
|
|
1
|
+
import { type ProjectLibraryDiffResult } from "../../resources/project-library.js";
|
|
1
2
|
import type { CommandMeta } from "../types.js";
|
|
3
|
+
export interface ProjectLibraryPutPayload {
|
|
4
|
+
/** Where the written bytes come from: --content flag, --file, or --stdin. */
|
|
5
|
+
contentSource: "flag" | "file" | "stdin";
|
|
6
|
+
/** Local file path when contentSource is "file". */
|
|
7
|
+
file?: string;
|
|
8
|
+
/** Byte count of the content when it is available without consuming stdin. */
|
|
9
|
+
bytes?: number;
|
|
10
|
+
/** SHA-256 hex digest of the exact bytes to write, when safely available. */
|
|
11
|
+
sha256?: string;
|
|
12
|
+
/** SHA-256 hex digest the caller expects the remote file to carry before the write. */
|
|
13
|
+
expectSha256?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Put-input metadata for plans and reports. Plans must never include content
|
|
17
|
+
* bytes: this helper reports only the source, byte count, and SHA-256 digest —
|
|
18
|
+
* and only when they are available without consuming stdin (dry-run keeps
|
|
19
|
+
* stdin unread, so the stdin source carries no size/hash).
|
|
20
|
+
*/
|
|
21
|
+
export declare function projectLibraryPutPayload(flags: Record<string, string | boolean>): ProjectLibraryPutPayload;
|
|
2
22
|
export declare const projectLibraryCommands: Record<string, CommandMeta>;
|
|
23
|
+
export type { ProjectLibraryDiffResult, };
|