dataiku-sdk 3.1.0 → 3.3.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.
Files changed (149) hide show
  1. package/README.md +253 -38
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +102 -0
  4. package/dist/packages/types/src/index.js +76 -0
  5. package/dist/src/auth.js +1 -1
  6. package/dist/src/cli/coerce.js +11 -2
  7. package/dist/src/cli/commands/analysis.js +4 -3
  8. package/dist/src/cli/commands/api-service.js +5 -3
  9. package/dist/src/cli/commands/app.js +61 -18
  10. package/dist/src/cli/commands/bundle.js +3 -2
  11. package/dist/src/cli/commands/code-env.js +23 -16
  12. package/dist/src/cli/commands/connection.d.ts +9 -0
  13. package/dist/src/cli/commands/connection.js +267 -1
  14. package/dist/src/cli/commands/dashboard.js +7 -6
  15. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  16. package/dist/src/cli/commands/data-collection.js +164 -0
  17. package/dist/src/cli/commands/data-quality.js +9 -8
  18. package/dist/src/cli/commands/dataset.js +170 -40
  19. package/dist/src/cli/commands/doctor.js +4 -2
  20. package/dist/src/cli/commands/flow-zone.js +9 -8
  21. package/dist/src/cli/commands/folder.js +9 -8
  22. package/dist/src/cli/commands/future.js +4 -3
  23. package/dist/src/cli/commands/group.d.ts +2 -0
  24. package/dist/src/cli/commands/group.js +109 -0
  25. package/dist/src/cli/commands/index.js +151 -78
  26. package/dist/src/cli/commands/insight.js +8 -7
  27. package/dist/src/cli/commands/job.js +4 -3
  28. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  29. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  30. package/dist/src/cli/commands/llm.d.ts +2 -0
  31. package/dist/src/cli/commands/llm.js +93 -0
  32. package/dist/src/cli/commands/macro.d.ts +2 -0
  33. package/dist/src/cli/commands/macro.js +221 -0
  34. package/dist/src/cli/commands/meaning.js +5 -4
  35. package/dist/src/cli/commands/ml-task.js +21 -8
  36. package/dist/src/cli/commands/model-evaluation-store.js +4 -3
  37. package/dist/src/cli/commands/notebook.js +17 -12
  38. package/dist/src/cli/commands/plugin.d.ts +2 -0
  39. package/dist/src/cli/commands/plugin.js +799 -0
  40. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  41. package/dist/src/cli/commands/project-folder.js +197 -0
  42. package/dist/src/cli/commands/project-git.js +14 -54
  43. package/dist/src/cli/commands/project-library.js +7 -6
  44. package/dist/src/cli/commands/project.js +55 -8
  45. package/dist/src/cli/commands/recipe.js +50 -14
  46. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  47. package/dist/src/cli/commands/saved-model.js +392 -4
  48. package/dist/src/cli/commands/scenario.js +147 -9
  49. package/dist/src/cli/commands/sql.js +3 -2
  50. package/dist/src/cli/commands/statistics.js +7 -6
  51. package/dist/src/cli/commands/user.d.ts +8 -0
  52. package/dist/src/cli/commands/user.js +296 -0
  53. package/dist/src/cli/commands/variable.js +2 -1
  54. package/dist/src/cli/commands/webapp.js +2 -1
  55. package/dist/src/cli/commands/wiki.js +7 -6
  56. package/dist/src/cli/contract.d.ts +2 -3
  57. package/dist/src/cli/contract.js +1167 -163
  58. package/dist/src/cli/flags.d.ts +4 -0
  59. package/dist/src/cli/flags.js +50 -2
  60. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  61. package/dist/src/cli/helpers/app-successor.js +90 -30
  62. package/dist/src/cli/helpers/cleanup.js +3 -1
  63. package/dist/src/cli/helpers/flow-zone.js +1 -2
  64. package/dist/src/cli/output.d.ts +1 -1
  65. package/dist/src/cli/output.js +23 -10
  66. package/dist/src/cli/usage.d.ts +1 -0
  67. package/dist/src/cli/usage.js +47 -1
  68. package/dist/src/cli.js +225 -95
  69. package/dist/src/client.d.ts +86 -6
  70. package/dist/src/client.js +263 -50
  71. package/dist/src/errors.d.ts +3 -0
  72. package/dist/src/errors.js +4 -0
  73. package/dist/src/index.d.ts +16 -7
  74. package/dist/src/index.js +11 -2
  75. package/dist/src/resources/analyses.js +1 -8
  76. package/dist/src/resources/applications.d.ts +8 -6
  77. package/dist/src/resources/applications.js +21 -21
  78. package/dist/src/resources/base.d.ts +4 -0
  79. package/dist/src/resources/base.js +15 -0
  80. package/dist/src/resources/bundles.d.ts +9 -4
  81. package/dist/src/resources/bundles.js +10 -5
  82. package/dist/src/resources/connections.d.ts +78 -1
  83. package/dist/src/resources/connections.js +132 -1
  84. package/dist/src/resources/data-collections.d.ts +118 -0
  85. package/dist/src/resources/data-collections.js +49 -0
  86. package/dist/src/resources/datasets.d.ts +69 -0
  87. package/dist/src/resources/datasets.js +99 -1
  88. package/dist/src/resources/folders.js +2 -1
  89. package/dist/src/resources/futures.d.ts +4 -1
  90. package/dist/src/resources/futures.js +38 -6
  91. package/dist/src/resources/groups.d.ts +47 -0
  92. package/dist/src/resources/groups.js +56 -0
  93. package/dist/src/resources/jobs.d.ts +8 -13
  94. package/dist/src/resources/jobs.js +41 -16
  95. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  96. package/dist/src/resources/knowledge-banks.js +61 -0
  97. package/dist/src/resources/llms.d.ts +92 -0
  98. package/dist/src/resources/llms.js +62 -0
  99. package/dist/src/resources/macros.d.ts +121 -0
  100. package/dist/src/resources/macros.js +231 -0
  101. package/dist/src/resources/ml-tasks.d.ts +37 -2
  102. package/dist/src/resources/ml-tasks.js +100 -20
  103. package/dist/src/resources/model-evaluation-stores.js +1 -8
  104. package/dist/src/resources/notebooks.d.ts +43 -7
  105. package/dist/src/resources/notebooks.js +53 -13
  106. package/dist/src/resources/plugins.d.ts +265 -0
  107. package/dist/src/resources/plugins.js +458 -0
  108. package/dist/src/resources/project-folders.d.ts +71 -0
  109. package/dist/src/resources/project-folders.js +57 -0
  110. package/dist/src/resources/project-git.d.ts +6 -0
  111. package/dist/src/resources/project-git.js +69 -42
  112. package/dist/src/resources/project-library.d.ts +0 -1
  113. package/dist/src/resources/project-library.js +1 -17
  114. package/dist/src/resources/projects.d.ts +19 -3
  115. package/dist/src/resources/projects.js +30 -18
  116. package/dist/src/resources/recipes.d.ts +15 -1
  117. package/dist/src/resources/recipes.js +73 -6
  118. package/dist/src/resources/saved-models.d.ts +134 -1
  119. package/dist/src/resources/saved-models.js +214 -6
  120. package/dist/src/resources/scenarios.d.ts +105 -0
  121. package/dist/src/resources/scenarios.js +232 -106
  122. package/dist/src/resources/sql.js +5 -1
  123. package/dist/src/resources/users.d.ts +129 -0
  124. package/dist/src/resources/users.js +128 -0
  125. package/dist/src/resources/webapps.d.ts +15 -2
  126. package/dist/src/resources/webapps.js +18 -4
  127. package/dist/src/schemas.d.ts +2 -2
  128. package/dist/src/schemas.js +1 -1
  129. package/dist/src/skill.d.ts +13 -4
  130. package/dist/src/skill.js +75 -24
  131. package/dist/src/utils/deep-merge.d.ts +1 -1
  132. package/dist/src/utils/polling.d.ts +20 -0
  133. package/dist/src/utils/polling.js +27 -0
  134. package/dist/src/utils/response-file.d.ts +2 -0
  135. package/dist/src/utils/response-file.js +34 -0
  136. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  137. package/dist/src/utils/secret-sanitize.js +101 -0
  138. package/package.json +8 -5
  139. package/packages/types/dist/index.d.ts +102 -0
  140. package/packages/types/dist/index.js +76 -0
  141. package/plugin.json +1 -1
  142. package/skills/dataiku-dss/SKILL.md +47 -225
  143. package/skills/dataiku-dss/references/app-releases.md +28 -0
  144. package/skills/dataiku-dss/references/authentication.md +39 -0
  145. package/skills/dataiku-dss/references/coding.md +8 -0
  146. package/skills/dataiku-dss/references/discovery.md +30 -0
  147. package/skills/dataiku-dss/references/flow-maps.md +12 -0
  148. package/skills/dataiku-dss/references/mutations.md +33 -0
  149. package/skills/dataiku-dss/references/troubleshooting.md +29 -0
@@ -2,6 +2,7 @@ import { buildDatasetCloneSettings, } from "../../resources/datasets.js";
2
2
  import { deepMerge, } from "../../utils/deep-merge.js";
3
3
  import { compareStrings, stableHash, } from "../../utils/stable-hash.js";
4
4
  import { jsonInput, num, schemaColumnsInput, unknownJsonInput, } from "../coerce.js";
5
+ import { executionMode, } from "../flags.js";
5
6
  import { datasetSourceSummary, } from "../helpers/dataset.js";
6
7
  import { moveCreatedItemsToZone, resolveFlowZoneIdFromFlags, } from "../helpers/flow-zone.js";
7
8
  import { enqueueCliWarning, readIfExists, skipResult, } from "../output.js";
@@ -195,7 +196,7 @@ export const datasetCommands = {
195
196
  requireArgs(a, 1, usage);
196
197
  const columns = schemaColumnsInput(f, usage);
197
198
  const pk = f["project-key"];
198
- if (f["dry-run"] === true) {
199
+ if (executionMode(f).dryRun) {
199
200
  const current = await c.datasets.schema(a[0], pk);
200
201
  return {
201
202
  dryRun: true,
@@ -307,43 +308,38 @@ export const datasetCommands = {
307
308
  description: "Get dataset-level metadata.",
308
309
  examples: ["dss dataset metadata orders",],
309
310
  },
310
- download: {
311
+ "metadata-set": {
311
312
  handler: async (c, a, f) => {
312
- requireArgs(a, 1, "dss dataset download <name>");
313
- const result = await c.datasets.download(a[0], {
314
- outputPath: f["output"],
315
- projectKey: f["project-key"],
316
- limit: num(f["limit"], "--limit"),
317
- rawData: f["raw-data"] === true,
318
- });
319
- if (result.truncated) {
320
- enqueueCliWarning({
321
- code: "dataset_download_truncated",
322
- message: `Download of '${a[0]}' stopped at the ${result.limit}-row cap; the dataset has more rows. `
323
- + "Re-run with --limit N for more, or read inside a recipe (get_dataframe) for the full data.",
324
- dataset: a[0],
325
- rows: result.rows,
326
- limit: result.limit,
327
- path: result.path,
328
- });
313
+ const usage = "dss dataset metadata-set <name> (--data JSON | --data-file PATH | --stdin) [--dry-run] [--project-key KEY]";
314
+ requireArgs(a, 1, usage);
315
+ // Validated identically for live, --plan, and --dry-run: the JSON
316
+ // input replaces the server metadata object verbatim (faithful PUT;
317
+ // no client-side merge).
318
+ const metadata = jsonInput(f);
319
+ if (metadata === undefined) {
320
+ throw new UsageError(`--data, --data-file, or --stdin is required. Usage: ${usage}`);
329
321
  }
330
- if (!f["output"]) {
331
- enqueueCliWarning({
332
- code: "dataset_download_default_location",
333
- message: `No --output was given, so '${a[0]}' was written to '${result.path}' in the current directory. `
334
- + "Pass --output PATH to control the destination and avoid writing into your working tree.",
335
- dataset: a[0],
336
- path: result.path,
337
- });
322
+ const pk = f["project-key"];
323
+ if (executionMode(f).dryRun) {
324
+ // Zero HTTP: the replacement JSON is validated and echoed as-is;
325
+ // current server state is intentionally not fetched (faithful
326
+ // replace means the PUT body is exactly this object).
327
+ return {
328
+ dryRun: true,
329
+ action: "metadata-set",
330
+ resource: "dataset",
331
+ name: a[0],
332
+ next: metadata,
333
+ };
338
334
  }
339
- return result;
335
+ const updated = await c.datasets.updateMetadata(a[0], metadata, pk);
336
+ return { updated: a[0], resource: "dataset", metadata: updated, };
340
337
  },
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.",
338
+ usage: "dss dataset metadata-set <name> (--data JSON | --data-file PATH | --stdin) [--dry-run] [--project-key KEY]",
339
+ description: "Replace dataset-level metadata with the given JSON object (faithful PUT: GET, edit, PUT full object; no merge).",
343
340
  examples: [
344
- "dss dataset download orders",
345
- "dss dataset download orders --output ./data/",
346
- "dss dataset download orders --raw-data",
341
+ `dss dataset metadata-set orders --data '{"label":"Orders","tags":["prod"]}' --dry-run`,
342
+ 'echo \'{"label":"Orders"}\' | dss dataset metadata-set orders --stdin',
347
343
  ],
348
344
  },
349
345
  create: {
@@ -365,13 +361,13 @@ export const datasetCommands = {
365
361
  projectKey: pk,
366
362
  };
367
363
  const zoneId = await resolveFlowZoneIdFromFlags(c, f, pk);
368
- if (f["if-not-exists"] === true || f["dry-run"] === true) {
364
+ if (f["if-not-exists"] === true || executionMode(f).dryRun) {
369
365
  const list = await c.datasets.list(pk);
370
366
  const existing = list.find((d) => d.name === name);
371
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
367
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
372
368
  return skipResult("dataset", name, "exists", { current: existing, });
373
369
  }
374
- if (f["dry-run"] === true) {
370
+ if (executionMode(f).dryRun) {
375
371
  return {
376
372
  dryRun: true,
377
373
  action: "create",
@@ -395,6 +391,140 @@ export const datasetCommands = {
395
391
  "dss dataset create --name orders --connection filesystem --type Filesystem --zone Experiments --dry-run",
396
392
  ],
397
393
  },
394
+ "create-managed": {
395
+ handler: async (c, _a, f) => {
396
+ const usage = "dss dataset create-managed --name NAME --connection CONN [--type-option-id ID] [--format-option-id ID] [--copy-partitioning-from REF] [--partitioning-folder] [--dry-run] [--project-key KEY]";
397
+ const name = f["name"];
398
+ if (!name || name.trim() === "") {
399
+ throw new UsageError(`--name is required. Usage: ${usage}`);
400
+ }
401
+ const connection = f["connection"];
402
+ if (!connection || connection.trim() === "") {
403
+ throw new UsageError(`--connection is required. Usage: ${usage}`);
404
+ }
405
+ const pk = f["project-key"];
406
+ // Server-derived settings only: no fabricated defaults are merged in.
407
+ const payload = {
408
+ name,
409
+ connection,
410
+ ...(typeof f["type-option-id"] === "string" ? { typeOptionId: f["type-option-id"], } : {}),
411
+ ...(typeof f["format-option-id"] === "string"
412
+ ? { formatOptionId: f["format-option-id"], }
413
+ : {}),
414
+ ...(typeof f["copy-partitioning-from"] === "string"
415
+ ? {
416
+ copyPartitioningFrom: {
417
+ ref: f["copy-partitioning-from"],
418
+ ...(f["partitioning-folder"] === true ? { type: "FOLDER", } : {}),
419
+ },
420
+ }
421
+ : {}),
422
+ };
423
+ if (executionMode(f).dryRun) {
424
+ return { dryRun: true, action: "create-managed", resource: "dataset", ...payload, };
425
+ }
426
+ const created = await c.datasets.createManaged({
427
+ name,
428
+ connection,
429
+ ...(typeof f["type-option-id"] === "string" ? { typeOptionId: f["type-option-id"], } : {}),
430
+ ...(typeof f["format-option-id"] === "string"
431
+ ? { formatOptionId: f["format-option-id"], }
432
+ : {}),
433
+ ...(typeof f["copy-partitioning-from"] === "string"
434
+ ? {
435
+ copyPartitioningFrom: {
436
+ ref: f["copy-partitioning-from"],
437
+ ...(f["partitioning-folder"] === true ? { type: "FOLDER", } : {}),
438
+ },
439
+ }
440
+ : {}),
441
+ }, pk);
442
+ return {
443
+ created: created.datasetName,
444
+ resource: "dataset",
445
+ managed: true,
446
+ projectKey: created.projectKey,
447
+ creationSettings: created.creationSettings,
448
+ };
449
+ },
450
+ usage: "dss dataset create-managed --name NAME --connection CONN [--type-option-id ID] [--format-option-id ID] [--copy-partitioning-from REF] [--partitioning-folder] [--dry-run] [--project-key KEY]",
451
+ description: "Create a managed dataset via the dedicated endpoint: DSS derives storage/format details; you pick name, connection, and optional type/format/partitioning options.",
452
+ examples: [
453
+ "dss dataset create-managed --name orders_copy --connection filesystem_managed",
454
+ "dss dataset create-managed --name events_parquet --connection s3_conn --format-option-id PARQUET_HIVE --dry-run",
455
+ "dss dataset create-managed --name partitioned --connection s3_conn --copy-partitioning-from raw_events",
456
+ ],
457
+ },
458
+ info: {
459
+ handler: (c, a, f) => {
460
+ requireArgs(a, 1, "dss dataset info <name>");
461
+ return c.datasets.info(a[0], f["project-key"]);
462
+ },
463
+ usage: "dss dataset info <name> [--project-key KEY]",
464
+ description: "Get the full info object for a dataset (type, parameters, last build information, schema, etc.).",
465
+ examples: ["dss dataset info orders", "dss dataset info orders --project-key MYPROJ",],
466
+ },
467
+ "column-lineage": {
468
+ handler: (c, a, f) => {
469
+ const usage = "dss dataset column-lineage <name> <column> [--max-dataset-count N] [--project-key KEY]";
470
+ requireArgs(a, 2, usage);
471
+ const maxDatasetCount = num(f["max-dataset-count"], "--max-dataset-count");
472
+ if (maxDatasetCount !== undefined
473
+ && (!Number.isSafeInteger(maxDatasetCount) || maxDatasetCount <= 0)) {
474
+ throw new UsageError(`--max-dataset-count must be a positive integer (got "${String(f["max-dataset-count"])}"). `
475
+ + `Usage: ${usage}`, "validation_failed");
476
+ }
477
+ return c.datasets.getColumnLineage(a[0], a[1], {
478
+ maxDatasetCount,
479
+ projectKey: f["project-key"],
480
+ });
481
+ },
482
+ usage: "dss dataset column-lineage <name> <column> [--max-dataset-count N] [--project-key KEY]",
483
+ description: "Get the full column lineage (auto-computed and manual) of a column; includes local and foreign project relations.",
484
+ examples: [
485
+ "dss dataset column-lineage orders customer_id",
486
+ "dss dataset column-lineage orders customer_id --max-dataset-count 2000",
487
+ ],
488
+ },
489
+ download: {
490
+ handler: async (c, a, f) => {
491
+ requireArgs(a, 1, "dss dataset download <name>");
492
+ const result = await c.datasets.download(a[0], {
493
+ outputPath: f["output"],
494
+ projectKey: f["project-key"],
495
+ limit: num(f["limit"], "--limit"),
496
+ rawData: f["raw-data"] === true,
497
+ });
498
+ if (result.truncated) {
499
+ enqueueCliWarning({
500
+ code: "dataset_download_truncated",
501
+ message: `Download of '${a[0]}' stopped at the ${result.limit}-row cap; the dataset has more rows. `
502
+ + "Re-run with --limit N for more, or read inside a recipe (get_dataframe) for the full data.",
503
+ dataset: a[0],
504
+ rows: result.rows,
505
+ limit: result.limit,
506
+ path: result.path,
507
+ });
508
+ }
509
+ if (!f["output"]) {
510
+ enqueueCliWarning({
511
+ code: "dataset_download_default_location",
512
+ message: `No --output was given, so '${a[0]}' was written to '${result.path}' in the current directory. `
513
+ + "Pass --output PATH to control the destination and avoid writing into your working tree.",
514
+ dataset: a[0],
515
+ path: result.path,
516
+ });
517
+ }
518
+ return result;
519
+ },
520
+ usage: "dss dataset download <name> [--output PATH] [--limit N] [--raw-data] [--project-key KEY]",
521
+ 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.",
522
+ examples: [
523
+ "dss dataset download orders",
524
+ "dss dataset download orders --output ./data/",
525
+ "dss dataset download orders --raw-data",
526
+ ],
527
+ },
398
528
  clone: {
399
529
  handler: async (c, a, f) => {
400
530
  const usage = "dss dataset clone <source> <target> [--path PATH] [--table TABLE] [--metastore-table TABLE] [--allow-same-path] [--zone ZONE|--zone-id ID] [--dry-run] [--project-key KEY]";
@@ -410,7 +540,7 @@ export const datasetCommands = {
410
540
  const current = await c.datasets.get(a[0], pk);
411
541
  const next = buildDatasetCloneSettings(current, a[1], pk ?? c.resolveProjectKey(pk), opts);
412
542
  const zoneId = await resolveFlowZoneIdFromFlags(c, f, pk);
413
- if (f["dry-run"] === true) {
543
+ if (executionMode(f).dryRun) {
414
544
  return {
415
545
  dryRun: true,
416
546
  action: "clone",
@@ -437,11 +567,11 @@ export const datasetCommands = {
437
567
  handler: async (c, a, f) => {
438
568
  requireArgs(a, 1, "dss dataset delete <name>");
439
569
  const pk = f["project-key"];
440
- if (f["dry-run"] === true || f["if-exists"] === true) {
570
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
441
571
  const current = await readIfExists(() => c.datasets.get(a[0], pk));
442
572
  if (!current)
443
573
  return skipResult("dataset", a[0], "missing");
444
- if (f["dry-run"] === true) {
574
+ if (executionMode(f).dryRun) {
445
575
  return { dryRun: true, action: "delete", resource: "dataset", name: a[0], current, };
446
576
  }
447
577
  }
@@ -460,7 +590,7 @@ export const datasetCommands = {
460
590
  throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss dataset update <name> [--data '{...}' | --data-file PATH | --stdin]");
461
591
  }
462
592
  const pk = f["project-key"];
463
- if (f["dry-run"] === true) {
593
+ if (executionMode(f).dryRun) {
464
594
  const current = await c.datasets.get(a[0], pk);
465
595
  const next = deepMerge(current, data);
466
596
  return { dryRun: true, action: "update", resource: "dataset", name: a[0], current, next, };
@@ -1,7 +1,9 @@
1
- import { runDoctor, } from "../doctor.js";
2
1
  export const doctorCommands = {
3
2
  run: {
4
- handler: async (_c, _a, f) => (await runDoctor(f)).result,
3
+ handler: async (_c, _a, f) => {
4
+ const { runDoctor, } = await import("../doctor.js");
5
+ return (await runDoctor(f)).result;
6
+ },
5
7
  usage: "dss doctor [--project-key KEY] [--capabilities] [--fast]",
6
8
  description: "Run JSON diagnostics for DSS credentials, connectivity, and project access.",
7
9
  examples: ["dss doctor", "dss doctor --project-key MYPROJ", "dss doctor --capabilities --fast",],
@@ -1,5 +1,6 @@
1
1
  import { ClientValidationError, } from "../../errors.js";
2
2
  import { deepMerge, } from "../../utils/deep-merge.js";
3
+ import { executionMode, } from "../flags.js";
3
4
  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";
4
5
  import { readIfExists, skipResult, } from "../output.js";
5
6
  import { requireArgs, UsageError, } from "../usage.js";
@@ -63,13 +64,13 @@ export const flowZoneCommands = {
63
64
  color: flowZoneColor(f["color"]),
64
65
  projectKey: pk,
65
66
  };
66
- if (f["if-not-exists"] === true || f["dry-run"] === true) {
67
+ if (f["if-not-exists"] === true || executionMode(f).dryRun) {
67
68
  const list = await c.flowZones.list(pk);
68
69
  const existing = list.find((zone) => zone.name === name);
69
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
70
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
70
71
  return skipResult("flow-zone", existing.id, "exists", { current: existing, });
71
72
  }
72
- if (f["dry-run"] === true) {
73
+ if (executionMode(f).dryRun) {
73
74
  return {
74
75
  dryRun: true,
75
76
  action: "create",
@@ -103,7 +104,7 @@ export const flowZoneCommands = {
103
104
  color: flowZoneColor(f["color"]),
104
105
  projectKey: pk,
105
106
  };
106
- if (f["dry-run"] === true) {
107
+ if (executionMode(f).dryRun) {
107
108
  const current = await c.flowZones.get(zoneId, pk);
108
109
  const next = deepMerge(current, patch);
109
110
  return { dryRun: true, action: "update", resource: "flow-zone", id: zoneId, current, next, };
@@ -118,11 +119,11 @@ export const flowZoneCommands = {
118
119
  handler: async (c, a, f) => {
119
120
  requireArgs(a, 1, "dss flow-zone delete <id>");
120
121
  const zoneId = flowZoneId(a[0]);
121
- if (f["dry-run"] === true || f["if-exists"] === true) {
122
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
122
123
  const current = await readIfExists(() => c.flowZones.get(zoneId, f["project-key"]));
123
124
  if (!current)
124
125
  return skipResult("flow-zone", zoneId, "missing");
125
- if (f["dry-run"] === true) {
126
+ if (executionMode(f).dryRun) {
126
127
  return { dryRun: true, action: "delete", resource: "flow-zone", id: zoneId, current, };
127
128
  }
128
129
  }
@@ -147,7 +148,7 @@ export const flowZoneCommands = {
147
148
  if (items.length === 0) {
148
149
  throw new UsageError("At least one object is required. Use --dataset, --recipe, --folder, or --object TYPE:ID.");
149
150
  }
150
- if (f["dry-run"] === true) {
151
+ if (executionMode(f).dryRun) {
151
152
  return {
152
153
  dryRun: true,
153
154
  action: "move",
@@ -206,7 +207,7 @@ export const flowZoneCommands = {
206
207
  const pruneItems = Array.isArray(step.pruneItems) ? step.pruneItems : [];
207
208
  return count + pruneItems.length;
208
209
  }, 0);
209
- if (f["dry-run"] === true) {
210
+ if (executionMode(f).dryRun) {
210
211
  return {
211
212
  dryRun: true,
212
213
  action: "organize",
@@ -1,5 +1,6 @@
1
1
  import { deepMerge, } from "../../utils/deep-merge.js";
2
2
  import { jsonInput, } from "../coerce.js";
3
+ import { executionMode, } from "../flags.js";
3
4
  import { addTransientTargetContext, encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
4
5
  import { requireArgs, UsageError, } from "../usage.js";
5
6
  async function resolveFolderId(client, nameOrId, flags) {
@@ -28,13 +29,13 @@ export const folderCommands = {
28
29
  path: f["path"],
29
30
  projectKey: pk,
30
31
  };
31
- if (f["if-not-exists"] === true || f["dry-run"] === true) {
32
+ if (f["if-not-exists"] === true || executionMode(f).dryRun) {
32
33
  const list = await c.folders.list(pk);
33
34
  const existing = list.find((folder) => folder.name === name);
34
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
35
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
35
36
  return skipResult("folder", existing.id ?? name, "exists", { current: existing, });
36
37
  }
37
- if (f["dry-run"] === true) {
38
+ if (executionMode(f).dryRun) {
38
39
  return {
39
40
  dryRun: true,
40
41
  action: "create",
@@ -74,7 +75,7 @@ export const folderCommands = {
74
75
  }
75
76
  const pk = f["project-key"];
76
77
  const folderId = await resolveFolderId(c, a[0], f);
77
- if (f["dry-run"] === true) {
78
+ if (executionMode(f).dryRun) {
78
79
  const current = await c.folders.get(folderId, pk);
79
80
  const next = deepMerge(current, data);
80
81
  return {
@@ -102,11 +103,11 @@ export const folderCommands = {
102
103
  requireArgs(a, 1, "dss folder delete <name-or-id>");
103
104
  const pk = f["project-key"];
104
105
  const folderId = await resolveFolderId(c, a[0], f);
105
- if (f["dry-run"] === true || f["if-exists"] === true) {
106
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
106
107
  const current = await readIfExists(() => c.folders.get(folderId, pk));
107
108
  if (!current)
108
109
  return skipResult("folder", folderId, "missing");
109
- if (f["dry-run"] === true) {
110
+ if (executionMode(f).dryRun) {
110
111
  return {
111
112
  dryRun: true,
112
113
  action: "delete",
@@ -167,7 +168,7 @@ export const folderCommands = {
167
168
  requireArgs(a, 3, "dss folder upload <name-or-id> <path> <localPath>");
168
169
  const pk = f["project-key"];
169
170
  const folderId = await resolveFolderId(c, a[0], f);
170
- if (f["dry-run"] === true) {
171
+ if (executionMode(f).dryRun) {
171
172
  return {
172
173
  dryRun: true,
173
174
  action: "upload",
@@ -190,7 +191,7 @@ export const folderCommands = {
190
191
  "delete-file": {
191
192
  handler: async (c, a, f) => {
192
193
  requireArgs(a, 2, "dss folder delete-file <name-or-id> <path>");
193
- if (f["dry-run"] === true) {
194
+ if (executionMode(f).dryRun) {
194
195
  return { dryRun: true, action: "delete-file", resource: "folder", folder: a[0], path: a[1], };
195
196
  }
196
197
  await c.folders.deleteFile(await resolveFolderId(c, a[0], f), a[1], f["project-key"]);
@@ -1,4 +1,5 @@
1
1
  import { num, } from "../coerce.js";
2
+ import { executionMode, } from "../flags.js";
2
3
  import { planResult, } from "../output.js";
3
4
  import { requireArgs, } from "../usage.js";
4
5
  export const futureCommands = {
@@ -36,10 +37,10 @@ export const futureCommands = {
36
37
  handler: async (c, a, f) => {
37
38
  requireArgs(a, 1, "dss future abort <id>");
38
39
  const id = a[0];
39
- if (f["dry-run"] === true) {
40
+ if (executionMode(f).dryRun) {
40
41
  return planResult("future", "abort", {
41
- method: "POST",
42
- endpoint: `/public/api/futures/${encodeURIComponent(id)}/abort`,
42
+ method: "DELETE",
43
+ endpoint: `/public/api/futures/${encodeURIComponent(id)}`,
43
44
  identifiers: { id, },
44
45
  idempotency: "none",
45
46
  asyncKind: "future",
@@ -0,0 +1,2 @@
1
+ import type { CommandMeta } from "../types.js";
2
+ export declare const groupCommands: Record<string, CommandMeta>;
@@ -0,0 +1,109 @@
1
+ import { requiredJsonInput, } from "../coerce.js";
2
+ import { executionMode, } from "../flags.js";
3
+ import { readIfExists, skipResult, } from "../output.js";
4
+ import { requireArgs, UsageError, } from "../usage.js";
5
+ /** Group identifiers must be non-empty before any request or dry-run preview. */
6
+ function requireGroupName(value, usage) {
7
+ const trimmed = value?.trim() ?? "";
8
+ if (trimmed.length === 0) {
9
+ throw new UsageError(`group name must be a non-empty string.\nUsage: ${usage}`, "validation_failed");
10
+ }
11
+ return trimmed;
12
+ }
13
+ export const groupCommands = {
14
+ list: {
15
+ handler: (c) => c.groups.list(),
16
+ usage: "dss group list",
17
+ description: "List DSS groups (admin).",
18
+ examples: ["dss group list",],
19
+ },
20
+ get: {
21
+ handler: (c, a) => {
22
+ const usage = "dss group get <name>";
23
+ requireArgs(a, 1, usage);
24
+ return c.groups.get(requireGroupName(a[0], usage));
25
+ },
26
+ usage: "dss group get <name>",
27
+ description: "Get a DSS group (admin).",
28
+ examples: ["dss group get administrators",],
29
+ },
30
+ create: {
31
+ handler: async (c, _a, f) => {
32
+ const usage = "dss group create (--data JSON|--data-file PATH|--stdin)";
33
+ const body = requiredJsonInput(f, `--data, --data-file, or --stdin is required (group definition). Usage: ${usage}`);
34
+ if (executionMode(f).dryRun) {
35
+ return {
36
+ dryRun: true,
37
+ action: "create",
38
+ resource: "group",
39
+ name: typeof body.name === "string" ? body.name : undefined,
40
+ method: "POST",
41
+ endpoint: "/public/api/admin/groups",
42
+ payload: body,
43
+ };
44
+ }
45
+ return c.groups.create(body);
46
+ },
47
+ usage: "dss group create (--data JSON|--data-file PATH|--stdin) [--dry-run]",
48
+ description: 'Create a DSS group (admin). Body follows the DSS Group schema, e.g. {"name":"analysts","admin":false,"description":"..."}.',
49
+ examples: [
50
+ `dss group create --data '{"name":"analysts","admin":false,"description":"Business analysts"}'`,
51
+ ],
52
+ },
53
+ update: {
54
+ handler: async (c, a, f) => {
55
+ const usage = "dss group update <name> (--data JSON|--data-file PATH|--stdin) [--dry-run]";
56
+ requireArgs(a, 1, usage);
57
+ const name = requireGroupName(a[0], usage);
58
+ const body = requiredJsonInput(f, `--data, --data-file, or --stdin is required (group definition). Usage: ${usage}`);
59
+ if (executionMode(f).dryRun) {
60
+ return {
61
+ dryRun: true,
62
+ action: "update",
63
+ resource: "group",
64
+ name,
65
+ method: "PUT",
66
+ endpoint: `/public/api/admin/groups/${encodeURIComponent(name)}`,
67
+ payload: body,
68
+ note: "PUT replaces the whole Group object; the body must come from `dss group get`. No GET was issued in dry-run.",
69
+ };
70
+ }
71
+ await c.groups.update(name, body);
72
+ return { updated: name, };
73
+ },
74
+ usage: "dss group update <name> (--data JSON|--data-file PATH|--stdin) [--dry-run]",
75
+ description: "Update a DSS group (admin). The body MUST be the Group object obtained from `dss group get` (PUT semantics); pass undocumented attributes through unchanged. Use --dry-run to preview the merged result without writing.",
76
+ examples: [
77
+ `dss group update analysts --data '{"name":"ANALYSTS","admin":false,"description":"Business analysts"}'`,
78
+ ],
79
+ },
80
+ delete: {
81
+ handler: async (c, a, f) => {
82
+ const usage = "dss group delete <name> [--if-exists] [--dry-run]";
83
+ requireArgs(a, 1, usage);
84
+ const name = requireGroupName(a[0], usage);
85
+ if (executionMode(f).dryRun) {
86
+ return {
87
+ dryRun: true,
88
+ action: "delete",
89
+ resource: "group",
90
+ name,
91
+ method: "DELETE",
92
+ endpoint: `/public/api/admin/groups/${encodeURIComponent(name)}`,
93
+ ...(f["if-exists"] === true
94
+ ? { note: "Existence not checked in dry-run; live --if-exists issues a GET probe first.", }
95
+ : {}),
96
+ };
97
+ }
98
+ if (f["if-exists"] === true) {
99
+ const current = await readIfExists(() => c.groups.get(name));
100
+ if (!current)
101
+ return skipResult("group", name, "missing");
102
+ }
103
+ return c.groups.delete(name);
104
+ },
105
+ usage: "dss group delete <name> [--if-exists] [--dry-run]",
106
+ description: "Delete a DSS group (admin). Destructive and irreversible.",
107
+ examples: ["dss group delete analysts", "dss group delete analysts --if-exists",],
108
+ },
109
+ };