dataiku-sdk 3.3.0 → 3.5.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 (126) hide show
  1. package/README.md +6 -4
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/src/cli/command-syntax.json +14524 -0
  4. package/dist/src/cli/commands/analysis.js +6 -9
  5. package/dist/src/cli/commands/api-deployer.js +15 -33
  6. package/dist/src/cli/commands/api-service.js +17 -27
  7. package/dist/src/cli/commands/app.js +22 -39
  8. package/dist/src/cli/commands/bundle.js +15 -24
  9. package/dist/src/cli/commands/business-app.js +10 -17
  10. package/dist/src/cli/commands/code-env.js +23 -36
  11. package/dist/src/cli/commands/code.js +5 -5
  12. package/dist/src/cli/commands/connection.js +12 -22
  13. package/dist/src/cli/commands/continuous-activity.js +6 -9
  14. package/dist/src/cli/commands/dashboard.js +8 -13
  15. package/dist/src/cli/commands/data-collection.js +9 -16
  16. package/dist/src/cli/commands/data-quality.js +14 -26
  17. package/dist/src/cli/commands/dataset.js +28 -51
  18. package/dist/src/cli/commands/discussion.js +7 -10
  19. package/dist/src/cli/commands/doctor.js +3 -3
  20. package/dist/src/cli/commands/flow-zone.js +8 -17
  21. package/dist/src/cli/commands/folder.js +12 -20
  22. package/dist/src/cli/commands/future.js +7 -10
  23. package/dist/src/cli/commands/group.js +7 -11
  24. package/dist/src/cli/commands/insight.js +7 -11
  25. package/dist/src/cli/commands/job.js +17 -25
  26. package/dist/src/cli/commands/knowledge-bank.js +5 -6
  27. package/dist/src/cli/commands/llm.js +5 -8
  28. package/dist/src/cli/commands/macro.js +11 -18
  29. package/dist/src/cli/commands/meaning.js +7 -11
  30. package/dist/src/cli/commands/metrics.js +7 -10
  31. package/dist/src/cli/commands/ml-task.js +12 -20
  32. package/dist/src/cli/commands/model-evaluation-store.js +7 -11
  33. package/dist/src/cli/commands/notebook.js +15 -27
  34. package/dist/src/cli/commands/plugin.js +35 -68
  35. package/dist/src/cli/commands/project-deployer.js +10 -22
  36. package/dist/src/cli/commands/project-folder.js +10 -17
  37. package/dist/src/cli/commands/project-git.js +39 -71
  38. package/dist/src/cli/commands/project-library.js +15 -23
  39. package/dist/src/cli/commands/project.js +9 -26
  40. package/dist/src/cli/commands/recipe.d.ts +16 -0
  41. package/dist/src/cli/commands/recipe.js +83 -94
  42. package/dist/src/cli/commands/saved-model.js +23 -39
  43. package/dist/src/cli/commands/scenario.js +21 -35
  44. package/dist/src/cli/commands/sql.js +4 -4
  45. package/dist/src/cli/commands/statistics.js +11 -18
  46. package/dist/src/cli/commands/streaming-endpoint.js +7 -11
  47. package/dist/src/cli/commands/user.js +11 -22
  48. package/dist/src/cli/commands/variable.js +3 -4
  49. package/dist/src/cli/commands/webapp.js +10 -16
  50. package/dist/src/cli/commands/wiki.js +9 -13
  51. package/dist/src/cli/commands/workspace.js +8 -14
  52. package/dist/src/cli/contract.d.ts +5 -15
  53. package/dist/src/cli/contract.js +179 -461
  54. package/dist/src/cli/doctor.js +1 -0
  55. package/dist/src/cli/env.d.ts +2 -0
  56. package/dist/src/cli/env.js +15 -1
  57. package/dist/src/cli/helpers/app-permissions.js +2 -1
  58. package/dist/src/cli/helpers/flow-zone.js +2 -1
  59. package/dist/src/cli/helpers/recipe.js +2 -1
  60. package/dist/src/cli/output-schemas.d.ts +24 -0
  61. package/dist/src/cli/output-schemas.js +233 -0
  62. package/dist/src/cli/syntax.d.ts +83 -0
  63. package/dist/src/cli/syntax.js +217 -0
  64. package/dist/src/cli/types.d.ts +3 -0
  65. package/dist/src/cli/usage.d.ts +1 -1
  66. package/dist/src/cli/usage.js +4 -2
  67. package/dist/src/cli/version.js +2 -0
  68. package/dist/src/cli.js +37 -64
  69. package/dist/src/client.d.ts +10 -1
  70. package/dist/src/client.js +27 -11
  71. package/dist/src/errors.d.ts +12 -1
  72. package/dist/src/errors.js +34 -7
  73. package/dist/src/generated/action-output-schemas.json +1735 -0
  74. package/dist/src/index.d.ts +1 -3
  75. package/dist/src/index.js +2 -2
  76. package/dist/src/resources/api-services.js +2 -3
  77. package/dist/src/resources/applications.js +2 -4
  78. package/dist/src/resources/code-envs.js +2 -2
  79. package/dist/src/resources/data-quality.js +4 -3
  80. package/dist/src/resources/datasets.d.ts +13 -0
  81. package/dist/src/resources/datasets.js +42 -8
  82. package/dist/src/resources/flow-zones.d.ts +6 -2
  83. package/dist/src/resources/flow-zones.js +10 -5
  84. package/dist/src/resources/folders.js +4 -2
  85. package/dist/src/resources/futures.d.ts +2 -0
  86. package/dist/src/resources/futures.js +39 -27
  87. package/dist/src/resources/insights.js +3 -2
  88. package/dist/src/resources/jobs.js +41 -31
  89. package/dist/src/resources/macros.js +2 -4
  90. package/dist/src/resources/ml-tasks.js +28 -19
  91. package/dist/src/resources/notebooks.js +2 -3
  92. package/dist/src/resources/plugins.d.ts +0 -2
  93. package/dist/src/resources/plugins.js +0 -3
  94. package/dist/src/resources/project-git.d.ts +6 -12
  95. package/dist/src/resources/project-git.js +31 -58
  96. package/dist/src/resources/project-library.d.ts +0 -2
  97. package/dist/src/resources/project-library.js +2 -7
  98. package/dist/src/resources/projects.d.ts +4 -2
  99. package/dist/src/resources/projects.js +37 -36
  100. package/dist/src/resources/recipes.d.ts +28 -0
  101. package/dist/src/resources/recipes.js +116 -84
  102. package/dist/src/resources/sql.js +8 -3
  103. package/dist/src/resources/statistics.js +2 -1
  104. package/dist/src/resources/variables.js +2 -1
  105. package/dist/src/resources/webapps.js +2 -3
  106. package/dist/src/schemas.d.ts +1 -2
  107. package/dist/src/schemas.js +1 -1
  108. package/dist/src/skill.js +6 -7
  109. package/dist/src/utils/cleanup-ledger.js +9 -8
  110. package/dist/src/utils/git-reference.d.ts +12 -0
  111. package/dist/src/utils/git-reference.js +38 -0
  112. package/dist/src/utils/polling.d.ts +3 -0
  113. package/dist/src/utils/polling.js +3 -0
  114. package/dist/src/utils/project-archive.js +2 -2
  115. package/dist/src/utils/stable-hash.d.ts +3 -1
  116. package/dist/src/utils/stable-hash.js +2 -0
  117. package/package.json +8 -8
  118. package/plugin.json +1 -1
  119. package/skills/dataiku-dss/SKILL.md +3 -2
  120. package/skills/dataiku-dss/references/app-releases.md +2 -3
  121. package/skills/dataiku-dss/references/authentication.md +7 -7
  122. package/skills/dataiku-dss/references/coding.md +1 -1
  123. package/skills/dataiku-dss/references/mutations.md +1 -1
  124. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
  125. package/dist/src/utils/pagination.d.ts +0 -8
  126. package/dist/src/utils/pagination.js +0 -23
@@ -29,6 +29,7 @@ export function firstStringField(items, fields) {
29
29
  return null;
30
30
  }
31
31
  export function integrationFlag(name) {
32
+ // oxlint-disable-next-line dss/no-direct-process-env -- RUN_DATAIKU_* integration opt-in flags
32
33
  const value = process.env[name];
33
34
  return value === "1" || value?.toLowerCase() === "true";
34
35
  }
@@ -8,4 +8,6 @@ export type EnvFileProvenance = "project" | "install";
8
8
  */
9
9
  export declare function envFileProvenance(name: string): EnvFileProvenance | undefined;
10
10
  export declare function dataikuEnvironmentEnabled(): boolean;
11
+ /** DATAIKU_PROJECT_KEY from the environment, unless `DATAIKU_DISABLE_ENV=1`. */
12
+ export declare function ambientProjectKey(): string | undefined;
11
13
  export declare function loadEnvFile(): void;
@@ -20,6 +20,20 @@ export function envFileProvenance(name) {
20
20
  export function dataikuEnvironmentEnabled() {
21
21
  return process.env.DATAIKU_DISABLE_ENV !== "1";
22
22
  }
23
+ /** DATAIKU_PROJECT_KEY from the environment, unless `DATAIKU_DISABLE_ENV=1`. */
24
+ export function ambientProjectKey() {
25
+ return dataikuEnvironmentEnabled() ? process.env.DATAIKU_PROJECT_KEY : undefined;
26
+ }
27
+ const allowedEnvKeys = new Set(["NODE_EXTRA_CA_CERTS", "NODE_TLS_REJECT_UNAUTHORIZED",]);
28
+ /**
29
+ * `.env` files are untrusted input: only the CLI's own `DATAIKU_*` variables and
30
+ * Node's TLS trust knobs are read. Every other key is ignored silently, so a
31
+ * project working tree cannot redirect the CLI (for example by smuggling in
32
+ * `DSS_CONFIG_DIR` and sending saved credentials elsewhere).
33
+ */
34
+ function isAllowedEnvKey(key) {
35
+ return key.startsWith("DATAIKU_") || allowedEnvKeys.has(key);
36
+ }
23
37
  export function loadEnvFile() {
24
38
  if (!dataikuEnvironmentEnabled())
25
39
  return;
@@ -42,7 +56,7 @@ export function loadEnvFile() {
42
56
  continue;
43
57
  const key = trimmed.slice(0, eq).trim();
44
58
  const val = trimmed.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
45
- if (!process.env[key]) {
59
+ if (isAllowedEnvKey(key) && !process.env[key]) {
46
60
  process.env[key] = val;
47
61
  (index === 0 ? projectEnvVars : installEnvVars).add(key);
48
62
  }
@@ -1,4 +1,5 @@
1
1
  import * as fs from "node:fs";
2
+ import { ClientValidationError, } from "../../errors.js";
2
3
  import { canonicalDssUrl, } from "../../utils/dss-url.js";
3
4
  import { compareStrings, } from "../../utils/stable-hash.js";
4
5
  import { parseJsonObject, plainRecord, stableHash, stableJson, } from "../coerce.js";
@@ -61,7 +62,7 @@ function writeAllSync(fd, payload) {
61
62
  while (offset < payload.length) {
62
63
  const written = fs.writeSync(fd, payload, offset, payload.length - offset, offset);
63
64
  if (written <= 0) {
64
- throw new Error(`Stalled writing app permissions snapshot at byte ${offset}.`);
65
+ throw new ClientValidationError(`Stalled writing app permissions snapshot at byte ${offset}.`, "internal_error");
65
66
  }
66
67
  offset += written;
67
68
  }
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, } from "node:fs";
2
2
  import { compareStrings, } from "../../utils/stable-hash.js";
3
3
  import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, plainRecord, requiredStringArray, splitCsvFlag, } from "../coerce.js";
4
+ import { commandUsage, } from "../syntax.js";
4
5
  import { UsageError, } from "../usage.js";
5
6
  export function flowZoneId(value) {
6
7
  const trimmed = value.trim();
@@ -10,7 +11,7 @@ export function flowZoneId(value) {
10
11
  }
11
12
  export function flowZoneName(value) {
12
13
  if (typeof value !== "string" || value.trim().length === 0) {
13
- throw new UsageError("--name is required. Usage: dss flow-zone create --name NAME [--color #RRGGBB]");
14
+ throw new UsageError(`--name is required. Usage: ${commandUsage("flow-zone", "create")}`);
14
15
  }
15
16
  return value.trim();
16
17
  }
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import { tmpdir, } from "node:os";
3
3
  import { join, parse as parsePath, relative as relativePath, resolve as resolvePath, sep, } from "node:path";
4
+ import { ClientValidationError, } from "../../errors.js";
4
5
  import { sanitizeFileName, } from "../../utils/sanitize.js";
5
6
  import { normalizeLineEndings, sha256Hex, stableHash, } from "../coerce.js";
6
7
  import { UsageError, } from "../usage.js";
@@ -126,7 +127,7 @@ function writeAllSync(fd, payload) {
126
127
  while (offset < payload.length) {
127
128
  const written = fs.writeSync(fd, payload, offset, payload.length - offset, offset);
128
129
  if (written <= 0) {
129
- throw new Error(`Stalled writing recipe backup at byte ${offset}.`);
130
+ throw new ClientValidationError(`Stalled writing recipe backup at byte ${offset}.`, "internal_error");
130
131
  }
131
132
  offset += written;
132
133
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Canonical discovery output schemas, keyed by `resource.action`.
3
+ *
4
+ * TypeBox definitions from `packages/types` plus the inline literal schemas
5
+ * that exist only on the CLI side (code-env log envelope, code.run outcome,
6
+ * project-library payloads, notebook saves, sql.query handoff envelope).
7
+ * `scripts/generate-action-output-schemas.mjs` serializes this map into
8
+ * `src/generated/action-output-schemas.json`; discovery (src/cli/contract.ts)
9
+ * reads that generated JSON as plain data, so the TypeBox schema graph never
10
+ * loads on the discovery path. SDK validation keeps using the TypeBox schema
11
+ * objects from `packages/types` directly.
12
+ *
13
+ * Imported by the generator and the discovery parity test only.
14
+ */
15
+ /**
16
+ * Canonical TypeBox-derived output schemas, keyed by `resource.action`. This is
17
+ * the single source of truth for `src/generated/action-output-schemas.json`,
18
+ * which discovery reads as plain data so no TypeBox schema graph loads on the
19
+ * discovery path. Regenerate with
20
+ * `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
21
+ * the generated file is stale. SDK validation keeps using the TypeBox schema
22
+ * objects from `packages/types` directly and never depends on the generated file.
23
+ */
24
+ export declare function typeBoxCommandOutputSchemas(): Record<string, Record<string, unknown>>;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Canonical discovery output schemas, keyed by `resource.action`.
3
+ *
4
+ * TypeBox definitions from `packages/types` plus the inline literal schemas
5
+ * that exist only on the CLI side (code-env log envelope, code.run outcome,
6
+ * project-library payloads, notebook saves, sql.query handoff envelope).
7
+ * `scripts/generate-action-output-schemas.mjs` serializes this map into
8
+ * `src/generated/action-output-schemas.json`; discovery (src/cli/contract.ts)
9
+ * reads that generated JSON as plain data, so the TypeBox schema graph never
10
+ * loads on the discovery path. SDK validation keeps using the TypeBox schema
11
+ * objects from `packages/types` directly.
12
+ *
13
+ * Imported by the generator and the discovery parity test only.
14
+ */
15
+ const PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA = {
16
+ type: "object",
17
+ additionalProperties: false,
18
+ required: ["name",],
19
+ properties: {
20
+ name: { type: "string", },
21
+ path: { type: "string", },
22
+ size: { type: "integer", minimum: 0, },
23
+ mimeType: { type: "string", },
24
+ hasData: { type: "boolean", },
25
+ lastModified: { type: "number", },
26
+ children: { type: "array", items: { type: "object", additionalProperties: true, }, },
27
+ },
28
+ };
29
+ const NOTEBOOK_SAVE_OUTPUT_SCHEMA = {
30
+ type: "object",
31
+ additionalProperties: false,
32
+ required: ["saved", "resource", "created", "hash",],
33
+ properties: {
34
+ saved: { type: "string", },
35
+ resource: { enum: ["jupyter-notebook", "sql-notebook",], },
36
+ created: { type: "boolean", },
37
+ hash: { type: "string", pattern: "^[a-f0-9]{64}$", },
38
+ },
39
+ };
40
+ /**
41
+ * `notebook.save-sql` adds `requested`: on create DSS allocates the persisted
42
+ * id (`saved`), while the requested handle only becomes the display name.
43
+ */
44
+ const NOTEBOOK_SAVE_SQL_OUTPUT_SCHEMA = {
45
+ ...NOTEBOOK_SAVE_OUTPUT_SCHEMA,
46
+ required: ["saved", "requested", "resource", "created", "hash",],
47
+ properties: {
48
+ ...NOTEBOOK_SAVE_OUTPUT_SCHEMA["properties"],
49
+ requested: { type: "string", },
50
+ },
51
+ };
52
+ let commandOutputSchemas;
53
+ /**
54
+ * Canonical TypeBox-derived output schemas, keyed by `resource.action`. This is
55
+ * the single source of truth for `src/generated/action-output-schemas.json`,
56
+ * which discovery reads as plain data so no TypeBox schema graph loads on the
57
+ * discovery path. Regenerate with
58
+ * `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
59
+ * the generated file is stale. SDK validation keeps using the TypeBox schema
60
+ * objects from `packages/types` directly and never depends on the generated file.
61
+ */
62
+ export function typeBoxCommandOutputSchemas() {
63
+ if (commandOutputSchemas)
64
+ return commandOutputSchemas;
65
+ const { CodeEnvDetailsSchema, CodeEnvLogSummaryArraySchema, CodeEnvSummaryArraySchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, FlowZoneArraySchema, FlowZoneSchema, JobSummaryArraySchema, JobWaitResultSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, NotebookSessionArraySchema, ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, RecipeSummaryArraySchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlQueryResponseSchema, } = require("../schemas.js");
66
+ return commandOutputSchemas = {
67
+ "code-env.list": CodeEnvSummaryArraySchema,
68
+ "code-env.get": CodeEnvDetailsSchema,
69
+ "code-env.list-logs": CodeEnvLogSummaryArraySchema,
70
+ "code-env.get-log": {
71
+ oneOf: [
72
+ {
73
+ type: "object",
74
+ required: ["log", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
75
+ properties: {
76
+ log: { type: "string", },
77
+ bytes: { type: "integer", minimum: 0, },
78
+ truncated: { type: "boolean", },
79
+ tailed: { type: "boolean", },
80
+ envLang: { enum: ["PYTHON", "R",], },
81
+ envName: { type: "string", },
82
+ logName: { type: "string", },
83
+ },
84
+ },
85
+ {
86
+ type: "object",
87
+ required: ["path", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
88
+ properties: {
89
+ path: { type: "string", },
90
+ bytes: { type: "integer", minimum: 0, },
91
+ truncated: { type: "boolean", },
92
+ tailed: { type: "boolean", },
93
+ envLang: { enum: ["PYTHON", "R",], },
94
+ envName: { type: "string", },
95
+ logName: { type: "string", },
96
+ },
97
+ },
98
+ ],
99
+ },
100
+ "code-env.version": CodeEnvVersionForProjectSchema,
101
+ "code-env.usages": CodeEnvUsageArraySchema,
102
+ "code.run": {
103
+ type: "object",
104
+ additionalProperties: false,
105
+ required: [
106
+ "outcome",
107
+ "success",
108
+ "runId",
109
+ "elapsedMs",
110
+ "pollCount",
111
+ "output",
112
+ "logTruncated",
113
+ "maxLogBytes",
114
+ "cleanup",
115
+ ],
116
+ properties: {
117
+ outcome: { type: "string", },
118
+ success: { type: "boolean", },
119
+ runId: { type: "string", },
120
+ elapsedMs: { type: "number", minimum: 0, },
121
+ pollCount: { type: "integer", minimum: 0, },
122
+ output: { type: "string", },
123
+ log: { type: "string", },
124
+ logTruncated: { type: "boolean", },
125
+ maxLogBytes: { type: "integer", minimum: 0, },
126
+ timedOut: { const: true, },
127
+ timeoutMs: { type: "integer", minimum: 0, },
128
+ cleanup: {
129
+ type: "object",
130
+ additionalProperties: false,
131
+ required: ["status",],
132
+ properties: {
133
+ status: { enum: ["deleted", "kept", "failed",], },
134
+ error: { type: "string", },
135
+ },
136
+ },
137
+ },
138
+ },
139
+ "notebook.list-jupyter": JupyterNotebookSummaryArraySchema,
140
+ "notebook.get-jupyter": JupyterNotebookContentSchema,
141
+ "notebook.sessions-jupyter": NotebookSessionArraySchema,
142
+ "notebook.list-sql": SqlNotebookSummaryArraySchema,
143
+ "notebook.get-sql": SqlNotebookContentSchema,
144
+ "notebook.history-sql": SqlNotebookHistorySchema,
145
+ "notebook.save-jupyter": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
146
+ "notebook.save-sql": NOTEBOOK_SAVE_SQL_OUTPUT_SCHEMA,
147
+ "project-library.list": { type: "array", items: PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA, },
148
+ "project-library.get-bytes": {
149
+ type: "object",
150
+ additionalProperties: false,
151
+ required: ["path", "bytes", "sha256",],
152
+ properties: {
153
+ path: { type: "string", },
154
+ bytes: { type: "integer", minimum: 0, },
155
+ sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
156
+ },
157
+ },
158
+ "project-library.put": {
159
+ type: "object",
160
+ additionalProperties: false,
161
+ required: ["updated", "bytes", "sha256",],
162
+ properties: {
163
+ updated: { type: "string", },
164
+ bytes: { type: "integer", minimum: 0, },
165
+ sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
166
+ beforeSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
167
+ },
168
+ },
169
+ "project-library.diff": {
170
+ type: "object",
171
+ additionalProperties: false,
172
+ required: [
173
+ "path",
174
+ "unchanged",
175
+ "added",
176
+ "removed",
177
+ "diff",
178
+ "diffTruncated",
179
+ "localSha256",
180
+ "localBytes",
181
+ "maxLines",
182
+ ],
183
+ properties: {
184
+ path: { type: "string", },
185
+ unchanged: { type: "boolean", },
186
+ added: { type: "integer", minimum: 0, },
187
+ removed: { type: "integer", minimum: 0, },
188
+ diff: { type: "string", },
189
+ diffTruncated: { type: "boolean", },
190
+ binary: { type: "boolean", },
191
+ remoteAbsent: { type: "boolean", },
192
+ remoteSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
193
+ remoteBytes: { type: "integer", minimum: 0, },
194
+ localSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
195
+ localBytes: { type: "integer", minimum: 0, },
196
+ maxLines: { type: "integer", minimum: 0, },
197
+ },
198
+ },
199
+ "project.list": ProjectSummaryArraySchema,
200
+ "project.get": ProjectDetailsSchema,
201
+ "project.metadata": ProjectMetadataSchema,
202
+ "dataset.list": DatasetSummaryArraySchema,
203
+ "dataset.get": DatasetDetailsSchema,
204
+ "dataset.schema": DatasetSchemaSchema,
205
+ "recipe.list": RecipeSummaryArraySchema,
206
+ "job.list": JobSummaryArraySchema,
207
+ "job.wait": JobWaitResultSchema,
208
+ "job.monitor": JobWaitResultSchema,
209
+ "scenario.list": ScenarioSummaryArraySchema,
210
+ "scenario.get": ScenarioDetailsSchema,
211
+ "scenario.status": ScenarioStatusSchema,
212
+ "flow-zone.list": FlowZoneArraySchema,
213
+ "flow-zone.get": FlowZoneSchema,
214
+ "sql.query": {
215
+ anyOf: [
216
+ SqlQueryResponseSchema,
217
+ {
218
+ type: "object",
219
+ required: ["queryId", "rowCount", "preview",],
220
+ additionalProperties: true,
221
+ properties: {
222
+ queryId: { type: "string", },
223
+ rowCount: { type: "number", },
224
+ preview: { type: "array", items: true, },
225
+ truncated: { type: "boolean", },
226
+ outputPath: { type: "string", },
227
+ written: { type: "string", },
228
+ },
229
+ },
230
+ ],
231
+ },
232
+ };
233
+ }
@@ -0,0 +1,83 @@
1
+ import type { CommandDefinition, CommandFlagChoice, CommandMeta } from "./types.js";
2
+ /**
3
+ * One element of a command's usage line. `src/cli/command-syntax.json` holds an
4
+ * ordered node list per command; it is the source of truth for the usage text
5
+ * (rendered by {@link commandUsage}) and for every usage-level contract field
6
+ * (derived by {@link commandSyntax}).
7
+ */
8
+ export type SyntaxNode =
9
+ /** Literal word: `dss`, the resource, the action. */
10
+ string
11
+ /** Positional argument, rendered `<name>` (or bare `name` when `bare`). */
12
+ | {
13
+ arg: string;
14
+ bare?: boolean;
15
+ }
16
+ /** Flag, rendered `--flag`, `--flag VALUE`, or `--flag a|b` (enum), plus an optional trailing note. */
17
+ | {
18
+ flag: string;
19
+ value?: string;
20
+ values?: string[];
21
+ note?: string;
22
+ }
23
+ /** Optional group `[a|b]`; each alternative is a node sequence. */
24
+ | {
25
+ optional: SyntaxNode[][];
26
+ sep?: string;
27
+ }
28
+ /** Required choice `(a|b)`; each alternative is a node sequence. */
29
+ | {
30
+ oneOf: SyntaxNode[][];
31
+ sep?: string;
32
+ };
33
+ export type FlagNode = Extract<SyntaxNode, {
34
+ flag: string;
35
+ }>;
36
+ export interface CommandPositionalMetadata {
37
+ name: string;
38
+ required: boolean;
39
+ }
40
+ /** Input channels a command accepts for its JSON payload. */
41
+ export interface CommandInputContract {
42
+ stdin?: boolean;
43
+ dataFlag?: boolean;
44
+ dataFileFlag?: boolean;
45
+ }
46
+ /** Usage-level contract fields derived from a command's syntax tree. */
47
+ export interface CommandSyntax {
48
+ /** Canonical names of the flags the usage line lists, in order. */
49
+ flags: string[];
50
+ requiredFlags: string[];
51
+ requiredOneOf: CommandFlagChoice[];
52
+ valueHints: Record<string, {
53
+ valueType: string;
54
+ enumValues?: string[];
55
+ }>;
56
+ inputContract: CommandInputContract;
57
+ positionalArguments: CommandPositionalMetadata[];
58
+ /** Alias spellings (canonical name -> raw names) the usage line uses. */
59
+ aliases: Record<string, string[]>;
60
+ dryRun: boolean;
61
+ producesLocalFile: boolean;
62
+ }
63
+ /** The syntax tree for `resource.action`, or undefined when no such command exists. */
64
+ export declare function commandSyntaxTree(resource: string, action: string): SyntaxNode[] | undefined;
65
+ export declare function commandSyntaxKeys(): string[];
66
+ /** Render a node sequence as usage text; `omitOptional` drops every `[...]` group. */
67
+ export declare function renderNodes(nodes: SyntaxNode[], omitOptional?: boolean): string;
68
+ /** The human usage line for `resource.action`, rendered from its syntax tree. */
69
+ export declare function commandUsage(resource: string, action: string): string;
70
+ /**
71
+ * Attach each action's usage line, rendered from its syntax tree. Every command
72
+ * module exports its definitions through this, so a command without a syntax
73
+ * entry fails at load instead of shipping an undocumented contract.
74
+ */
75
+ export declare function withUsage(resource: string, definitions: Record<string, CommandDefinition>): Record<string, CommandMeta>;
76
+ /** Whether the syntax lists `--name` (the raw spelling, not an alias). */
77
+ export declare function syntaxHasFlag(nodes: SyntaxNode[], name: string): boolean;
78
+ /** Leading literal words after `dss` (resource and, when present, action). */
79
+ export declare function syntaxCommandWords(nodes: SyntaxNode[]): string[];
80
+ /** Usage-level contract fields of a syntax tree (flags, requirements, value hints, positionals, inputs). */
81
+ export declare function deriveSyntax(nodes: SyntaxNode[]): CommandSyntax;
82
+ /** Usage-level contract fields for `resource.action`, derived from its syntax tree. */
83
+ export declare function commandSyntax(resource: string, action: string): CommandSyntax;
@@ -0,0 +1,217 @@
1
+ import { ClientValidationError, } from "../errors.js";
2
+ import commandSyntaxSource from "./command-syntax.json" with { type: "json" };
3
+ import { FLAG_ALIASES, KNOWN_LONG_FLAGS, } from "./flags.js";
4
+ const SOURCE = commandSyntaxSource.commands;
5
+ /** The syntax tree for `resource.action`, or undefined when no such command exists. */
6
+ export function commandSyntaxTree(resource, action) {
7
+ return Object.hasOwn(SOURCE, `${resource}.${action}`)
8
+ ? SOURCE[`${resource}.${action}`]
9
+ : undefined;
10
+ }
11
+ function requiredSyntaxTree(resource, action) {
12
+ const nodes = commandSyntaxTree(resource, action);
13
+ if (!nodes) {
14
+ throw new ClientValidationError(`No syntax for ${resource} ${action} in src/cli/command-syntax.json.`, "internal_error");
15
+ }
16
+ return nodes;
17
+ }
18
+ export function commandSyntaxKeys() {
19
+ return Object.keys(SOURCE);
20
+ }
21
+ function renderNode(node, omitOptional) {
22
+ if (typeof node === "string")
23
+ return node;
24
+ if ("arg" in node)
25
+ return node.bare ? node.arg : `<${node.arg}>`;
26
+ if ("flag" in node) {
27
+ const value = node.values ? node.values.join("|") : node.value;
28
+ return `--${node.flag}${value === undefined ? "" : ` ${value}`}${node.note ? ` ${node.note}` : ""}`;
29
+ }
30
+ if ("optional" in node) {
31
+ return omitOptional ? "" : `[${renderAlternatives(node.optional, node.sep, omitOptional)}]`;
32
+ }
33
+ return `(${renderAlternatives(node.oneOf, node.sep, omitOptional)})`;
34
+ }
35
+ function renderAlternatives(alternatives, sep, omitOptional) {
36
+ return alternatives.map((alternative) => renderNodes(alternative, omitOptional)).join(sep ?? "|");
37
+ }
38
+ /** Render a node sequence as usage text; `omitOptional` drops every `[...]` group. */
39
+ export function renderNodes(nodes, omitOptional = false) {
40
+ return nodes.map((node) => renderNode(node, omitOptional)).filter((part) => part !== "").join(" ");
41
+ }
42
+ const usageCache = new Map();
43
+ /** The human usage line for `resource.action`, rendered from its syntax tree. */
44
+ export function commandUsage(resource, action) {
45
+ const key = `${resource}.${action}`;
46
+ let usage = usageCache.get(key);
47
+ if (usage === undefined) {
48
+ usage = renderNodes(requiredSyntaxTree(resource, action));
49
+ usageCache.set(key, usage);
50
+ }
51
+ return usage;
52
+ }
53
+ /**
54
+ * Attach each action's usage line, rendered from its syntax tree. Every command
55
+ * module exports its definitions through this, so a command without a syntax
56
+ * entry fails at load instead of shipping an undocumented contract.
57
+ */
58
+ export function withUsage(resource, definitions) {
59
+ const commands = {};
60
+ for (const [action, definition,] of Object.entries(definitions)) {
61
+ commands[action] = { ...definition, usage: commandUsage(resource, action), };
62
+ }
63
+ return commands;
64
+ }
65
+ function walk(nodes, visit, context = {
66
+ optional: false,
67
+ oneOf: undefined,
68
+ }) {
69
+ for (const node of nodes) {
70
+ if (typeof node === "string")
71
+ continue;
72
+ visit(node, context);
73
+ if ("optional" in node) {
74
+ for (const alternative of node.optional) {
75
+ walk(alternative, visit, { ...context, optional: true, });
76
+ }
77
+ }
78
+ else if ("oneOf" in node) {
79
+ for (const alternative of node.oneOf) {
80
+ walk(alternative, visit, { ...context, oneOf: node.oneOf, });
81
+ }
82
+ }
83
+ }
84
+ }
85
+ function flagNodes(nodes) {
86
+ const found = [];
87
+ walk(nodes, (node) => {
88
+ if ("flag" in node)
89
+ found.push(node);
90
+ });
91
+ return found;
92
+ }
93
+ /** Whether the syntax lists `--name` (the raw spelling, not an alias). */
94
+ export function syntaxHasFlag(nodes, name) {
95
+ return flagNodes(nodes).some((node) => node.flag === name);
96
+ }
97
+ /** Leading literal words after `dss` (resource and, when present, action). */
98
+ export function syntaxCommandWords(nodes) {
99
+ const words = [];
100
+ for (const node of nodes.slice(1)) {
101
+ if (typeof node !== "string")
102
+ break;
103
+ words.push(node);
104
+ }
105
+ return words;
106
+ }
107
+ function canonicalFlag(name) {
108
+ return FLAG_ALIASES[name] ?? name;
109
+ }
110
+ function knownCanonicalFlags(nodes) {
111
+ return unique(nodes.map((node) => canonicalFlag(node.flag))).filter((flag) => KNOWN_LONG_FLAGS.has(flag));
112
+ }
113
+ function unique(values) {
114
+ return [...new Set(values),];
115
+ }
116
+ /** Flags of a sequence outside its optional groups (a required choice's alternative). */
117
+ function requiredFlagsOf(nodes) {
118
+ const found = [];
119
+ walk(nodes, (node, context) => {
120
+ if ("flag" in node && !context.optional)
121
+ found.push(node);
122
+ });
123
+ return knownCanonicalFlags(found);
124
+ }
125
+ function deriveRequired(nodes) {
126
+ const requiredFlags = knownCanonicalFlags(nodes.filter((node) => typeof node !== "string" && "flag" in node));
127
+ const requiredOneOf = [];
128
+ for (const node of nodes) {
129
+ if (typeof node === "string" || !("oneOf" in node))
130
+ continue;
131
+ const alternatives = node.oneOf.map(requiredFlagsOf);
132
+ if (alternatives.length <= 1) {
133
+ requiredFlags.push(...(alternatives[0] ?? []));
134
+ continue;
135
+ }
136
+ // A choice with a flagless alternative (a positional, e.g. sql query's
137
+ // `(SQL | --sql QUERY | ...)`) cannot be expressed as a flag-only choice:
138
+ // requiring one of the flags would be false. The handler stays the authority.
139
+ if (alternatives.some((alternative) => alternative.length === 0))
140
+ continue;
141
+ requiredOneOf.push({ oneOf: alternatives, });
142
+ }
143
+ return { requiredFlags: unique(requiredFlags), requiredOneOf, };
144
+ }
145
+ const PLACEHOLDER_VALUE = /^(<[^>]+>|[A-Z][A-Za-z0-9_]*)/;
146
+ function deriveValueHints(flags) {
147
+ const hints = {};
148
+ // Enumerations take precedence over a placeholder elsewhere in the line.
149
+ for (const node of flags) {
150
+ const flag = canonicalFlag(node.flag);
151
+ if (node.values && hints[flag] === undefined) {
152
+ hints[flag] = { valueType: "enum", enumValues: node.values, };
153
+ }
154
+ }
155
+ for (const node of flags) {
156
+ const flag = canonicalFlag(node.flag);
157
+ const placeholder = node.value?.match(PLACEHOLDER_VALUE)?.[1];
158
+ if (placeholder && hints[flag] === undefined)
159
+ hints[flag] = { valueType: placeholder, };
160
+ }
161
+ return hints;
162
+ }
163
+ function derivePositionals(nodes) {
164
+ const positionals = [];
165
+ walk(nodes, (node, context) => {
166
+ if (!("arg" in node))
167
+ return;
168
+ const arg = node;
169
+ // A positional inside a required choice that offers a flag instead is optional.
170
+ const flagAlternative = context.oneOf?.some((alternative) => flagNodes(alternative).length > 0)
171
+ ?? false;
172
+ const required = !context.optional && !flagAlternative;
173
+ const existing = positionals.find((positional) => positional.name === arg.arg);
174
+ if (existing)
175
+ existing.required ||= required;
176
+ else
177
+ positionals.push({ name: arg.arg, required, });
178
+ });
179
+ return positionals;
180
+ }
181
+ /** Usage-level contract fields of a syntax tree (flags, requirements, value hints, positionals, inputs). */
182
+ export function deriveSyntax(nodes) {
183
+ const flags = flagNodes(nodes);
184
+ const has = (name) => flags.some((node) => node.flag === name);
185
+ const aliases = {};
186
+ for (const [raw, canonical,] of Object.entries(FLAG_ALIASES)) {
187
+ if (has(raw))
188
+ (aliases[canonical] ??= []).push(raw);
189
+ }
190
+ return {
191
+ flags: knownCanonicalFlags(flags),
192
+ ...deriveRequired(nodes),
193
+ valueHints: deriveValueHints(flags),
194
+ inputContract: {
195
+ ...(has("stdin") ? { stdin: true, } : {}),
196
+ ...(flags.some((node) => node.flag === "data" && (node.value !== undefined || node.values !== undefined))
197
+ ? { dataFlag: true, }
198
+ : {}),
199
+ ...(has("data-file") ? { dataFileFlag: true, } : {}),
200
+ },
201
+ positionalArguments: derivePositionals(nodes),
202
+ aliases,
203
+ dryRun: has("dry-run"),
204
+ producesLocalFile: flags.some((node) => (node.flag === "output" || node.flag === "output-file") && node.value === "PATH"),
205
+ };
206
+ }
207
+ const syntaxCache = new Map();
208
+ /** Usage-level contract fields for `resource.action`, derived from its syntax tree. */
209
+ export function commandSyntax(resource, action) {
210
+ const key = `${resource}.${action}`;
211
+ let syntax = syntaxCache.get(key);
212
+ if (!syntax) {
213
+ syntax = deriveSyntax(requiredSyntaxTree(resource, action));
214
+ syntaxCache.set(key, syntax);
215
+ }
216
+ return syntax;
217
+ }
@@ -28,7 +28,10 @@ export interface CommandMeta extends CommandRegistryOverride {
28
28
  * credentials are configured, instead of being masked by a missing-URL error.
29
29
  */
30
30
  validate?: (args: string[], flags: Record<string, string | boolean>) => void;
31
+ /** Rendered from the command's syntax tree in `src/cli/command-syntax.json`; never hand-written. */
31
32
  usage: string;
32
33
  description?: string;
33
34
  examples?: string[];
34
35
  }
36
+ /** A command as authored: its usage line comes from `src/cli/command-syntax.json` (see `withUsage`). */
37
+ export type CommandDefinition = Omit<CommandMeta, "usage">;