dataiku-sdk 3.0.0 → 3.2.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 +246 -19
- 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/auth.js +1 -1
- package/dist/src/cli/coerce.js +11 -2
- package/dist/src/cli/commands/analysis.js +4 -3
- package/dist/src/cli/commands/api-service.js +66 -18
- package/dist/src/cli/commands/app.js +4 -3
- package/dist/src/cli/commands/bundle.js +51 -16
- package/dist/src/cli/commands/code-env.js +219 -57
- package/dist/src/cli/commands/code.d.ts +19 -0
- package/dist/src/cli/commands/code.js +87 -15
- package/dist/src/cli/commands/dashboard.js +7 -6
- package/dist/src/cli/commands/data-quality.js +9 -8
- package/dist/src/cli/commands/dataset.js +17 -11
- package/dist/src/cli/commands/doctor.js +4 -2
- package/dist/src/cli/commands/flow-zone.js +42 -13
- package/dist/src/cli/commands/folder.js +9 -8
- package/dist/src/cli/commands/future.js +2 -1
- package/dist/src/cli/commands/insight.js +8 -7
- package/dist/src/cli/commands/job.js +4 -3
- package/dist/src/cli/commands/meaning.js +5 -4
- package/dist/src/cli/commands/ml-task.js +3 -2
- package/dist/src/cli/commands/model-evaluation-store.js +4 -3
- package/dist/src/cli/commands/notebook.js +82 -50
- 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 +128 -56
- package/dist/src/cli/commands/project.js +19 -4
- package/dist/src/cli/commands/recipe.js +51 -25
- package/dist/src/cli/commands/saved-model.js +4 -3
- package/dist/src/cli/commands/scenario.js +9 -8
- package/dist/src/cli/commands/sql.d.ts +10 -0
- package/dist/src/cli/commands/sql.js +42 -19
- package/dist/src/cli/commands/statistics.js +7 -6
- package/dist/src/cli/commands/variable.js +2 -1
- package/dist/src/cli/commands/webapp.js +68 -13
- package/dist/src/cli/commands/wiki.js +7 -6
- package/dist/src/cli/contract.d.ts +16 -1
- package/dist/src/cli/contract.js +967 -186
- package/dist/src/cli/env.d.ts +9 -0
- package/dist/src/cli/env.js +20 -2
- package/dist/src/cli/flags.d.ts +4 -0
- package/dist/src/cli/flags.js +29 -1
- 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 +226 -95
- package/dist/src/client.d.ts +63 -1
- package/dist/src/client.js +305 -53
- package/dist/src/errors.d.ts +8 -2
- package/dist/src/errors.js +42 -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/folders.js +2 -1
- 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 -4
- package/dist/src/resources/projects.js +21 -19
- 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 +23 -3
- 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 +18 -0
- package/dist/src/skill.js +93 -9
- 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/dist/src/utils/response-file.d.ts +2 -0
- package/dist/src/utils/response-file.js +34 -0
- package/package.json +9 -7
- package/packages/types/dist/index.d.ts +39 -4
- package/packages/types/dist/index.js +31 -2
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +44 -197
- package/skills/dataiku-dss/references/app-releases.md +27 -0
- package/skills/dataiku-dss/references/authentication.md +39 -0
- package/skills/dataiku-dss/references/coding.md +8 -0
- package/skills/dataiku-dss/references/discovery.md +30 -0
- package/skills/dataiku-dss/references/flow-maps.md +12 -0
- package/skills/dataiku-dss/references/mutations.md +33 -0
- package/skills/dataiku-dss/references/troubleshooting.md +29 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { jsonInput, num, } from "../coerce.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { jsonInput, num, parseBooleanOption, } from "../coerce.js";
|
|
2
|
+
import { executionMode, } from "../flags.js";
|
|
3
|
+
import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
|
|
4
|
+
import { requireArgs, requireNoArgs, UsageError, } from "../usage.js";
|
|
4
5
|
export const notebookCommands = {
|
|
5
6
|
"list-jupyter": {
|
|
6
|
-
handler: (c, _a, f) => c.notebooks.listJupyter(f["project-key"]
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
handler: (c, _a, f) => c.notebooks.listJupyter(f["project-key"], {
|
|
8
|
+
active: parseBooleanOption(f["active"], "--active"),
|
|
9
|
+
}),
|
|
10
|
+
usage: "dss notebook list-jupyter [--active true|false] [--project-key KEY]",
|
|
11
|
+
description: "List Jupyter notebooks. Pass --active to use the official ?active= filter: true lists currently running notebooks, false lists non-running ones.",
|
|
12
|
+
examples: [
|
|
13
|
+
"dss notebook list-jupyter",
|
|
14
|
+
"dss notebook list-jupyter --active true",
|
|
15
|
+
],
|
|
10
16
|
},
|
|
11
17
|
"get-jupyter": {
|
|
12
18
|
handler: (c, a, f) => {
|
|
@@ -21,11 +27,11 @@ export const notebookCommands = {
|
|
|
21
27
|
handler: async (c, a, f) => {
|
|
22
28
|
requireArgs(a, 1, "dss notebook delete-jupyter <name>");
|
|
23
29
|
const pk = f["project-key"];
|
|
24
|
-
if (f
|
|
30
|
+
if (executionMode(f).dryRun || f["if-exists"] === true) {
|
|
25
31
|
const current = await readIfExists(() => c.notebooks.getJupyter(a[0], pk));
|
|
26
32
|
if (!current)
|
|
27
33
|
return skipResult("jupyter-notebook", a[0], "missing");
|
|
28
|
-
if (f
|
|
34
|
+
if (executionMode(f).dryRun) {
|
|
29
35
|
return { dryRun: true, action: "delete", resource: "jupyter-notebook", name: a[0], current, };
|
|
30
36
|
}
|
|
31
37
|
}
|
|
@@ -40,23 +46,20 @@ export const notebookCommands = {
|
|
|
40
46
|
handler: async (c, a, f) => {
|
|
41
47
|
requireArgs(a, 1, "dss notebook clear-jupyter-outputs <name>");
|
|
42
48
|
const pk = f["project-key"];
|
|
43
|
-
if (f
|
|
49
|
+
if (executionMode(f).dryRun) {
|
|
50
|
+
// The real mutation is a single server-side DELETE of the
|
|
51
|
+
// outputs endpoint, so the dry run can only report the current
|
|
52
|
+
// content (read-only) and the exact request that would run; the
|
|
53
|
+
// stripped `next` shape would misdescribe the operation.
|
|
44
54
|
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
55
|
return {
|
|
54
56
|
dryRun: true,
|
|
55
57
|
action: "clear-jupyter-outputs",
|
|
56
58
|
resource: "jupyter-notebook",
|
|
57
59
|
name: a[0],
|
|
58
60
|
current,
|
|
59
|
-
|
|
61
|
+
endpoint: encodedProjectEndpoint(c, pk, `/jupyter-notebooks/${encodeURIComponent(a[0])}/outputs`),
|
|
62
|
+
method: "DELETE",
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
65
|
await c.notebooks.clearJupyterOutputs(a[0], pk);
|
|
@@ -77,9 +80,34 @@ export const notebookCommands = {
|
|
|
77
80
|
},
|
|
78
81
|
"unload-jupyter": {
|
|
79
82
|
handler: async (c, a, f) => {
|
|
80
|
-
requireArgs(a, 2, "dss notebook unload-jupyter <name> <sessionId>");
|
|
81
83
|
const pk = f["project-key"];
|
|
82
|
-
if (f["
|
|
84
|
+
if (f["all"] === true) {
|
|
85
|
+
requireNoArgs(a, "dss notebook unload-jupyter --all [--dry-run] [--project-key KEY]");
|
|
86
|
+
if (executionMode(f).dryRun) {
|
|
87
|
+
const planned = [];
|
|
88
|
+
for (const notebook of await c.notebooks.listJupyter(pk, { active: true, })) {
|
|
89
|
+
for (const session of await c.notebooks.listJupyterSessions(notebook.name, pk)) {
|
|
90
|
+
planned.push({
|
|
91
|
+
name: notebook.name,
|
|
92
|
+
sessionId: session.sessionId,
|
|
93
|
+
endpoint: encodedProjectEndpoint(c, pk, `/jupyter-notebooks/${encodeURIComponent(notebook.name)}/sessions/${encodeURIComponent(session.sessionId)}`),
|
|
94
|
+
method: "DELETE",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
dryRun: true,
|
|
100
|
+
action: "unload-jupyter",
|
|
101
|
+
resource: "jupyter-notebook",
|
|
102
|
+
all: true,
|
|
103
|
+
planned,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const unloaded = await c.notebooks.unloadJupyterAll(pk);
|
|
107
|
+
return { unloaded, resource: "jupyter-notebook", all: true, };
|
|
108
|
+
}
|
|
109
|
+
requireArgs(a, 2, "dss notebook unload-jupyter <name> <sessionId>");
|
|
110
|
+
if (executionMode(f).dryRun) {
|
|
83
111
|
const sessions = await c.notebooks.listJupyterSessions(a[0], pk);
|
|
84
112
|
const current = sessions.find((session) => session.sessionId === a[1]);
|
|
85
113
|
return {
|
|
@@ -96,9 +124,12 @@ export const notebookCommands = {
|
|
|
96
124
|
await c.notebooks.unloadJupyter(a[0], a[1], pk);
|
|
97
125
|
return { unloaded: a[0], sessionId: a[1], resource: "jupyter-notebook", };
|
|
98
126
|
},
|
|
99
|
-
usage: "dss notebook unload-jupyter <name> <sessionId
|
|
100
|
-
description: "Unload a Jupyter notebook kernel session.",
|
|
101
|
-
examples: [
|
|
127
|
+
usage: "dss notebook unload-jupyter (<name> <sessionId>|--all) [--dry-run] [--project-key KEY]",
|
|
128
|
+
description: "Unload a Jupyter notebook kernel session, or every session of every running notebook with --all.",
|
|
129
|
+
examples: [
|
|
130
|
+
"dss notebook unload-jupyter my_notebook SESSION_ID --dry-run",
|
|
131
|
+
"dss notebook unload-jupyter --all --dry-run",
|
|
132
|
+
],
|
|
102
133
|
},
|
|
103
134
|
"list-sql": {
|
|
104
135
|
handler: (c, _a, f) => c.notebooks.listSql(f["project-key"]),
|
|
@@ -119,11 +150,11 @@ export const notebookCommands = {
|
|
|
119
150
|
handler: async (c, a, f) => {
|
|
120
151
|
requireArgs(a, 1, "dss notebook delete-sql <id>");
|
|
121
152
|
const pk = f["project-key"];
|
|
122
|
-
if (f
|
|
153
|
+
if (executionMode(f).dryRun || f["if-exists"] === true) {
|
|
123
154
|
const current = await readIfExists(() => c.notebooks.getSql(a[0], pk));
|
|
124
155
|
if (!current)
|
|
125
156
|
return skipResult("sql-notebook", a[0], "missing");
|
|
126
|
-
if (f
|
|
157
|
+
if (executionMode(f).dryRun) {
|
|
127
158
|
return { dryRun: true, action: "delete", resource: "sql-notebook", id: a[0], current, };
|
|
128
159
|
}
|
|
129
160
|
}
|
|
@@ -151,7 +182,7 @@ export const notebookCommands = {
|
|
|
151
182
|
throw new UsageError("--data, --data-file, or --stdin is required (notebook JSON content).");
|
|
152
183
|
}
|
|
153
184
|
const pk = f["project-key"];
|
|
154
|
-
if (f
|
|
185
|
+
if (executionMode(f).dryRun) {
|
|
155
186
|
const current = await readIfExists(() => c.notebooks.getJupyter(a[0], pk));
|
|
156
187
|
return {
|
|
157
188
|
dryRun: true,
|
|
@@ -162,18 +193,18 @@ export const notebookCommands = {
|
|
|
162
193
|
next: data,
|
|
163
194
|
};
|
|
164
195
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
196
|
+
const result = await c.notebooks.saveOrCreateJupyter(a[0], data, pk, {
|
|
197
|
+
expectHash: f["expect-hash"],
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
saved: a[0],
|
|
201
|
+
resource: "jupyter-notebook",
|
|
202
|
+
created: result.created,
|
|
203
|
+
hash: result.hash,
|
|
204
|
+
};
|
|
174
205
|
},
|
|
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.",
|
|
206
|
+
usage: "dss notebook save-jupyter <name> (--data '{...}' | --data-file PATH | --stdin) [--expect-hash SHA256] [--dry-run] [--project-key KEY]",
|
|
207
|
+
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
208
|
examples: [
|
|
178
209
|
"dss notebook save-jupyter my_notebook --data-file notebook.json --dry-run",
|
|
179
210
|
"cat notebook.json | dss notebook save-jupyter my_notebook --stdin",
|
|
@@ -187,7 +218,7 @@ export const notebookCommands = {
|
|
|
187
218
|
throw new UsageError("--data, --data-file, or --stdin is required (SQL notebook content JSON).");
|
|
188
219
|
}
|
|
189
220
|
const pk = f["project-key"];
|
|
190
|
-
if (f
|
|
221
|
+
if (executionMode(f).dryRun) {
|
|
191
222
|
const current = await readIfExists(() => c.notebooks.getSql(a[0], pk));
|
|
192
223
|
return {
|
|
193
224
|
dryRun: true,
|
|
@@ -198,17 +229,18 @@ export const notebookCommands = {
|
|
|
198
229
|
next: data,
|
|
199
230
|
};
|
|
200
231
|
}
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
232
|
+
const result = await c.notebooks.saveOrCreateSql(a[0], data, pk, {
|
|
233
|
+
expectHash: f["expect-hash"],
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
saved: a[0],
|
|
237
|
+
resource: "sql-notebook",
|
|
238
|
+
created: result.created,
|
|
239
|
+
hash: result.hash,
|
|
240
|
+
};
|
|
209
241
|
},
|
|
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.",
|
|
242
|
+
usage: "dss notebook save-sql <id> (--data '{...}' | --data-file PATH | --stdin) [--expect-hash SHA256] [--dry-run] [--project-key KEY]",
|
|
243
|
+
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
244
|
examples: ["dss notebook save-sql my_sql_notebook --data-file content.json --dry-run",],
|
|
213
245
|
},
|
|
214
246
|
"clear-sql-history": {
|
|
@@ -220,7 +252,7 @@ export const notebookCommands = {
|
|
|
220
252
|
numRunsToRetain: num(f["retain"], "--retain"),
|
|
221
253
|
projectKey: pk,
|
|
222
254
|
};
|
|
223
|
-
if (f
|
|
255
|
+
if (executionMode(f).dryRun) {
|
|
224
256
|
const current = await c.notebooks.getSqlHistory(a[0], pk);
|
|
225
257
|
return {
|
|
226
258
|
dryRun: true,
|
|
@@ -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, };
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { encodeLibraryPath, EXPECT_SHA256_PATTERN, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../../resources/project-library.js";
|
|
3
|
+
import { numFlag, readStdinText, sha256Hex, } from "../coerce.js";
|
|
4
|
+
import { executionMode, } from "../flags.js";
|
|
5
|
+
import { encodedProjectEndpoint, planResult, skipResult, } from "../output.js";
|
|
4
6
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
5
7
|
const PROJECT_LIBRARY_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
throw new Error("Project library path is required");
|
|
10
|
-
return normalized;
|
|
11
|
-
}
|
|
12
|
-
function normalizeLibraryDestinationPath(path) {
|
|
13
|
-
if (path === "/")
|
|
14
|
-
return "/";
|
|
15
|
-
return `/${normalizeLibraryPath(path)}`;
|
|
16
|
-
}
|
|
17
|
-
function encodeLibraryPath(path) {
|
|
18
|
-
return normalizeLibraryPath(path)
|
|
19
|
-
.split("/")
|
|
20
|
-
.map((segment) => encodeURIComponent(segment))
|
|
21
|
-
.join("/");
|
|
8
|
+
const PUT_FLAG_ERROR_HINT = "Pass a 64-character SHA-256 hex digest (lowercase or uppercase).";
|
|
9
|
+
function sha256BytesHex(bytes) {
|
|
10
|
+
return new Bun.CryptoHasher("sha256").update(bytes).digest("hex");
|
|
22
11
|
}
|
|
23
12
|
function projectLibraryContentsEndpoint(client, projectKey, path) {
|
|
24
13
|
return encodedProjectEndpoint(client, projectKey, `/libraries/contents/${encodeLibraryPath(path)}`);
|
|
@@ -29,22 +18,53 @@ function projectLibraryFolderEndpoint(client, projectKey, path) {
|
|
|
29
18
|
function projectLibraryActionEndpoint(client, projectKey, action) {
|
|
30
19
|
return encodedProjectEndpoint(client, projectKey, `/libraries/contents-actions/${action}`);
|
|
31
20
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Put-input metadata for plans and reports. Plans must never include content
|
|
23
|
+
* bytes: this helper reports only the source, byte count, and SHA-256 digest —
|
|
24
|
+
* and only when they are available without consuming stdin (dry-run keeps
|
|
25
|
+
* stdin unread, so the stdin source carries no size/hash).
|
|
26
|
+
*/
|
|
27
|
+
export function projectLibraryPutPayload(flags) {
|
|
28
|
+
const expectSha256 = expectSha256FromFlags(flags);
|
|
29
|
+
if (typeof flags["content"] === "string") {
|
|
30
|
+
const text = flags["content"];
|
|
31
|
+
return {
|
|
32
|
+
contentSource: "flag",
|
|
33
|
+
bytes: Buffer.byteLength(text, "utf8"),
|
|
34
|
+
sha256: sha256Hex(text),
|
|
35
|
+
...(expectSha256 ? { expectSha256, } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (typeof flags["file"] === "string") {
|
|
39
|
+
const bytes = fs.readFileSync(flags["file"]);
|
|
40
|
+
return {
|
|
41
|
+
contentSource: "file",
|
|
42
|
+
file: flags["file"],
|
|
43
|
+
bytes: bytes.length,
|
|
44
|
+
sha256: sha256BytesHex(bytes),
|
|
45
|
+
...(expectSha256 ? { expectSha256, } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { contentSource: "stdin", ...(expectSha256 ? { expectSha256, } : {}), };
|
|
38
49
|
}
|
|
39
50
|
function projectLibraryPlan(action, options) {
|
|
40
51
|
return planResult("project-library", action, {
|
|
41
52
|
...options,
|
|
53
|
+
idempotency: options.idempotency ?? "none",
|
|
42
54
|
asyncKind: "none",
|
|
43
55
|
exitCodesOnFailure: PROJECT_LIBRARY_EXIT_CODES,
|
|
44
|
-
idempotency: "none",
|
|
45
56
|
plannedAndDryRun: true,
|
|
46
57
|
});
|
|
47
58
|
}
|
|
59
|
+
function expectSha256FromFlags(flags) {
|
|
60
|
+
const value = flags["expect-sha256"];
|
|
61
|
+
if (value === undefined || value === false)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (typeof value !== "string" || !EXPECT_SHA256_PATTERN.test(value)) {
|
|
64
|
+
throw new UsageError("--expect-sha256 must be a 64-character SHA-256 hex digest.", "validation_failed", PUT_FLAG_ERROR_HINT);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
48
68
|
export const projectLibraryCommands = {
|
|
49
69
|
list: {
|
|
50
70
|
handler: (c, _a, f) => c.projectLibrary.listContents(f["project-key"]),
|
|
@@ -55,6 +75,7 @@ export const projectLibraryCommands = {
|
|
|
55
75
|
get: {
|
|
56
76
|
handler: (c, a, f) => {
|
|
57
77
|
requireArgs(a, 1, "dss project-library get <path> [--project-key KEY]");
|
|
78
|
+
validateLibraryPath(a[0]);
|
|
58
79
|
return c.projectLibrary.getFile(a[0], f["project-key"]);
|
|
59
80
|
},
|
|
60
81
|
usage: "dss project-library get <path> [--project-key KEY]",
|
|
@@ -69,7 +90,7 @@ export const projectLibraryCommands = {
|
|
|
69
90
|
throw new UsageError("--output PATH is required.", "missing_required_flag");
|
|
70
91
|
const bytes = await c.projectLibrary.getFileBytes(a[0], f["project-key"]);
|
|
71
92
|
const written = await Bun.write(out, bytes, { createPath: false, });
|
|
72
|
-
return { path: out, bytes: written, };
|
|
93
|
+
return { path: out, bytes: written, sha256: sha256BytesHex(bytes), };
|
|
73
94
|
},
|
|
74
95
|
usage: "dss project-library get-bytes <path> --output PATH [--project-key KEY]",
|
|
75
96
|
description: "Download a project library file's raw bytes to a local file.",
|
|
@@ -77,45 +98,63 @@ export const projectLibraryCommands = {
|
|
|
77
98
|
},
|
|
78
99
|
"create-file": {
|
|
79
100
|
handler: async (c, a, f) => {
|
|
80
|
-
requireArgs(a, 1, "dss project-library create-file <path> [--dry-run] [--project-key KEY]");
|
|
101
|
+
requireArgs(a, 1, "dss project-library create-file <path> [--if-not-exists] [--dry-run] [--project-key KEY]");
|
|
81
102
|
const pk = f["project-key"];
|
|
82
|
-
|
|
103
|
+
validateLibraryPath(a[0]);
|
|
104
|
+
if (executionMode(f).dryRun) {
|
|
83
105
|
return projectLibraryPlan("create-file", {
|
|
84
106
|
method: "POST",
|
|
85
107
|
endpoint: projectLibraryContentsEndpoint(c, pk, a[0]),
|
|
86
108
|
identifiers: { path: a[0], },
|
|
109
|
+
idempotency: "if-not-exists",
|
|
87
110
|
});
|
|
88
111
|
}
|
|
112
|
+
if (f["if-not-exists"] === true) {
|
|
113
|
+
const exists = await c.projectLibrary.hasLibraryItem(a[0], pk);
|
|
114
|
+
if (exists) {
|
|
115
|
+
return skipResult("project-library", a[0], "exists", { kind: "file", });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
89
118
|
await c.projectLibrary.addFile(a[0], pk);
|
|
90
119
|
return { created: a[0], };
|
|
91
120
|
},
|
|
92
|
-
usage: "dss project-library create-file <path> [--dry-run] [--project-key KEY]",
|
|
93
|
-
description: "Create an empty file in the project library.",
|
|
94
|
-
examples: ["dss project-library create-file python/mylib/new.py",],
|
|
121
|
+
usage: "dss project-library create-file <path> [--if-not-exists] [--dry-run] [--project-key KEY]",
|
|
122
|
+
description: "Create an empty file in the project library; refuses to overwrite an existing item.",
|
|
123
|
+
examples: ["dss project-library create-file python/mylib/new.py --if-not-exists",],
|
|
95
124
|
},
|
|
96
125
|
"create-folder": {
|
|
97
126
|
handler: async (c, a, f) => {
|
|
98
|
-
requireArgs(a, 1, "dss project-library create-folder <path> [--dry-run] [--project-key KEY]");
|
|
127
|
+
requireArgs(a, 1, "dss project-library create-folder <path> [--if-not-exists] [--dry-run] [--project-key KEY]");
|
|
99
128
|
const pk = f["project-key"];
|
|
100
|
-
|
|
129
|
+
validateLibraryPath(a[0]);
|
|
130
|
+
if (executionMode(f).dryRun) {
|
|
101
131
|
return projectLibraryPlan("create-folder", {
|
|
102
132
|
method: "POST",
|
|
103
133
|
endpoint: projectLibraryFolderEndpoint(c, pk, a[0]),
|
|
104
134
|
identifiers: { path: a[0], },
|
|
135
|
+
idempotency: "if-not-exists",
|
|
105
136
|
});
|
|
106
137
|
}
|
|
138
|
+
if (f["if-not-exists"] === true) {
|
|
139
|
+
const exists = await c.projectLibrary.hasLibraryItem(a[0], pk);
|
|
140
|
+
if (exists) {
|
|
141
|
+
return skipResult("project-library", a[0], "exists", { kind: "folder", });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
107
144
|
await c.projectLibrary.addFolder(a[0], pk);
|
|
108
145
|
return { created: a[0], };
|
|
109
146
|
},
|
|
110
|
-
usage: "dss project-library create-folder <path> [--dry-run] [--project-key KEY]",
|
|
111
|
-
description: "Create a folder in the project library.",
|
|
112
|
-
examples: ["dss project-library create-folder python/mylib",],
|
|
147
|
+
usage: "dss project-library create-folder <path> [--if-not-exists] [--dry-run] [--project-key KEY]",
|
|
148
|
+
description: "Create a folder in the project library; refuses to overwrite an existing item.",
|
|
149
|
+
examples: ["dss project-library create-folder python/mylib --if-not-exists",],
|
|
113
150
|
},
|
|
114
151
|
put: {
|
|
115
152
|
handler: async (c, a, f) => {
|
|
116
|
-
requireArgs(a, 1, "dss project-library put <path> (--content TEXT|--file PATH|--stdin) [--dry-run] [--project-key KEY]");
|
|
153
|
+
requireArgs(a, 1, "dss project-library put <path> (--content TEXT|--file PATH|--stdin) [--expect-sha256 SHA256] [--dry-run] [--project-key KEY]");
|
|
117
154
|
const pk = f["project-key"];
|
|
118
|
-
|
|
155
|
+
const expectSha256 = expectSha256FromFlags(f);
|
|
156
|
+
validateLibraryPath(a[0]);
|
|
157
|
+
if (executionMode(f).dryRun) {
|
|
119
158
|
return projectLibraryPlan("put", {
|
|
120
159
|
method: "POST",
|
|
121
160
|
endpoint: projectLibraryContentsEndpoint(c, pk, a[0]),
|
|
@@ -126,20 +165,49 @@ export const projectLibraryCommands = {
|
|
|
126
165
|
const content = typeof f["content"] === "string"
|
|
127
166
|
? f["content"]
|
|
128
167
|
: typeof f["file"] === "string"
|
|
129
|
-
? fs.readFileSync(f["file"]
|
|
168
|
+
? fs.readFileSync(f["file"])
|
|
169
|
+
: await readStdinText();
|
|
170
|
+
const result = await c.projectLibrary.addOrUpdateFile(a[0], content, pk, expectSha256 !== undefined ? { expectSha256, } : undefined);
|
|
171
|
+
return {
|
|
172
|
+
updated: result.path,
|
|
173
|
+
bytes: result.bytes,
|
|
174
|
+
sha256: result.sha256,
|
|
175
|
+
...(result.beforeSha256 !== undefined ? { beforeSha256: result.beforeSha256, } : {}),
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
usage: "dss project-library put <path> (--content TEXT|--file PATH|--stdin) [--expect-sha256 SHA256] [--dry-run] [--project-key KEY]",
|
|
179
|
+
description: "Create or overwrite a project library file with text or binary content; reports the written byte count and sha256.",
|
|
180
|
+
examples: [
|
|
181
|
+
"dss project-library put python/mylib/utils.py --file ./utils.py",
|
|
182
|
+
"dss project-library put python/mylib/utils.py --file ./utils.py --expect-sha256 <previous-sha256>",
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
diff: {
|
|
186
|
+
handler: async (c, a, f) => {
|
|
187
|
+
requireArgs(a, 1, "dss project-library diff <path> (--content TEXT|--file PATH|--stdin) [--max-lines N] [--project-key KEY]");
|
|
188
|
+
const pk = f["project-key"];
|
|
189
|
+
validateLibraryPath(a[0]);
|
|
190
|
+
const maxLines = numFlag(f, ["max-lines",]);
|
|
191
|
+
const local = typeof f["content"] === "string"
|
|
192
|
+
? f["content"]
|
|
193
|
+
: typeof f["file"] === "string"
|
|
194
|
+
? fs.readFileSync(f["file"])
|
|
130
195
|
: await readStdinText();
|
|
131
|
-
|
|
132
|
-
return { updated: a[0], };
|
|
196
|
+
return c.projectLibrary.diffFile(a[0], local, pk, maxLines !== undefined ? { maxLines, } : {});
|
|
133
197
|
},
|
|
134
|
-
usage: "dss project-library
|
|
135
|
-
description: "
|
|
136
|
-
examples: [
|
|
198
|
+
usage: "dss project-library diff <path> (--content TEXT|--file PATH|--stdin) [--max-lines N] [--project-key KEY]",
|
|
199
|
+
description: "Diff a project library file against local content; capped unified text diff, binary files detected instead of dumped.",
|
|
200
|
+
examples: [
|
|
201
|
+
"dss project-library diff python/mylib/utils.py --file ./utils.py",
|
|
202
|
+
"dss project-library diff python/mylib/utils.py --content 'print(1)' --max-lines 50",
|
|
203
|
+
],
|
|
137
204
|
},
|
|
138
205
|
delete: {
|
|
139
206
|
handler: async (c, a, f) => {
|
|
140
207
|
requireArgs(a, 1, "dss project-library delete <path> [--dry-run] [--project-key KEY]");
|
|
141
208
|
const pk = f["project-key"];
|
|
142
|
-
|
|
209
|
+
validateLibraryPath(a[0]);
|
|
210
|
+
if (executionMode(f).dryRun) {
|
|
143
211
|
return projectLibraryPlan("delete", {
|
|
144
212
|
method: "DELETE",
|
|
145
213
|
endpoint: projectLibraryContentsEndpoint(c, pk, a[0]),
|
|
@@ -157,16 +225,18 @@ export const projectLibraryCommands = {
|
|
|
157
225
|
handler: async (c, a, f) => {
|
|
158
226
|
requireArgs(a, 2, "dss project-library rename <path> <new-name> [--dry-run] [--project-key KEY]");
|
|
159
227
|
const pk = f["project-key"];
|
|
160
|
-
|
|
228
|
+
const validPath = validateLibraryPath(a[0]);
|
|
229
|
+
const validName = validateLibraryName(a[1]);
|
|
230
|
+
if (executionMode(f).dryRun) {
|
|
161
231
|
return projectLibraryPlan("rename", {
|
|
162
232
|
method: "POST",
|
|
163
233
|
endpoint: projectLibraryActionEndpoint(c, pk, "rename/"),
|
|
164
|
-
identifiers: { path:
|
|
165
|
-
payload: { oldPath: `/${
|
|
234
|
+
identifiers: { path: validPath, newName: validName, },
|
|
235
|
+
payload: { oldPath: `/${validPath}`, newName: validName, },
|
|
166
236
|
});
|
|
167
237
|
}
|
|
168
|
-
await c.projectLibrary.rename(
|
|
169
|
-
return { renamed:
|
|
238
|
+
await c.projectLibrary.rename(validPath, validName, pk);
|
|
239
|
+
return { renamed: validPath, to: validName, };
|
|
170
240
|
},
|
|
171
241
|
usage: "dss project-library rename <path> <new-name> [--dry-run] [--project-key KEY]",
|
|
172
242
|
description: "Rename a project library file or folder within its parent.",
|
|
@@ -176,19 +246,21 @@ export const projectLibraryCommands = {
|
|
|
176
246
|
handler: async (c, a, f) => {
|
|
177
247
|
requireArgs(a, 2, "dss project-library move <path> <destination-folder> [--dry-run] [--project-key KEY]");
|
|
178
248
|
const pk = f["project-key"];
|
|
179
|
-
|
|
249
|
+
const validPath = validateLibraryPath(a[0]);
|
|
250
|
+
const destination = validateLibraryDestinationPath(a[1]);
|
|
251
|
+
if (executionMode(f).dryRun) {
|
|
180
252
|
return projectLibraryPlan("move", {
|
|
181
253
|
method: "POST",
|
|
182
254
|
endpoint: projectLibraryActionEndpoint(c, pk, "move"),
|
|
183
|
-
identifiers: { path:
|
|
255
|
+
identifiers: { path: validPath, destinationFolder: destination, },
|
|
184
256
|
payload: {
|
|
185
|
-
oldPath: `/${
|
|
186
|
-
newPath:
|
|
257
|
+
oldPath: `/${validPath}`,
|
|
258
|
+
newPath: destination,
|
|
187
259
|
},
|
|
188
260
|
});
|
|
189
261
|
}
|
|
190
|
-
await c.projectLibrary.move(
|
|
191
|
-
return { moved:
|
|
262
|
+
await c.projectLibrary.move(validPath, destination, pk);
|
|
263
|
+
return { moved: validPath, to: destination, };
|
|
192
264
|
},
|
|
193
265
|
usage: "dss project-library move <path> <destination-folder> [--dry-run] [--project-key KEY]",
|
|
194
266
|
description: "Move a project library file or folder into another folder.",
|
|
@@ -3,9 +3,17 @@ import { deepMerge, } from "../../utils/deep-merge.js";
|
|
|
3
3
|
import { inspectProjectArchive, } from "../../utils/project-archive.js";
|
|
4
4
|
import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
|
|
5
5
|
import { jsonInput, num, requiredJsonInput, requiredStringFlag, } from "../coerce.js";
|
|
6
|
+
import { executionMode, } from "../flags.js";
|
|
6
7
|
import { CommandResultFailure, } from "../output.js";
|
|
7
8
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
8
9
|
const INSPECT_ARCHIVE_USAGE = "dss project inspect-archive <file>";
|
|
10
|
+
function flowRenderFormat(value) {
|
|
11
|
+
if (value === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (value === "ascii" || value === "mermaid")
|
|
14
|
+
return value;
|
|
15
|
+
throw new UsageError("--render must be ascii or mermaid.", "invalid_enum");
|
|
16
|
+
}
|
|
9
17
|
/** Local, DSS-free archive inspection shared by the local handler and the command handler. */
|
|
10
18
|
function inspectArchiveCommand(args) {
|
|
11
19
|
requireArgs(args, 1, INSPECT_ARCHIVE_USAGE);
|
|
@@ -42,12 +50,19 @@ export const projectCommands = {
|
|
|
42
50
|
maxNodes: num(f["max-nodes"], "--max-nodes"),
|
|
43
51
|
maxEdges: num(f["max-edges"], "--max-edges"),
|
|
44
52
|
includeRaw: f["include-raw"] === true,
|
|
53
|
+
render: flowRenderFormat(f["render"]),
|
|
45
54
|
}),
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
validate: (_a, f) => {
|
|
56
|
+
flowRenderFormat(f["render"]);
|
|
57
|
+
num(f["max-nodes"], "--max-nodes");
|
|
58
|
+
num(f["max-edges"], "--max-edges");
|
|
59
|
+
},
|
|
60
|
+
usage: "dss project map [--max-nodes N] [--max-edges N] [--include-raw] [--render ascii|mermaid] [--project-key KEY]",
|
|
61
|
+
description: "Get an analyzed flow map with zones, layers, components, and diagnostics.",
|
|
48
62
|
examples: [
|
|
49
63
|
"dss project map",
|
|
50
64
|
"dss project map --max-nodes 50 --max-edges 100",
|
|
65
|
+
"dss project map --render mermaid",
|
|
51
66
|
"dss project map --include-raw",
|
|
52
67
|
],
|
|
53
68
|
},
|
|
@@ -77,7 +92,7 @@ export const projectCommands = {
|
|
|
77
92
|
throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
|
|
78
93
|
}
|
|
79
94
|
const guarded = f["if-exists"] === true
|
|
80
|
-
|| f
|
|
95
|
+
|| executionMode(f).dryRun
|
|
81
96
|
|| expectedIncarnation !== undefined;
|
|
82
97
|
if (!guarded) {
|
|
83
98
|
// Purely user-requested delete without any guard flag: keep the
|
|
@@ -114,7 +129,7 @@ export const projectCommands = {
|
|
|
114
129
|
}
|
|
115
130
|
throw error;
|
|
116
131
|
}
|
|
117
|
-
if (f
|
|
132
|
+
if (executionMode(f).dryRun) {
|
|
118
133
|
return {
|
|
119
134
|
deleted: false,
|
|
120
135
|
dryRun: true,
|