dataiku-sdk 3.4.0 → 3.6.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 +4 -4
- package/bin/dss.js +28 -2
- package/dist/build-metadata.json +1 -1
- package/dist/src/cli/coerce.d.ts +0 -1
- package/dist/src/cli/coerce.js +0 -5
- package/dist/src/cli/command-syntax.json +14524 -0
- package/dist/src/cli/commands/analysis.js +6 -9
- package/dist/src/cli/commands/api-deployer.js +15 -33
- package/dist/src/cli/commands/api-service.js +17 -27
- package/dist/src/cli/commands/app.js +25 -41
- package/dist/src/cli/commands/bundle.js +15 -24
- package/dist/src/cli/commands/business-app.js +10 -17
- package/dist/src/cli/commands/code-env.js +23 -36
- package/dist/src/cli/commands/code.js +4 -4
- package/dist/src/cli/commands/connection.js +12 -22
- package/dist/src/cli/commands/continuous-activity.js +6 -9
- package/dist/src/cli/commands/dashboard.js +8 -13
- package/dist/src/cli/commands/data-collection.js +9 -16
- package/dist/src/cli/commands/data-quality.js +14 -26
- package/dist/src/cli/commands/dataset.js +29 -54
- package/dist/src/cli/commands/discussion.js +7 -10
- package/dist/src/cli/commands/doctor.js +3 -3
- package/dist/src/cli/commands/flow-zone.js +8 -17
- package/dist/src/cli/commands/folder.js +12 -20
- package/dist/src/cli/commands/future.js +7 -10
- package/dist/src/cli/commands/group.js +7 -11
- package/dist/src/cli/commands/insight.js +7 -11
- package/dist/src/cli/commands/job.js +17 -26
- package/dist/src/cli/commands/knowledge-bank.js +5 -6
- package/dist/src/cli/commands/llm.js +5 -8
- package/dist/src/cli/commands/macro.js +9 -16
- package/dist/src/cli/commands/meaning.js +7 -11
- package/dist/src/cli/commands/metrics.js +7 -10
- package/dist/src/cli/commands/ml-task.js +12 -20
- package/dist/src/cli/commands/model-evaluation-store.js +7 -11
- package/dist/src/cli/commands/notebook.js +15 -27
- package/dist/src/cli/commands/plugin.js +35 -68
- package/dist/src/cli/commands/project-deployer.js +10 -22
- package/dist/src/cli/commands/project-folder.js +10 -17
- package/dist/src/cli/commands/project-git.js +38 -70
- package/dist/src/cli/commands/project-library.js +15 -23
- package/dist/src/cli/commands/project.js +9 -26
- package/dist/src/cli/commands/recipe.d.ts +16 -0
- package/dist/src/cli/commands/recipe.js +83 -94
- package/dist/src/cli/commands/saved-model.js +23 -39
- package/dist/src/cli/commands/scenario.js +20 -34
- package/dist/src/cli/commands/sql.js +4 -4
- package/dist/src/cli/commands/statistics.js +11 -18
- package/dist/src/cli/commands/streaming-endpoint.js +7 -11
- package/dist/src/cli/commands/user.js +11 -22
- package/dist/src/cli/commands/variable.js +3 -4
- package/dist/src/cli/commands/webapp.js +10 -16
- package/dist/src/cli/commands/wiki.js +9 -13
- package/dist/src/cli/commands/workspace.js +8 -14
- package/dist/src/cli/contract.d.ts +5 -15
- package/dist/src/cli/contract.js +157 -254
- package/dist/src/cli/doctor.js +1 -0
- package/dist/src/cli/env.d.ts +2 -0
- package/dist/src/cli/env.js +16 -1
- package/dist/src/cli/flags.d.ts +1 -0
- package/dist/src/cli/flags.js +4 -9
- package/dist/src/cli/helpers/app-permissions.js +7 -5
- package/dist/src/cli/helpers/flow-zone.js +8 -6
- package/dist/src/cli/helpers/recipe.js +2 -1
- package/dist/src/cli/syntax.d.ts +83 -0
- package/dist/src/cli/syntax.js +217 -0
- package/dist/src/cli/types.d.ts +3 -0
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +4 -2
- package/dist/src/cli/version.d.ts +0 -2
- package/dist/src/cli/version.js +6 -7
- package/dist/src/cli.js +132 -274
- package/dist/src/client.d.ts +2 -1
- package/dist/src/client.js +32 -17
- package/dist/src/errors.d.ts +12 -1
- package/dist/src/errors.js +28 -3
- package/dist/src/index.d.ts +1 -3
- package/dist/src/index.js +2 -2
- package/dist/src/resources/api-services.js +2 -3
- package/dist/src/resources/applications.js +2 -4
- package/dist/src/resources/base.d.ts +2 -0
- package/dist/src/resources/base.js +14 -2
- package/dist/src/resources/code-envs.js +3 -5
- package/dist/src/resources/data-quality.js +14 -13
- package/dist/src/resources/datasets.d.ts +13 -0
- package/dist/src/resources/datasets.js +5 -4
- package/dist/src/resources/flow-zones.js +4 -2
- package/dist/src/resources/folders.js +4 -2
- package/dist/src/resources/futures.js +1 -3
- package/dist/src/resources/insights.js +3 -2
- package/dist/src/resources/jobs.js +4 -6
- package/dist/src/resources/macros.d.ts +1 -1
- package/dist/src/resources/macros.js +3 -7
- package/dist/src/resources/notebooks.js +2 -3
- package/dist/src/resources/project-git.js +9 -11
- package/dist/src/resources/project-library.d.ts +0 -2
- package/dist/src/resources/project-library.js +2 -7
- package/dist/src/resources/projects.js +4 -3
- package/dist/src/resources/recipes.d.ts +28 -0
- package/dist/src/resources/recipes.js +117 -89
- package/dist/src/resources/saved-models.js +1 -13
- package/dist/src/resources/scenarios.js +1 -3
- package/dist/src/resources/sql.js +9 -8
- package/dist/src/resources/statistics.js +3 -4
- package/dist/src/resources/variables.js +2 -1
- package/dist/src/resources/webapps.js +2 -3
- package/dist/src/schemas.d.ts +1 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.js +6 -7
- package/dist/src/utils/cleanup-ledger.js +9 -8
- package/dist/src/utils/deep-merge.js +2 -4
- package/dist/src/utils/flow-map.js +1 -6
- package/dist/src/utils/polling.d.ts +3 -0
- package/dist/src/utils/polling.js +3 -0
- package/dist/src/utils/project-archive.js +10 -14
- package/dist/src/utils/project-incarnation.js +3 -7
- package/dist/src/utils/records.d.ts +4 -0
- package/dist/src/utils/records.js +8 -0
- package/dist/src/utils/stable-hash.d.ts +3 -1
- package/dist/src/utils/stable-hash.js +2 -0
- package/package.json +10 -14
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +3 -4
- package/skills/dataiku-dss/references/authentication.md +3 -3
- package/skills/dataiku-dss/references/coding.md +1 -1
- package/skills/dataiku-dss/references/discovery.md +1 -1
- package/skills/dataiku-dss/references/mutations.md +0 -1
- package/dist/src/utils/pagination.d.ts +0 -8
- package/dist/src/utils/pagination.js +0 -23
- package/packages/types/dist/index.d.ts +0 -1062
- package/packages/types/dist/index.js +0 -871
package/dist/src/cli/doctor.js
CHANGED
|
@@ -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
|
}
|
package/dist/src/cli/env.d.ts
CHANGED
|
@@ -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;
|
package/dist/src/cli/env.js
CHANGED
|
@@ -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,8 @@ 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
|
-
|
|
59
|
+
// A variable set in the environment, even to "", wins over .env ("" clears it).
|
|
60
|
+
if (isAllowedEnvKey(key) && process.env[key] === undefined) {
|
|
46
61
|
process.env[key] = val;
|
|
47
62
|
(index === 0 ? projectEnvVars : installEnvVars).add(key);
|
|
48
63
|
}
|
package/dist/src/cli/flags.d.ts
CHANGED
package/dist/src/cli/flags.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { parseBooleanOption, } from "./coerce.js";
|
|
2
2
|
import { unsupportedHelpFlag, UsageError, } from "./usage.js";
|
|
3
3
|
/** Planning wins dispatch; dryRun remains set for combined plan metadata. */
|
|
4
|
-
const EXECUTION_MODES = [
|
|
5
|
-
Object.freeze({ plan: false, dryRun: false, }),
|
|
6
|
-
Object.freeze({ plan: false, dryRun: true, }),
|
|
7
|
-
Object.freeze({ plan: true, dryRun: false, }),
|
|
8
|
-
Object.freeze({ plan: true, dryRun: true, }),
|
|
9
|
-
];
|
|
10
4
|
export function executionMode(flags) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
return {
|
|
6
|
+
plan: parseBooleanOption(flags["plan"], "--plan") ?? false,
|
|
7
|
+
dryRun: parseBooleanOption(flags["dry-run"], "--dry-run") ?? false,
|
|
8
|
+
};
|
|
14
9
|
}
|
|
15
10
|
export const BOOLEAN_FLAGS = new Set([
|
|
16
11
|
"verbose",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { ClientValidationError, } from "../../errors.js";
|
|
2
3
|
import { canonicalDssUrl, } from "../../utils/dss-url.js";
|
|
4
|
+
import { asRecord, } from "../../utils/records.js";
|
|
3
5
|
import { compareStrings, } from "../../utils/stable-hash.js";
|
|
4
|
-
import { parseJsonObject,
|
|
6
|
+
import { parseJsonObject, stableHash, stableJson, } from "../coerce.js";
|
|
5
7
|
import { UsageError, } from "../usage.js";
|
|
6
8
|
/**
|
|
7
9
|
* Version 3 binds every snapshot to both its DSS server and the concrete project
|
|
@@ -61,7 +63,7 @@ function writeAllSync(fd, payload) {
|
|
|
61
63
|
while (offset < payload.length) {
|
|
62
64
|
const written = fs.writeSync(fd, payload, offset, payload.length - offset, offset);
|
|
63
65
|
if (written <= 0) {
|
|
64
|
-
throw new
|
|
66
|
+
throw new ClientValidationError(`Stalled writing app permissions snapshot at byte ${offset}.`, "internal_error");
|
|
65
67
|
}
|
|
66
68
|
offset += written;
|
|
67
69
|
}
|
|
@@ -123,7 +125,7 @@ export function parseAppPermissionsSnapshot(text, source) {
|
|
|
123
125
|
const capturedAt = requiredSnapshotString(record, "capturedAt", source);
|
|
124
126
|
const hash = requiredSnapshotString(record, "hash", source);
|
|
125
127
|
const permissionsHash = requiredSnapshotString(record, "permissionsHash", source);
|
|
126
|
-
const permissions =
|
|
128
|
+
const permissions = asRecord(record["permissions"]);
|
|
127
129
|
if (!permissions) {
|
|
128
130
|
throw new UsageError(`${source} is not an app permissions snapshot: "permissions" must be a JSON object.`, "validation_failed");
|
|
129
131
|
}
|
|
@@ -208,8 +210,8 @@ function pushDifference(out, path, backup, current) {
|
|
|
208
210
|
function collectDifferences(path, backup, current, out) {
|
|
209
211
|
if (stableJson(backup) === stableJson(current))
|
|
210
212
|
return;
|
|
211
|
-
const backupRecord =
|
|
212
|
-
const currentRecord =
|
|
213
|
+
const backupRecord = asRecord(backup);
|
|
214
|
+
const currentRecord = asRecord(current);
|
|
213
215
|
if (backupRecord && currentRecord) {
|
|
214
216
|
const keys = [...new Set([...Object.keys(backupRecord), ...Object.keys(currentRecord),]),]
|
|
215
217
|
.sort(compareStrings);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync, } from "node:fs";
|
|
2
|
+
import { asRecord, } from "../../utils/records.js";
|
|
2
3
|
import { compareStrings, } from "../../utils/stable-hash.js";
|
|
3
|
-
import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject,
|
|
4
|
+
import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, requiredStringArray, splitCsvFlag, } from "../coerce.js";
|
|
5
|
+
import { commandUsage, } from "../syntax.js";
|
|
4
6
|
import { UsageError, } from "../usage.js";
|
|
5
7
|
export function flowZoneId(value) {
|
|
6
8
|
const trimmed = value.trim();
|
|
@@ -10,7 +12,7 @@ export function flowZoneId(value) {
|
|
|
10
12
|
}
|
|
11
13
|
export function flowZoneName(value) {
|
|
12
14
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
13
|
-
throw new UsageError(
|
|
15
|
+
throw new UsageError(`--name is required. Usage: ${commandUsage("flow-zone", "create")}`);
|
|
14
16
|
}
|
|
15
17
|
return value.trim();
|
|
16
18
|
}
|
|
@@ -106,7 +108,7 @@ export function flowZonePlanColor(value, source) {
|
|
|
106
108
|
export function flowZonePlanPosition(value, source) {
|
|
107
109
|
if (value === undefined)
|
|
108
110
|
return undefined;
|
|
109
|
-
const record =
|
|
111
|
+
const record = asRecord(value);
|
|
110
112
|
if (!record) {
|
|
111
113
|
throw new UsageError(`${source} must be an object with x and y.`, "validation_failed");
|
|
112
114
|
}
|
|
@@ -116,7 +118,7 @@ export function flowZonePlanPosition(value, source) {
|
|
|
116
118
|
};
|
|
117
119
|
}
|
|
118
120
|
export function flowZoneCurrentPosition(zone) {
|
|
119
|
-
const position =
|
|
121
|
+
const position = asRecord(zone.position);
|
|
120
122
|
if (!position)
|
|
121
123
|
return undefined;
|
|
122
124
|
const x = position.x;
|
|
@@ -133,7 +135,7 @@ export function flowZoneSamePosition(a, b) {
|
|
|
133
135
|
export function parseFlowZonePlanItem(value, source) {
|
|
134
136
|
if (typeof value === "string")
|
|
135
137
|
return parseFlowZoneObject(value);
|
|
136
|
-
const record =
|
|
138
|
+
const record = asRecord(value);
|
|
137
139
|
if (!record) {
|
|
138
140
|
throw new UsageError(`${source} must be TYPE:ID or an object.`, "validation_failed");
|
|
139
141
|
}
|
|
@@ -212,7 +214,7 @@ export function parseFlowZoneOrganizePlan(input) {
|
|
|
212
214
|
...(topologyFingerprint ? { topologyFingerprint, } : {}),
|
|
213
215
|
zones: zones.map((value, index) => {
|
|
214
216
|
const source = `zones[${index}]`;
|
|
215
|
-
const record =
|
|
217
|
+
const record = asRecord(value);
|
|
216
218
|
if (!record)
|
|
217
219
|
throw new UsageError(`${source} must be an object.`, "validation_failed");
|
|
218
220
|
const id = optionalStringField(record, ["id", "zoneId",]);
|
|
@@ -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
|
|
130
|
+
throw new ClientValidationError(`Stalled writing recipe backup at byte ${offset}.`, "internal_error");
|
|
130
131
|
}
|
|
131
132
|
offset += written;
|
|
132
133
|
}
|
|
@@ -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
|
+
}
|
package/dist/src/cli/types.d.ts
CHANGED
|
@@ -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">;
|
package/dist/src/cli/usage.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StableErrorCode } from "../errors.js";
|
|
2
2
|
export declare const RESOURCE_NAMES: string[];
|
|
3
|
-
export declare function inferRequiresProject(resource: string, action: string
|
|
3
|
+
export declare function inferRequiresProject(resource: string, action: string): boolean;
|
|
4
4
|
export declare class UsageError extends Error {
|
|
5
5
|
readonly code: StableErrorCode;
|
|
6
6
|
readonly hint?: string;
|
package/dist/src/cli/usage.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { commandSyntaxTree, syntaxHasFlag, } from "./syntax.js";
|
|
1
2
|
export const RESOURCE_NAMES = [
|
|
2
3
|
"agent",
|
|
3
4
|
"app",
|
|
@@ -79,7 +80,7 @@ const PROJECT_SCOPED_RESOURCES = new Set([
|
|
|
79
80
|
"saved-model",
|
|
80
81
|
"wiki",
|
|
81
82
|
]);
|
|
82
|
-
export function inferRequiresProject(resource, action
|
|
83
|
+
export function inferRequiresProject(resource, action) {
|
|
83
84
|
if (resource === "agent" || resource === "auth" || resource === "doctor" || resource === "commands"
|
|
84
85
|
|| resource === "install-skill" || resource === "version")
|
|
85
86
|
return false;
|
|
@@ -92,7 +93,8 @@ export function inferRequiresProject(resource, action, usage) {
|
|
|
92
93
|
return false;
|
|
93
94
|
if (resource === "project-git")
|
|
94
95
|
return !action.startsWith("future-");
|
|
95
|
-
|
|
96
|
+
const syntax = commandSyntaxTree(resource, action);
|
|
97
|
+
return syntax !== undefined && syntaxHasFlag(syntax, "project-key");
|
|
96
98
|
}
|
|
97
99
|
export class UsageError extends Error {
|
|
98
100
|
code;
|
|
@@ -37,8 +37,6 @@ export interface CliVersionPayload {
|
|
|
37
37
|
buildRevision: string | null;
|
|
38
38
|
staleBuild: boolean;
|
|
39
39
|
}
|
|
40
|
-
/** Bun is the only supported runtime; the field is retained for payload stability. */
|
|
41
|
-
export declare function detectRuntime(): CliRuntime;
|
|
42
40
|
export declare function buildVersionPayload(input: {
|
|
43
41
|
packageVersion: string;
|
|
44
42
|
checkoutRevision: string | undefined;
|
package/dist/src/cli/version.js
CHANGED
|
@@ -182,10 +182,6 @@ export function sourceTreeNewerThanBuild(packageRoot) {
|
|
|
182
182
|
return false;
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
-
/** Bun is the only supported runtime; the field is retained for payload stability. */
|
|
186
|
-
export function detectRuntime() {
|
|
187
|
-
return "bun";
|
|
188
|
-
}
|
|
189
185
|
export function buildVersionPayload(input) {
|
|
190
186
|
const isDist = input.loadSource === "dist";
|
|
191
187
|
// Source execution never claims a build revision, even if the environment
|
|
@@ -212,8 +208,11 @@ export const AGENT_CONTRACT_VERSION = 2;
|
|
|
212
208
|
export const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
213
209
|
export const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-v2.json";
|
|
214
210
|
export function cliVersionResult() {
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
// Only code actually loaded from dist/ may report dist provenance; an
|
|
212
|
+
// inherited DSS_LOAD_SOURCE on a source run is ignored.
|
|
213
|
+
const loadSource = detectLoadSource();
|
|
214
|
+
/* oxlint-disable-next-line dss/no-direct-process-env -- launcher-injected build revision */
|
|
215
|
+
const envBuildRevision = loadSource === "dist" ? process.env["DSS_BUILD_REVISION"] : undefined;
|
|
217
216
|
return buildVersionPayload({
|
|
218
217
|
packageVersion: CLI_VERSION,
|
|
219
218
|
checkoutRevision: CLI_GIT_REVISION,
|
|
@@ -225,6 +224,6 @@ export function cliVersionResult() {
|
|
|
225
224
|
sourceNewerThanBuild: loadSource === "dist"
|
|
226
225
|
? sourceTreeNewerThanBuild(PACKAGE_ROOT)
|
|
227
226
|
: false,
|
|
228
|
-
runtime:
|
|
227
|
+
runtime: "bun",
|
|
229
228
|
});
|
|
230
229
|
}
|