dataiku-sdk 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/bin/dss.js +20 -1
- package/dist/build-metadata.json +1 -0
- package/dist/src/cli/commands/data-quality.js +42 -0
- package/dist/src/cli/commands/dataset.d.ts +23 -0
- package/dist/src/cli/commands/dataset.js +187 -6
- package/dist/src/cli/commands/job.js +15 -2
- package/dist/src/cli/commands/project.js +116 -12
- package/dist/src/cli/contract.d.ts +1 -0
- package/dist/src/cli/contract.js +182 -13
- package/dist/src/cli/doctor.d.ts +12 -2
- package/dist/src/cli/doctor.js +44 -13
- package/dist/src/cli/flags.js +2 -0
- package/dist/src/cli/helpers/cleanup.js +30 -1
- package/dist/src/cli/output.d.ts +3 -1
- package/dist/src/cli/output.js +11 -6
- package/dist/src/cli/types.d.ts +2 -0
- package/dist/src/cli/version.d.ts +46 -4
- package/dist/src/cli/version.js +175 -9
- package/dist/src/cli.js +51 -13
- package/dist/src/client.d.ts +28 -1
- package/dist/src/client.js +34 -7
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +1 -0
- package/dist/src/resources/datasets.d.ts +43 -0
- package/dist/src/resources/datasets.js +199 -28
- package/dist/src/resources/projects.d.ts +36 -3
- package/dist/src/resources/projects.js +127 -16
- package/dist/src/utils/project-archive.d.ts +32 -0
- package/dist/src/utils/project-archive.js +624 -0
- package/package.json +6 -5
- package/plugin.json +1 -1
package/dist/src/cli/contract.js
CHANGED
|
@@ -234,7 +234,7 @@ const AGENT_CONTRACT_EXAMPLES = [
|
|
|
234
234
|
"dss agent contract --fields commands.actions",
|
|
235
235
|
];
|
|
236
236
|
const VERSION_USAGE = "dss version";
|
|
237
|
-
const VERSION_DESCRIPTION = "Print the CLI version and
|
|
237
|
+
const VERSION_DESCRIPTION = "Print the CLI version, checkout/build revisions, load source, runtime, and stale-build status as JSON.";
|
|
238
238
|
const VERSION_EXAMPLES = ["dss version", "dss --version",];
|
|
239
239
|
const INSTALL_SKILL_USAGE = "dss install-skill [--global] [--agent NAME] [--target PATH] [--list-agents] [--dry-run] [--plan]";
|
|
240
240
|
const INSTALL_SKILL_DESCRIPTION = "Install the dataiku-dss agent skill for detected coding agents.";
|
|
@@ -270,6 +270,7 @@ const ALLOWED_CLEANUP_ACTIONS = new Set([
|
|
|
270
270
|
"code-env delete",
|
|
271
271
|
"folder delete",
|
|
272
272
|
"folder delete-file",
|
|
273
|
+
"project delete",
|
|
273
274
|
"app delete-instance",
|
|
274
275
|
]);
|
|
275
276
|
export function isAllowedCleanupAction(resource, action) {
|
|
@@ -676,6 +677,7 @@ export function inferRequiresProject(resource, action, usage) {
|
|
|
676
677
|
const ARRAY_OUTPUT_ACTIONS = new Set([
|
|
677
678
|
"history",
|
|
678
679
|
"find",
|
|
680
|
+
"files",
|
|
679
681
|
"infer",
|
|
680
682
|
"last-results",
|
|
681
683
|
"list",
|
|
@@ -861,6 +863,9 @@ function inferPayloadSchema(inputContract) {
|
|
|
861
863
|
function inferExitCodes(resource, action, asyncKind) {
|
|
862
864
|
const longRunning = asyncKind !== "none" || `${resource}.${action}` === "batch.run";
|
|
863
865
|
const assertion = `${resource}.${action}` === "recipe.assert-unchanged"
|
|
866
|
+
|| `${resource}.${action}` === "dataset.assert-count"
|
|
867
|
+
|| `${resource}.${action}` === "dataset.assert-schema"
|
|
868
|
+
|| `${resource}.${action}` === "data-quality.assert-results"
|
|
864
869
|
|| `${resource}.${action}` === "batch.run";
|
|
865
870
|
return {
|
|
866
871
|
ok: 0,
|
|
@@ -872,6 +877,9 @@ function inferExitCodes(resource, action, asyncKind) {
|
|
|
872
877
|
};
|
|
873
878
|
}
|
|
874
879
|
function cleanupCommandFromDeleteUsage(resource, action) {
|
|
880
|
+
if (`${resource}.${action}` === "project.import") {
|
|
881
|
+
return "dss project delete <used-project-key> --if-exists --expect-project-incarnation <hash>";
|
|
882
|
+
}
|
|
875
883
|
if (resource === "project" && (action === "create" || action === "duplicate")) {
|
|
876
884
|
return undefined;
|
|
877
885
|
}
|
|
@@ -895,9 +903,20 @@ export function supportsCleanupLedger(resource, action) {
|
|
|
895
903
|
return cleanupCommandFromDeleteUsage(resource, action) !== undefined
|
|
896
904
|
|| `${resource}.${action}` === "folder.upload";
|
|
897
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* Write actions that no public DSS route can undo. `dataset upload-file` adds a
|
|
908
|
+
* file to the dataset's uploaded-files set, and DSS exposes no endpoint to
|
|
909
|
+
* replace or delete an individual uploaded file, so the write is permanent and
|
|
910
|
+
* no cleanup-ledger entry can recover the previous file set.
|
|
911
|
+
*/
|
|
912
|
+
const EXPLICIT_DESTRUCTIVE_KEYS = {
|
|
913
|
+
"dataset.upload-file": true,
|
|
914
|
+
};
|
|
898
915
|
function inferDestructiveLevel(resource, sideEffect, action) {
|
|
899
916
|
if (sideEffect !== "write")
|
|
900
917
|
return "none";
|
|
918
|
+
if (EXPLICIT_DESTRUCTIVE_KEYS[`${resource}.${action}`] === true)
|
|
919
|
+
return "destructive";
|
|
901
920
|
if (resource === "project-git") {
|
|
902
921
|
return PROJECT_GIT_DESTRUCTIVE_ACTIONS[action] === true ? "destructive" : "reversible";
|
|
903
922
|
}
|
|
@@ -992,7 +1011,7 @@ export function inferCleanupHint(resource, action) {
|
|
|
992
1011
|
return `Delete with \`dss ${resource} delete <id>${ifExists}\` when the created object is disposable.`;
|
|
993
1012
|
}
|
|
994
1013
|
function buildRegistryEntry(resource, action, meta) {
|
|
995
|
-
const requiresAuth = inferRequiresAuth(resource);
|
|
1014
|
+
const requiresAuth = meta.localHandler === undefined && inferRequiresAuth(resource);
|
|
996
1015
|
const requiresProject = inferRequiresProject(resource, action, meta.usage);
|
|
997
1016
|
const sideEffect = inferSideEffect(resource, action);
|
|
998
1017
|
const destructive = inferDestructiveLevel(resource, sideEffect, action);
|
|
@@ -1781,6 +1800,38 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
1781
1800
|
identifiers: { oldName: args[0], newName: args[1], },
|
|
1782
1801
|
payload: { oldName: args[0], newName: args[1], },
|
|
1783
1802
|
};
|
|
1803
|
+
case "dataset.upload-file": {
|
|
1804
|
+
const fileName = requiredPlanFlag(flags, "file-name", entry.usage);
|
|
1805
|
+
const datasetEndpoint = projectEndpoint(`/datasets/${encodeURIComponent(args[0])}`);
|
|
1806
|
+
const filesEndpoint = `${datasetEndpoint}/uploaded/files`;
|
|
1807
|
+
const upload = {
|
|
1808
|
+
method: "POST",
|
|
1809
|
+
endpoint: filesEndpoint,
|
|
1810
|
+
payload: { ...uploadPayload(args[1]), fileName, },
|
|
1811
|
+
};
|
|
1812
|
+
return {
|
|
1813
|
+
...upload,
|
|
1814
|
+
identifiers: {
|
|
1815
|
+
datasetName: args[0],
|
|
1816
|
+
localPath: args[1],
|
|
1817
|
+
fileName,
|
|
1818
|
+
},
|
|
1819
|
+
requests: [
|
|
1820
|
+
{ sequence: 1, method: "GET", endpoint: datasetEndpoint, },
|
|
1821
|
+
{ sequence: 2, method: "GET", endpoint: filesEndpoint, },
|
|
1822
|
+
{ sequence: 3, ...upload, },
|
|
1823
|
+
{
|
|
1824
|
+
sequence: 4,
|
|
1825
|
+
method: "GET",
|
|
1826
|
+
endpoint: filesEndpoint,
|
|
1827
|
+
assert: {
|
|
1828
|
+
filename: fileName,
|
|
1829
|
+
length: "local file byte length",
|
|
1830
|
+
},
|
|
1831
|
+
},
|
|
1832
|
+
],
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1784
1835
|
case "dataset.delete":
|
|
1785
1836
|
return {
|
|
1786
1837
|
method: "DELETE",
|
|
@@ -2812,17 +2863,74 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2812
2863
|
},
|
|
2813
2864
|
};
|
|
2814
2865
|
}
|
|
2815
|
-
case "project.delete":
|
|
2866
|
+
case "project.delete": {
|
|
2867
|
+
const expectedProjectIncarnation = flags["expect-project-incarnation"] === undefined
|
|
2868
|
+
? undefined
|
|
2869
|
+
: requiredPlanFlag(flags, "expect-project-incarnation", entry.usage);
|
|
2870
|
+
if (expectedProjectIncarnation !== undefined
|
|
2871
|
+
&& !/^[0-9a-f]{64}$/.test(expectedProjectIncarnation)) {
|
|
2872
|
+
throw new UsageError("--expect-project-incarnation must be a 64-character lowercase SHA-256 hash.", "validation_failed");
|
|
2873
|
+
}
|
|
2874
|
+
const endpoint = `/public/api/projects/${encodeURIComponent(id)}${querySuffix({
|
|
2875
|
+
clearManagedDatasets: flags["drop-data"] === true,
|
|
2876
|
+
clearOutputManagedFolders: false,
|
|
2877
|
+
clearJobAndScenarioLogs: true,
|
|
2878
|
+
wait: true,
|
|
2879
|
+
})}`;
|
|
2880
|
+
const guarded = flags["if-exists"] === true
|
|
2881
|
+
|| flags["dry-run"] === true
|
|
2882
|
+
|| expectedProjectIncarnation !== undefined;
|
|
2883
|
+
if (!guarded) {
|
|
2884
|
+
return {
|
|
2885
|
+
method: "DELETE",
|
|
2886
|
+
endpoint,
|
|
2887
|
+
identifiers: { projectKey: id, },
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
const projectProbe = `/public/api/projects/${encodeURIComponent(id)}/`;
|
|
2891
|
+
const incarnationGate = expectedProjectIncarnation === undefined
|
|
2892
|
+
? { required: false, provided: false, }
|
|
2893
|
+
: {
|
|
2894
|
+
required: true,
|
|
2895
|
+
provided: true,
|
|
2896
|
+
expectedHash: expectedProjectIncarnation,
|
|
2897
|
+
};
|
|
2898
|
+
if (flags["dry-run"] === true) {
|
|
2899
|
+
return {
|
|
2900
|
+
method: "GET",
|
|
2901
|
+
endpoint: projectProbe,
|
|
2902
|
+
identifiers: {
|
|
2903
|
+
projectKey: id,
|
|
2904
|
+
dryRun: true,
|
|
2905
|
+
ifExists: flags["if-exists"] === true,
|
|
2906
|
+
projectIncarnationGate: incarnationGate,
|
|
2907
|
+
note: "Read-only guarded-delete preflight. No DELETE is issued; a supplied creationTag hash must match.",
|
|
2908
|
+
},
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2816
2911
|
return {
|
|
2817
2912
|
method: "DELETE",
|
|
2818
|
-
endpoint
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2913
|
+
endpoint,
|
|
2914
|
+
identifiers: {
|
|
2915
|
+
projectKey: id,
|
|
2916
|
+
ifExists: flags["if-exists"] === true,
|
|
2917
|
+
projectIncarnationGate: incarnationGate,
|
|
2918
|
+
incarnationControl: expectedProjectIncarnation === undefined
|
|
2919
|
+
? "none"
|
|
2920
|
+
: "client-side-non-atomic-stale-identity-check",
|
|
2921
|
+
preflightRequests: [{
|
|
2922
|
+
method: "GET",
|
|
2923
|
+
endpoint: projectProbe,
|
|
2924
|
+
intent: expectedProjectIncarnation === undefined
|
|
2925
|
+
? "Confirm existence before a convergent delete."
|
|
2926
|
+
: "Recompute the current project-incarnation hash from creationTag before DELETE.",
|
|
2927
|
+
},],
|
|
2928
|
+
note: expectedProjectIncarnation === undefined
|
|
2929
|
+
? "Convergent delete: a 404 preflight is an already-absent success only with --if-exists."
|
|
2930
|
+
: "Incarnation-bound delete: the GET must match the expected creationTag hash before an unconditional DELETE. DSS exposes no conditional project DELETE, so the client-side gate cannot serialize against key reuse after the GET.",
|
|
2931
|
+
},
|
|
2825
2932
|
};
|
|
2933
|
+
}
|
|
2826
2934
|
case "project.duplicate": {
|
|
2827
2935
|
const options = jsonInput(flags);
|
|
2828
2936
|
return {
|
|
@@ -2844,11 +2952,32 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2844
2952
|
},
|
|
2845
2953
|
};
|
|
2846
2954
|
}
|
|
2847
|
-
case "project.import":
|
|
2848
|
-
|
|
2955
|
+
case "project.import": {
|
|
2956
|
+
const settings = jsonInput(flags) ?? {};
|
|
2957
|
+
const rawTarget = flags["target-project-key"];
|
|
2958
|
+
const targetProjectKey = rawTarget?.trim();
|
|
2959
|
+
if (rawTarget !== undefined && targetProjectKey === "") {
|
|
2960
|
+
throw new UsageError(`--target-project-key must not be empty. Usage: ${entry.usage}`);
|
|
2961
|
+
}
|
|
2962
|
+
const settingsTarget = settings.targetProjectKey;
|
|
2963
|
+
if (settingsTarget !== undefined
|
|
2964
|
+
&& (typeof settingsTarget !== "string" || settingsTarget.trim() === "")) {
|
|
2965
|
+
throw new UsageError(`targetProjectKey in import settings must be a non-empty string. Usage: ${entry.usage}`);
|
|
2966
|
+
}
|
|
2967
|
+
if (targetProjectKey !== undefined
|
|
2968
|
+
&& settingsTarget !== undefined
|
|
2969
|
+
&& targetProjectKey !== settingsTarget.trim()) {
|
|
2970
|
+
throw new UsageError(`--target-project-key conflicts with targetProjectKey in import settings. Usage: ${entry.usage}`);
|
|
2971
|
+
}
|
|
2972
|
+
const processPayload = targetProjectKey === undefined
|
|
2973
|
+
? settings
|
|
2974
|
+
: { ...settings, targetProjectKey, };
|
|
2975
|
+
const finalizedPayload = Object.keys(processPayload).length === 0
|
|
2976
|
+
? { _: "_", }
|
|
2977
|
+
: processPayload;
|
|
2978
|
+
const upload = {
|
|
2849
2979
|
method: "POST",
|
|
2850
2980
|
endpoint: "/public/api/projects/import/upload",
|
|
2851
|
-
identifiers: { filePath: id, },
|
|
2852
2981
|
payload: {
|
|
2853
2982
|
contentType: "multipart/form-data",
|
|
2854
2983
|
fileField: "file",
|
|
@@ -2856,6 +2985,46 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2856
2985
|
fileName: "tmp-import.zip",
|
|
2857
2986
|
},
|
|
2858
2987
|
};
|
|
2988
|
+
return {
|
|
2989
|
+
...upload,
|
|
2990
|
+
identifiers: {
|
|
2991
|
+
filePath: id,
|
|
2992
|
+
...(targetProjectKey ? { targetProjectKey, } : {}),
|
|
2993
|
+
archivePreflight: {
|
|
2994
|
+
local: true,
|
|
2995
|
+
required: true,
|
|
2996
|
+
checks: [
|
|
2997
|
+
"zip-integrity",
|
|
2998
|
+
"safe-unique-members",
|
|
2999
|
+
"manifest",
|
|
3000
|
+
"flow-references",
|
|
3001
|
+
"orphan-members",
|
|
3002
|
+
],
|
|
3003
|
+
},
|
|
3004
|
+
successVerification: {
|
|
3005
|
+
usedProjectKeyRequired: true,
|
|
3006
|
+
projectReadRequired: true,
|
|
3007
|
+
projectIncarnationRequired: true,
|
|
3008
|
+
remappingReported: true,
|
|
3009
|
+
},
|
|
3010
|
+
...(flags["record-cleanup"] === undefined
|
|
3011
|
+
? {}
|
|
3012
|
+
: {
|
|
3013
|
+
cleanupBinding: "actual used project key plus verified creationTag incarnation hash",
|
|
3014
|
+
}),
|
|
3015
|
+
},
|
|
3016
|
+
requests: [
|
|
3017
|
+
{ sequence: 1, ...upload, },
|
|
3018
|
+
{
|
|
3019
|
+
sequence: 2,
|
|
3020
|
+
method: "POST",
|
|
3021
|
+
endpoint: "/public/api/projects/import/{importId}/process",
|
|
3022
|
+
pathBindings: { importId: "requests[0].response.id", },
|
|
3023
|
+
payload: finalizedPayload,
|
|
3024
|
+
},
|
|
3025
|
+
],
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
2859
3028
|
case "project.settings-set":
|
|
2860
3029
|
return {
|
|
2861
3030
|
method: "PUT",
|
package/dist/src/cli/doctor.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface FixtureDiscoveryResult {
|
|
|
38
38
|
export interface DoctorEnvironment {
|
|
39
39
|
projectKey?: string;
|
|
40
40
|
dssVersion?: string;
|
|
41
|
+
dssApiVersion?: string;
|
|
41
42
|
instanceTime?: string;
|
|
42
43
|
integrationFlags: {
|
|
43
44
|
mutating: boolean;
|
|
@@ -48,6 +49,15 @@ export interface DoctorEnvironment {
|
|
|
48
49
|
apiServices: boolean;
|
|
49
50
|
};
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Server provenance captured from documented response headers on the public
|
|
54
|
+
* project-list connectivity call. Null entries mean the header was absent.
|
|
55
|
+
*/
|
|
56
|
+
export interface DoctorVersionInfo {
|
|
57
|
+
dssVersion: string | null;
|
|
58
|
+
dssApiVersion: string | null;
|
|
59
|
+
instanceTime: string | null;
|
|
60
|
+
}
|
|
51
61
|
export interface DoctorResult {
|
|
52
62
|
ok: boolean;
|
|
53
63
|
checks: DoctorCheck[];
|
|
@@ -66,7 +76,7 @@ export interface DoctorResult {
|
|
|
66
76
|
export declare function errorDetails(error: unknown): Record<string, unknown>;
|
|
67
77
|
export declare function firstStringField(items: unknown[] | undefined, fields: string[]): string | null;
|
|
68
78
|
export declare function integrationFlag(name: string): boolean;
|
|
69
|
-
export declare function doctorEnvironment(projectKey?: string): DoctorEnvironment;
|
|
79
|
+
export declare function doctorEnvironment(projectKey?: string, versionInfo?: DoctorVersionInfo): DoctorEnvironment;
|
|
70
80
|
export declare function permissionStatusForError(error: unknown): PermissionStatus;
|
|
71
81
|
export declare function probeDoctorPermission(probe: () => Promise<unknown>): Promise<{
|
|
72
82
|
status: PermissionStatus;
|
|
@@ -96,7 +106,7 @@ export declare function firstSafeJupyterNotebook(items: unknown[]): {
|
|
|
96
106
|
unsafe: FixtureReject[];
|
|
97
107
|
};
|
|
98
108
|
export declare function discoverFixtureReport(client: DataikuClient, projectKey: string, flags: Record<string, string | boolean>): Promise<FixtureDiscoveryResult>;
|
|
99
|
-
export declare function doctorCapabilities(client: DataikuClient, projectKey: string | undefined, accessibleProjects: unknown[] | undefined, flags: Record<string, string | boolean
|
|
109
|
+
export declare function doctorCapabilities(client: DataikuClient, projectKey: string | undefined, accessibleProjects: unknown[] | undefined, flags: Record<string, string | boolean>, versionInfo?: DoctorVersionInfo): Promise<Pick<DoctorResult, "permissions" | "permissionDetails" | "fixtures" | "environment">>;
|
|
100
110
|
export declare function runDoctor(flags: Record<string, string | boolean>): Promise<{
|
|
101
111
|
result: DoctorResult;
|
|
102
112
|
exitCode: number;
|
package/dist/src/cli/doctor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DataikuClient, } from "../client.js";
|
|
2
2
|
import { DataikuError, } from "../errors.js";
|
|
3
|
+
import { ProjectSummaryArraySchema, safeParseSchema, } from "../schemas.js";
|
|
3
4
|
import { num, splitCsvFlag, stringField, } from "./coerce.js";
|
|
4
5
|
import { currentCommandContext, resolveCredentials, } from "./runtime.js";
|
|
5
6
|
import { UsageError, } from "./usage.js";
|
|
@@ -31,9 +32,12 @@ export function integrationFlag(name) {
|
|
|
31
32
|
const value = process.env[name];
|
|
32
33
|
return value === "1" || value?.toLowerCase() === "true";
|
|
33
34
|
}
|
|
34
|
-
export function doctorEnvironment(projectKey) {
|
|
35
|
+
export function doctorEnvironment(projectKey, versionInfo) {
|
|
35
36
|
return {
|
|
36
37
|
...(projectKey ? { projectKey, } : {}),
|
|
38
|
+
...(versionInfo?.dssVersion ? { dssVersion: versionInfo.dssVersion, } : {}),
|
|
39
|
+
...(versionInfo?.dssApiVersion ? { dssApiVersion: versionInfo.dssApiVersion, } : {}),
|
|
40
|
+
...(versionInfo?.instanceTime ? { instanceTime: versionInfo.instanceTime, } : {}),
|
|
37
41
|
integrationFlags: {
|
|
38
42
|
mutating: integrationFlag("RUN_DATAIKU_INTEGRATION_MUTATING"),
|
|
39
43
|
adminMutating: integrationFlag("RUN_DATAIKU_ADMIN_MUTATING"),
|
|
@@ -174,7 +178,7 @@ export async function discoverFixtureReport(client, projectKey, flags) {
|
|
|
174
178
|
},
|
|
175
179
|
};
|
|
176
180
|
}
|
|
177
|
-
export async function doctorCapabilities(client, projectKey, accessibleProjects, flags) {
|
|
181
|
+
export async function doctorCapabilities(client, projectKey, accessibleProjects, flags, versionInfo) {
|
|
178
182
|
const probeProjectKey = projectKey ?? firstStringField(accessibleProjects, ["projectKey",])
|
|
179
183
|
?? undefined;
|
|
180
184
|
const probes = {
|
|
@@ -210,7 +214,7 @@ export async function doctorCapabilities(client, projectKey, accessibleProjects,
|
|
|
210
214
|
const capabilityResult = {
|
|
211
215
|
permissions,
|
|
212
216
|
...(Object.keys(permissionDetails).length > 0 ? { permissionDetails, } : {}),
|
|
213
|
-
environment: doctorEnvironment(projectKey),
|
|
217
|
+
environment: doctorEnvironment(projectKey, versionInfo),
|
|
214
218
|
};
|
|
215
219
|
if (flags["fast"] !== true && probeProjectKey) {
|
|
216
220
|
try {
|
|
@@ -255,6 +259,7 @@ export async function runDoctor(flags) {
|
|
|
255
259
|
: "Missing Dataiku URL and/or API key. Set DATAIKU_URL/DATAIKU_API_KEY or pass --url/--api-key.",
|
|
256
260
|
});
|
|
257
261
|
let accessibleProjects;
|
|
262
|
+
let environmentVersion;
|
|
258
263
|
if (credentialsOk) {
|
|
259
264
|
const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
|
|
260
265
|
const retryMaxAttempts = num(flags["retries"], "--retries");
|
|
@@ -269,14 +274,40 @@ export async function runDoctor(flags) {
|
|
|
269
274
|
caCertPath,
|
|
270
275
|
});
|
|
271
276
|
try {
|
|
272
|
-
const projects = await client.projects
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
const projects = await client.getWithMetadata("/public/api/projects/");
|
|
278
|
+
// Documented headers on the public project-list call populate the
|
|
279
|
+
// optional environment fields; absent headers stay absent. Header
|
|
280
|
+
// provenance is independent of the body being well-formed.
|
|
281
|
+
environmentVersion = {
|
|
282
|
+
dssVersion: projects.meta.dssVersion,
|
|
283
|
+
dssApiVersion: projects.meta.dssApiVersion,
|
|
284
|
+
instanceTime: projects.meta.date,
|
|
285
|
+
};
|
|
286
|
+
// A reachable endpoint that answers with something other than a
|
|
287
|
+
// project list is not working connectivity: the body never becomes
|
|
288
|
+
// capability input, so probes cannot iterate a non-array.
|
|
289
|
+
const parsed = safeParseSchema(ProjectSummaryArraySchema, projects.data);
|
|
290
|
+
if (parsed.success) {
|
|
291
|
+
accessibleProjects = parsed.data;
|
|
292
|
+
checks.push({
|
|
293
|
+
name: "connectivity",
|
|
294
|
+
ok: true,
|
|
295
|
+
message: "Connected to DSS and listed accessible projects.",
|
|
296
|
+
details: { projectCount: parsed.data.length, },
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
checks.push({
|
|
301
|
+
name: "connectivity",
|
|
302
|
+
ok: false,
|
|
303
|
+
message: "Connected to DSS but the project list response was not a project list.",
|
|
304
|
+
details: {
|
|
305
|
+
reason: "invalid_project_list",
|
|
306
|
+
errorCount: parsed.errors.length,
|
|
307
|
+
errors: parsed.errors.slice(0, 5),
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
}
|
|
280
311
|
}
|
|
281
312
|
catch (error) {
|
|
282
313
|
checks.push({
|
|
@@ -323,8 +354,8 @@ export async function runDoctor(flags) {
|
|
|
323
354
|
tlsRejectUnauthorized,
|
|
324
355
|
caCertPath,
|
|
325
356
|
});
|
|
326
|
-
result.environment = doctorEnvironment(projectKey);
|
|
327
|
-
Object.assign(result, await doctorCapabilities(client, projectKey, accessibleProjects, flags));
|
|
357
|
+
result.environment = doctorEnvironment(projectKey, environmentVersion);
|
|
358
|
+
Object.assign(result, await doctorCapabilities(client, projectKey, accessibleProjects, flags, environmentVersion));
|
|
328
359
|
}
|
|
329
360
|
return { result, exitCode: result.ok ? 0 : 2, };
|
|
330
361
|
}
|
package/dist/src/cli/flags.js
CHANGED
|
@@ -96,11 +96,13 @@ export const VALUE_FLAGS = new Set([
|
|
|
96
96
|
"env-version",
|
|
97
97
|
"expect-hash",
|
|
98
98
|
"expect-project-incarnation",
|
|
99
|
+
"expected",
|
|
99
100
|
"expect-version",
|
|
100
101
|
"data-file",
|
|
101
102
|
"database",
|
|
102
103
|
"dataset",
|
|
103
104
|
"file",
|
|
105
|
+
"file-name",
|
|
104
106
|
"file-type",
|
|
105
107
|
"env",
|
|
106
108
|
"install-core-packages",
|
|
@@ -4,7 +4,7 @@ export function projectArg(projectKey) {
|
|
|
4
4
|
}
|
|
5
5
|
export function cleanupLedgerEntry(resource, action, args, flags, result, projectKey) {
|
|
6
6
|
if (!(action.startsWith("create") || action === "clone" || action === "duplicate"
|
|
7
|
-
|| action === "upload"))
|
|
7
|
+
|| action === "import" || action === "upload"))
|
|
8
8
|
return undefined;
|
|
9
9
|
const record = resultRecord(result);
|
|
10
10
|
if (record.skipped !== undefined)
|
|
@@ -190,6 +190,35 @@ export function cleanupLedgerEntry(resource, action, args, flags, result, projec
|
|
|
190
190
|
path: args[1],
|
|
191
191
|
cleanup: { argv: ["folder", "delete-file", args[0], args[1], ...withProject,], },
|
|
192
192
|
};
|
|
193
|
+
case "project.import": {
|
|
194
|
+
const usedProjectKey = stringField(record, ["usedProjectKey",]);
|
|
195
|
+
const recordedIncarnation = stringField(record, ["projectIncarnationHash",]);
|
|
196
|
+
const boundIncarnation = recordedIncarnation
|
|
197
|
+
&& /^[0-9a-f]{64}$/.test(recordedIncarnation)
|
|
198
|
+
? recordedIncarnation
|
|
199
|
+
: undefined;
|
|
200
|
+
// Only a verified success may produce a cleanup entry, and it must
|
|
201
|
+
// bind the concrete landed project incarnation: replaying an
|
|
202
|
+
// unbound delete could remove a replacement project that reused the
|
|
203
|
+
// key. Never emit legacy/unbound project entries.
|
|
204
|
+
if (!usedProjectKey || !boundIncarnation)
|
|
205
|
+
return undefined;
|
|
206
|
+
return {
|
|
207
|
+
...base,
|
|
208
|
+
projectKey: usedProjectKey,
|
|
209
|
+
name: usedProjectKey,
|
|
210
|
+
cleanup: {
|
|
211
|
+
argv: [
|
|
212
|
+
"project",
|
|
213
|
+
"delete",
|
|
214
|
+
usedProjectKey,
|
|
215
|
+
"--if-exists",
|
|
216
|
+
"--expect-project-incarnation",
|
|
217
|
+
boundIncarnation,
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
193
222
|
case "app.create-instance":
|
|
194
223
|
case "app.create-successor-instance": {
|
|
195
224
|
const targetKey = stringField(record, ["projectKey", "targetProjectKey",]);
|
package/dist/src/cli/output.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ export declare function commandFailureExitCode(result: unknown): number | undefi
|
|
|
21
21
|
* Stable error code for a command-level failure report. A failed synchronous
|
|
22
22
|
* assertion (exit 4) is a distinct outcome from a failed long-running remote
|
|
23
23
|
* operation (also exit 4), so agents must never see `long_running_failure`
|
|
24
|
-
* for a result that finished synchronously with `unchanged: false
|
|
24
|
+
* for a result that finished synchronously with `unchanged: false` or
|
|
25
|
+
* `satisfied: false`.
|
|
25
26
|
*/
|
|
26
27
|
export declare function commandFailureCode(result: unknown, exitCode: number): StableErrorCode;
|
|
27
28
|
export declare class CommandResultFailure extends Error {
|
|
@@ -44,6 +45,7 @@ export declare function planResult(resource: string, action: string, options: {
|
|
|
44
45
|
payload?: unknown;
|
|
45
46
|
localWrites?: unknown;
|
|
46
47
|
plannedAndDryRun?: boolean;
|
|
48
|
+
requests?: unknown;
|
|
47
49
|
wait?: unknown;
|
|
48
50
|
}): Record<string, unknown>;
|
|
49
51
|
export declare function encodedProjectEndpoint(client: DataikuClient, projectKey: string | undefined, suffix: string): string;
|
package/dist/src/cli/output.js
CHANGED
|
@@ -87,10 +87,10 @@ export function isFailedWaitResult(result) {
|
|
|
87
87
|
&& (typeof record.state === "string" || typeof record.outcome === "string");
|
|
88
88
|
}
|
|
89
89
|
export function isAssertionFailureResult(result) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
if (result === null || typeof result !== "object" || Array.isArray(result))
|
|
91
|
+
return false;
|
|
92
|
+
const record = result;
|
|
93
|
+
return record.unchanged === false || record.satisfied === false;
|
|
94
94
|
}
|
|
95
95
|
function isNestedAssertionFailureResult(result) {
|
|
96
96
|
if (result === null || typeof result !== "object" || Array.isArray(result))
|
|
@@ -121,7 +121,8 @@ export function commandFailureExitCode(result) {
|
|
|
121
121
|
* Stable error code for a command-level failure report. A failed synchronous
|
|
122
122
|
* assertion (exit 4) is a distinct outcome from a failed long-running remote
|
|
123
123
|
* operation (also exit 4), so agents must never see `long_running_failure`
|
|
124
|
-
* for a result that finished synchronously with `unchanged: false
|
|
124
|
+
* for a result that finished synchronously with `unchanged: false` or
|
|
125
|
+
* `satisfied: false`.
|
|
125
126
|
*/
|
|
126
127
|
export function commandFailureCode(result, exitCode) {
|
|
127
128
|
if (isAssertionFailureResult(result) || isNestedAssertionFailureResult(result)) {
|
|
@@ -150,7 +151,10 @@ export function commandFailureMessage(result) {
|
|
|
150
151
|
return `Command completed with failed long-running result${state ? `: ${state}` : ""}.`;
|
|
151
152
|
}
|
|
152
153
|
if (isAssertionFailureResult(result)) {
|
|
153
|
-
|
|
154
|
+
const record = result;
|
|
155
|
+
return record.unchanged === false
|
|
156
|
+
? "Command completed with failed assertion result."
|
|
157
|
+
: "Command completed with unsatisfied assertion result.";
|
|
154
158
|
}
|
|
155
159
|
if (isNestedAssertionFailureResult(result)) {
|
|
156
160
|
return "Command completed with nested failed assertion result.";
|
|
@@ -188,6 +192,7 @@ export function planResult(resource, action, options) {
|
|
|
188
192
|
...(options.endpoint ? { endpoint: options.endpoint, } : {}),
|
|
189
193
|
...(options.payload !== undefined ? { payload: options.payload, } : {}),
|
|
190
194
|
...(options.localWrites !== undefined ? { localWrites: options.localWrites, } : {}),
|
|
195
|
+
...(options.requests !== undefined ? { requests: options.requests, } : {}),
|
|
191
196
|
...(options.wait !== undefined ? { wait: options.wait, } : {}),
|
|
192
197
|
idempotency: options.idempotency,
|
|
193
198
|
async: options.asyncKind,
|
package/dist/src/cli/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DataikuClient } from "../client.js";
|
|
2
2
|
export type CommandHandler = (client: DataikuClient, args: string[], flags: Record<string, string | boolean>) => Promise<unknown>;
|
|
3
|
+
export type LocalCommandHandler = (args: string[], flags: Record<string, string | boolean>) => Promise<unknown>;
|
|
3
4
|
export interface CommandPayloadSchema {
|
|
4
5
|
stdin?: boolean;
|
|
5
6
|
dataFlag?: boolean;
|
|
@@ -19,6 +20,7 @@ export interface CommandRegistryOverride {
|
|
|
19
20
|
}
|
|
20
21
|
export interface CommandMeta extends CommandRegistryOverride {
|
|
21
22
|
handler: CommandHandler;
|
|
23
|
+
localHandler?: LocalCommandHandler;
|
|
22
24
|
usage: string;
|
|
23
25
|
description?: string;
|
|
24
26
|
examples?: string[];
|
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
export declare function findPackageRoot(): string | undefined;
|
|
2
2
|
export declare function packageVersion(packageRoot: string | undefined): string;
|
|
3
3
|
export declare function gitDirectory(packageRoot: string): string;
|
|
4
|
+
export declare function isFullRevision(value: unknown): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Full checkout HEAD revision (40-lowercase-hex) — the basis for staleness
|
|
7
|
+
* comparison. Resolution follows Git's own layouts: a detached revision, then
|
|
8
|
+
* loose refs in the worktree git dir, then the shared store reached through
|
|
9
|
+
* `commondir`, then packed-refs in each. Anything that is not a full
|
|
10
|
+
* lowercase hexadecimal revision is rejected.
|
|
11
|
+
*/
|
|
12
|
+
export declare function gitFullRevision(packageRoot: string | undefined): string | undefined;
|
|
4
13
|
export declare function gitRevision(packageRoot: string | undefined): string | undefined;
|
|
14
|
+
export type CliLoadSource = "source" | "dist";
|
|
15
|
+
export type CliRuntime = "bun" | "node";
|
|
16
|
+
export declare function detectLoadSource(modulePath?: string): CliLoadSource;
|
|
17
|
+
export declare function buildMetadataRevision(packageRoot: string | undefined): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Whether compiled CLI output predates any source input. The build metadata
|
|
20
|
+
* file is written after `tsc`, so its mtime is the build-completion marker.
|
|
21
|
+
* Installed packages omit `src/`; without both sides there is no drift claim.
|
|
22
|
+
*/
|
|
23
|
+
export declare function sourceTreeNewerThanBuild(packageRoot: string | undefined): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* CLI provenance payload. `source` distinguishes a source checkout run from a
|
|
26
|
+
* published dist run; `runtime` names the executing runtime. `buildRevision`
|
|
27
|
+
* is only ever claimed by a dist build that carries build metadata — source
|
|
28
|
+
* execution never claims one. `staleBuild` is true only when the running dist
|
|
29
|
+
* build revision differs from the checkout or a source input is newer than the
|
|
30
|
+
* build-completion marker.
|
|
31
|
+
*/
|
|
32
|
+
export interface CliVersionPayload {
|
|
33
|
+
version: string;
|
|
34
|
+
gitRevision: string | null;
|
|
35
|
+
source: CliLoadSource;
|
|
36
|
+
runtime: CliRuntime;
|
|
37
|
+
buildRevision: string | null;
|
|
38
|
+
staleBuild: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function detectRuntime(): CliRuntime;
|
|
41
|
+
export declare function buildVersionPayload(input: {
|
|
42
|
+
packageVersion: string;
|
|
43
|
+
checkoutRevision: string | undefined;
|
|
44
|
+
checkoutFullRevision: string | undefined;
|
|
45
|
+
loadSource: string | undefined;
|
|
46
|
+
buildRevision: string | undefined;
|
|
47
|
+
sourceNewerThanBuild?: boolean;
|
|
48
|
+
runtime: CliRuntime;
|
|
49
|
+
}): CliVersionPayload;
|
|
5
50
|
export declare const PACKAGE_ROOT: string | undefined;
|
|
6
51
|
export declare const CLI_VERSION: string;
|
|
7
52
|
export declare const CLI_GIT_REVISION: string | undefined;
|
|
8
53
|
export declare const AGENT_CONTRACT_VERSION = 2;
|
|
9
54
|
export declare const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
10
55
|
export declare const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-v2.json";
|
|
11
|
-
export declare function cliVersionResult():
|
|
12
|
-
version: string;
|
|
13
|
-
gitRevision: string | null;
|
|
14
|
-
};
|
|
56
|
+
export declare function cliVersionResult(): CliVersionPayload;
|