dataiku-sdk 1.1.0 → 3.0.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/LICENSE +107 -17
- package/README.md +44 -10
- package/bin/dss.js +67 -97
- package/dist/build-metadata.json +1 -1
- package/dist/src/cli/commands/api-service.js +5 -4
- package/dist/src/cli/commands/app.js +45 -12
- package/dist/src/cli/commands/bundle.js +5 -4
- package/dist/src/cli/commands/project-library.js +4 -4
- package/dist/src/cli/contract.js +12 -2
- package/dist/src/cli/helpers/app-successor.d.ts +15 -3
- package/dist/src/cli/helpers/app-successor.js +155 -15
- package/dist/src/cli/version.d.ts +2 -1
- package/dist/src/cli/version.js +2 -1
- package/dist/src/cli.js +4 -1
- package/dist/src/client.js +2 -2
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/resources/folders.js +6 -8
- package/dist/src/resources/projects.js +1 -10
- package/dist/src/skill.js +1 -18
- package/dist/src/utils/project-archive.d.ts +1 -1
- package/dist/src/utils/project-archive.js +3 -4
- package/dist/src/utils/stable-hash.js +1 -2
- package/package.json +9 -10
- package/plugin.json +2 -2
- package/skills/dataiku-dss/SKILL.md +28 -36
package/dist/src/cli/contract.js
CHANGED
|
@@ -2315,6 +2315,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2315
2315
|
identifiers: {
|
|
2316
2316
|
appId: id,
|
|
2317
2317
|
targetProjectKey,
|
|
2318
|
+
preflightExecuted: false,
|
|
2319
|
+
preflightWillRunDuringApply: true,
|
|
2318
2320
|
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
2319
2321
|
incarnationObservationRequests: [
|
|
2320
2322
|
{
|
|
@@ -2337,7 +2339,13 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2337
2339
|
method: "GET",
|
|
2338
2340
|
endpoint: "/public/api/projects/",
|
|
2339
2341
|
when: "conditional",
|
|
2340
|
-
intent: "Fallback list probe issued only when the direct project GET is forbidden (403)
|
|
2342
|
+
intent: "Fallback list probe issued only when the direct project GET is forbidden (403). Presence proves a collision; absence cannot prove availability.",
|
|
2343
|
+
},
|
|
2344
|
+
{
|
|
2345
|
+
method: "GET",
|
|
2346
|
+
endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
|
|
2347
|
+
when: "conditional",
|
|
2348
|
+
intent: "Fallback app-instance list probe issued when the direct project GET is forbidden (403). Presence proves a collision; absence still rejects creation as unverifiable.",
|
|
2341
2349
|
},
|
|
2342
2350
|
],
|
|
2343
2351
|
note: "Strict preflight: the instance POST runs only after the payload targetProjectKey is confirmed absent. Creation never writes into an existing or unprovable project.",
|
|
@@ -2399,6 +2407,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2399
2407
|
appId: id,
|
|
2400
2408
|
sourceProjectKey,
|
|
2401
2409
|
targetProjectKey,
|
|
2410
|
+
preflightExecuted: false,
|
|
2411
|
+
preflightWillRunDuringApply: true,
|
|
2402
2412
|
...(targetProjectName !== undefined ? { targetProjectName, } : {}),
|
|
2403
2413
|
copyPermissions,
|
|
2404
2414
|
incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
|
|
@@ -2437,7 +2447,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
2437
2447
|
method: "GET",
|
|
2438
2448
|
endpoint: "/public/api/projects/",
|
|
2439
2449
|
when: "conditional",
|
|
2440
|
-
intent: "Fallback list probe issued only when the direct project GET is forbidden (403)
|
|
2450
|
+
intent: "Fallback list probe issued only when the direct project GET is forbidden (403). Presence proves a collision; absence cannot prove availability and rejects creation.",
|
|
2441
2451
|
},
|
|
2442
2452
|
],
|
|
2443
2453
|
...(copyPermissions
|
|
@@ -87,12 +87,22 @@ export declare function futureTargetIdentity(result: unknown): {
|
|
|
87
87
|
projectKey: string;
|
|
88
88
|
field: "targetProjectKey" | "projectKey";
|
|
89
89
|
} | null;
|
|
90
|
+
interface TargetAbsenceContext {
|
|
91
|
+
appId: string;
|
|
92
|
+
visibleAppInstanceKeys?: readonly (string | undefined)[];
|
|
93
|
+
}
|
|
90
94
|
/**
|
|
91
95
|
* Creation cleanup is safe only when the target was proven absent before POST.
|
|
92
96
|
* A hidden 403 is not absence: project listing can prove a collision, but it
|
|
93
97
|
* cannot prove that an inaccessible project does not exist.
|
|
94
98
|
*/
|
|
95
|
-
export declare function requireAbsentProjectTarget(client: DataikuClient, targetProjectKey: string, targetFlag: "--to" | "targetProjectKey"): Promise<void>;
|
|
99
|
+
export declare function requireAbsentProjectTarget(client: DataikuClient, targetProjectKey: string, targetFlag: "--to" | "targetProjectKey", context: TargetAbsenceContext): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Run every read-only gate used by successor creation without changing the
|
|
102
|
+
* template version or issuing the creation POST. Its manifest hash is suitable
|
|
103
|
+
* for the subsequent set-manifest-version --expect-hash guard.
|
|
104
|
+
*/
|
|
105
|
+
export declare function preflightSuccessorInstance(client: DataikuClient, request: SuccessorInstanceRequest, usage: string): Promise<Record<string, unknown>>;
|
|
96
106
|
/**
|
|
97
107
|
* Create a new app instance from the current template version alongside an
|
|
98
108
|
* existing instance. Classic app instances have no public upgrade endpoint, so
|
|
@@ -100,7 +110,9 @@ export declare function requireAbsentProjectTarget(client: DataikuClient, target
|
|
|
100
110
|
* and deliberately retired.
|
|
101
111
|
*
|
|
102
112
|
* After the creation POST this function never throws. A post-POST failure
|
|
103
|
-
* returns a failed-wait result
|
|
104
|
-
*
|
|
113
|
+
* returns a failed-wait result. Only a DSS future ID or verified project
|
|
114
|
+
* incarnation can authorize cleanup; an ambiguous POST with neither stays
|
|
115
|
+
* indeterminate and produces no cleanup entry.
|
|
105
116
|
*/
|
|
106
117
|
export declare function createSuccessorInstance(client: DataikuClient, request: SuccessorInstanceRequest, usage: string): Promise<Record<string, unknown>>;
|
|
118
|
+
export {};
|
|
@@ -150,6 +150,7 @@ export async function verifyAppInstance(client, appId, projectKey, expectVersion
|
|
|
150
150
|
}
|
|
151
151
|
const SUCCESSOR_REMEDIATION = "The successor project may exist: replay the recorded cleanup entry, which future-gates deletion when DSS supplied a future ID. Without a cleanup entry, inspect the creation future and wait for it to become terminal before deleting the requested successor project. The predecessor project was not modified.";
|
|
152
152
|
const CREATE_REJECTED_REMEDIATION = "DSS refused the creation request, so no successor project was created: fix the reported problem and retry. The predecessor project was not modified.";
|
|
153
|
+
const SUCCESSOR_UNBOUND_REMEDIATION = "DSS may have accepted the successor request, but no future ID or project incarnation is available to authorize cleanup. Inspect the app's instances and DSS task history; do not delete the requested key until creation is known terminal and its identity is verified. The predecessor project was not modified.";
|
|
153
154
|
/**
|
|
154
155
|
* True only when DSS definitively refused a write: a non-retryable 4xx with a
|
|
155
156
|
* classified client-side taxonomy. Transport failures (status 0), 5xx, retryable
|
|
@@ -186,7 +187,19 @@ export function futureTargetIdentity(result) {
|
|
|
186
187
|
* A hidden 403 is not absence: project listing can prove a collision, but it
|
|
187
188
|
* cannot prove that an inaccessible project does not exist.
|
|
188
189
|
*/
|
|
189
|
-
export async function requireAbsentProjectTarget(client, targetProjectKey, targetFlag) {
|
|
190
|
+
export async function requireAbsentProjectTarget(client, targetProjectKey, targetFlag, context) {
|
|
191
|
+
if (context.visibleAppInstanceKeys?.includes(targetProjectKey) === true) {
|
|
192
|
+
throw new UsageError(`Target project ${targetProjectKey} already exists as an app instance.`, "validation_failed", `Choose an unused ${targetFlag}: app instance creation never writes into an existing project.`, {
|
|
193
|
+
targetProjectKey,
|
|
194
|
+
targetFlag,
|
|
195
|
+
directTargetProbe: "not-executed",
|
|
196
|
+
targetVisibleInProjectList: null,
|
|
197
|
+
targetVisibleInAppInstances: true,
|
|
198
|
+
appInstancesProbe: 200,
|
|
199
|
+
preflightExecuted: true,
|
|
200
|
+
creationPostAttempted: false,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
190
203
|
try {
|
|
191
204
|
const existing = await readIfExists(() => client.projects.get(targetProjectKey));
|
|
192
205
|
if (existing === undefined)
|
|
@@ -195,10 +208,61 @@ export async function requireAbsentProjectTarget(client, targetProjectKey, targe
|
|
|
195
208
|
catch (error) {
|
|
196
209
|
if (!(error instanceof DataikuError) || error.status !== 403)
|
|
197
210
|
throw error;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
211
|
+
let projectListProbe = 200;
|
|
212
|
+
let targetVisibleInProjectList;
|
|
213
|
+
try {
|
|
214
|
+
const accessible = await client.projects.list();
|
|
215
|
+
targetVisibleInProjectList = accessible.some((project) => project.projectKey === targetProjectKey);
|
|
216
|
+
}
|
|
217
|
+
catch (projectListError) {
|
|
218
|
+
if (!(projectListError instanceof DataikuError) || projectListError.status !== 403) {
|
|
219
|
+
throw projectListError;
|
|
220
|
+
}
|
|
221
|
+
projectListProbe = 403;
|
|
222
|
+
targetVisibleInProjectList = null;
|
|
201
223
|
}
|
|
224
|
+
let appInstancesProbe = 200;
|
|
225
|
+
let visibleAppInstanceKeys = context.visibleAppInstanceKeys;
|
|
226
|
+
if (visibleAppInstanceKeys === undefined) {
|
|
227
|
+
try {
|
|
228
|
+
visibleAppInstanceKeys = (await client.applications.listInstances(context.appId))
|
|
229
|
+
.map((instance) => instance.projectKey);
|
|
230
|
+
}
|
|
231
|
+
catch (appInstancesError) {
|
|
232
|
+
if (!(appInstancesError instanceof DataikuError) || appInstancesError.status !== 403) {
|
|
233
|
+
throw appInstancesError;
|
|
234
|
+
}
|
|
235
|
+
appInstancesProbe = 403;
|
|
236
|
+
visibleAppInstanceKeys = [];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const targetVisibleInAppInstances = appInstancesProbe === 200
|
|
240
|
+
? visibleAppInstanceKeys?.includes(targetProjectKey) ?? false
|
|
241
|
+
: null;
|
|
242
|
+
const probeDetails = {
|
|
243
|
+
targetProjectKey,
|
|
244
|
+
targetFlag,
|
|
245
|
+
directTargetProbe: 403,
|
|
246
|
+
projectListProbe,
|
|
247
|
+
targetVisibleInProjectList,
|
|
248
|
+
targetVisibleInAppInstances,
|
|
249
|
+
appInstancesProbe,
|
|
250
|
+
preflightExecuted: true,
|
|
251
|
+
creationPostAttempted: false,
|
|
252
|
+
};
|
|
253
|
+
if (targetVisibleInProjectList !== true && targetVisibleInAppInstances !== true) {
|
|
254
|
+
throw new UsageError(`Could not confirm target project ${targetProjectKey} is absent.`, "target_absence_unverifiable", "DSS exposes no permission-independent project-key availability endpoint, and app-instance creation has no published atomic duplicate-key guarantee. Retry with an identity that has global project visibility; creation was not attempted.", {
|
|
255
|
+
...probeDetails,
|
|
256
|
+
targetProbe: "forbidden-and-not-listable",
|
|
257
|
+
deploymentMasksUnknownProjects: "possible-but-unproven",
|
|
258
|
+
supportedRecoveryModes: ["grant-global-project-visibility",],
|
|
259
|
+
unavailableRecoveryModes: [
|
|
260
|
+
"use-supported-key-availability-endpoint",
|
|
261
|
+
"server-atomic-create",
|
|
262
|
+
],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
throw new UsageError(`Target project ${targetProjectKey} already exists.`, "validation_failed", `Choose an unused ${targetFlag}: app instance creation never writes into an existing project.`, probeDetails);
|
|
202
266
|
}
|
|
203
267
|
throw new UsageError(`Target project ${targetProjectKey} already exists.`, "validation_failed", `Choose an unused ${targetFlag}: app instance creation never writes into an existing project.`, { targetProjectKey, targetFlag, });
|
|
204
268
|
}
|
|
@@ -226,12 +290,26 @@ async function preflightSuccessor(client, request, usage) {
|
|
|
226
290
|
if (source.projectAppType !== "APP_INSTANCE") {
|
|
227
291
|
throw new UsageError(`Project ${from} is a ${source.projectAppType ?? "non-app"} project, not a classic Dataiku App instance.`, "validation_failed", "--from must name an APP_INSTANCE project; app templates are versioned with `dss app set-manifest-version`.", { sourceProjectKey: from, projectAppType: source.projectAppType ?? null, });
|
|
228
292
|
}
|
|
229
|
-
await requireAbsentProjectTarget(client, to, "--to"
|
|
293
|
+
await requireAbsentProjectTarget(client, to, "--to", {
|
|
294
|
+
appId,
|
|
295
|
+
visibleAppInstanceKeys: instances.map((instance) => instance.projectKey),
|
|
296
|
+
});
|
|
230
297
|
const templateManifest = await client.applications.getAppManifest(appId);
|
|
231
298
|
const templateVersion = rawVersion(templateManifest);
|
|
232
299
|
if (templateVersion === null) {
|
|
233
300
|
throw new UsageError(`App template ${appId} has no manifest version, so a successor instance cannot be verified against it.`, "validation_failed", "Set the template version first with `dss app set-manifest-version --manifest-version V --project-key <templateProjectKey>`.", { appId, });
|
|
234
301
|
}
|
|
302
|
+
const templateReferenceValidation = await client.applications.validateAppManifest(templateManifest, appId);
|
|
303
|
+
if (!templateReferenceValidation.valid) {
|
|
304
|
+
throw new UsageError(`App template ${appId} failed manifest reference validation.`, "validation_failed", "Fix the template manifest references before changing its version or creating a successor.", {
|
|
305
|
+
appId,
|
|
306
|
+
manifestHash: templateReferenceValidation.manifestHash,
|
|
307
|
+
checks: templateReferenceValidation.checks,
|
|
308
|
+
errors: templateReferenceValidation.errors,
|
|
309
|
+
preflightExecuted: true,
|
|
310
|
+
creationPostAttempted: false,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
235
313
|
const sourcePermissions = request.copyPermissions
|
|
236
314
|
? await client.projects.getPermissions(from)
|
|
237
315
|
: undefined;
|
|
@@ -243,6 +321,7 @@ async function preflightSuccessor(client, request, usage) {
|
|
|
243
321
|
targetExists: false,
|
|
244
322
|
templateManifestHash: stableHash(templateManifest),
|
|
245
323
|
templateVersion,
|
|
324
|
+
templateReferenceValidation,
|
|
246
325
|
...(sourcePermissions
|
|
247
326
|
? {
|
|
248
327
|
sourcePermissions,
|
|
@@ -251,6 +330,52 @@ async function preflightSuccessor(client, request, usage) {
|
|
|
251
330
|
: {}),
|
|
252
331
|
};
|
|
253
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Run every read-only gate used by successor creation without changing the
|
|
335
|
+
* template version or issuing the creation POST. Its manifest hash is suitable
|
|
336
|
+
* for the subsequent set-manifest-version --expect-hash guard.
|
|
337
|
+
*/
|
|
338
|
+
export async function preflightSuccessorInstance(client, request, usage) {
|
|
339
|
+
const preflight = await preflightSuccessor(client, request, usage);
|
|
340
|
+
return {
|
|
341
|
+
action: "successor-preflight",
|
|
342
|
+
preflight: "passed",
|
|
343
|
+
preflightExecuted: true,
|
|
344
|
+
creationPostAttempted: false,
|
|
345
|
+
versionMutationAttempted: false,
|
|
346
|
+
appId: request.appId,
|
|
347
|
+
source: {
|
|
348
|
+
projectKey: request.from,
|
|
349
|
+
projectAppType: preflight.sourceProjectAppType,
|
|
350
|
+
version: preflight.sourceVersion,
|
|
351
|
+
manifestHash: preflight.sourceManifestHash,
|
|
352
|
+
...(preflight.sourcePermissionsHash
|
|
353
|
+
? { permissionsHash: preflight.sourcePermissionsHash, }
|
|
354
|
+
: {}),
|
|
355
|
+
},
|
|
356
|
+
target: {
|
|
357
|
+
projectKey: request.to,
|
|
358
|
+
name: preflight.targetProjectName,
|
|
359
|
+
exists: false,
|
|
360
|
+
},
|
|
361
|
+
targetPreflight: "confirmed-absent",
|
|
362
|
+
template: {
|
|
363
|
+
projectKey: request.appId,
|
|
364
|
+
version: preflight.templateVersion,
|
|
365
|
+
manifestHash: preflight.templateManifestHash,
|
|
366
|
+
referenceValidation: {
|
|
367
|
+
valid: preflight.templateReferenceValidation.valid,
|
|
368
|
+
checks: preflight.templateReferenceValidation.checks,
|
|
369
|
+
errors: preflight.templateReferenceValidation.errors,
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
copyPermissions: request.copyPermissions,
|
|
373
|
+
next: {
|
|
374
|
+
setVersion: `dss app set-manifest-version --manifest-version <VERSION> --expect-hash ${preflight.templateManifestHash} --project-key ${request.appId}`,
|
|
375
|
+
createSuccessor: `dss app create-successor-instance ${request.appId} --from ${request.from} --to ${request.to}${request.name !== undefined ? ` --name ${JSON.stringify(request.name)}` : ""}${request.copyPermissions ? " --copy-permissions" : ""}`,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
254
379
|
/**
|
|
255
380
|
* Create a new app instance from the current template version alongside an
|
|
256
381
|
* existing instance. Classic app instances have no public upgrade endpoint, so
|
|
@@ -258,8 +383,9 @@ async function preflightSuccessor(client, request, usage) {
|
|
|
258
383
|
* and deliberately retired.
|
|
259
384
|
*
|
|
260
385
|
* After the creation POST this function never throws. A post-POST failure
|
|
261
|
-
* returns a failed-wait result
|
|
262
|
-
*
|
|
386
|
+
* returns a failed-wait result. Only a DSS future ID or verified project
|
|
387
|
+
* incarnation can authorize cleanup; an ambiguous POST with neither stays
|
|
388
|
+
* indeterminate and produces no cleanup entry.
|
|
263
389
|
*/
|
|
264
390
|
export async function createSuccessorInstance(client, request, usage) {
|
|
265
391
|
const { appId, from, to, } = request;
|
|
@@ -312,12 +438,17 @@ export async function createSuccessorInstance(client, request, usage) {
|
|
|
312
438
|
projectKey: to,
|
|
313
439
|
target: { projectKey: to, name: preflight.targetProjectName, },
|
|
314
440
|
success: false,
|
|
315
|
-
state: "CREATE_FAILED",
|
|
441
|
+
state: rejected ? "CREATE_FAILED" : "INDETERMINATE",
|
|
316
442
|
elapsedMs: 0,
|
|
317
443
|
pollCount: 0,
|
|
318
444
|
stage: "create",
|
|
319
|
-
|
|
320
|
-
|
|
445
|
+
outcome: rejected ? "rejected" : "indeterminate",
|
|
446
|
+
creationPostAttempted: true,
|
|
447
|
+
cleanupEligible: false,
|
|
448
|
+
recoveryCommands: rejected
|
|
449
|
+
? []
|
|
450
|
+
: [`dss app instances ${appId}`,],
|
|
451
|
+
remediation: rejected ? CREATE_REJECTED_REMEDIATION : SUCCESSOR_UNBOUND_REMEDIATION,
|
|
321
452
|
...safeErrorSummary(error),
|
|
322
453
|
};
|
|
323
454
|
}
|
|
@@ -342,11 +473,13 @@ export async function createSuccessorInstance(client, request, usage) {
|
|
|
342
473
|
stage: "create",
|
|
343
474
|
...(instance ? { instance, } : {}),
|
|
344
475
|
...(jobId !== undefined ? { futureId: jobId, jobId, } : {}),
|
|
345
|
-
cleanupEligible:
|
|
476
|
+
cleanupEligible: jobId !== undefined,
|
|
346
477
|
error: "DSS named a different target project in the creation response.",
|
|
347
478
|
expected: { projectKey: to, },
|
|
348
479
|
actual: { projectKey: echoedTarget, },
|
|
349
|
-
remediation:
|
|
480
|
+
remediation: jobId !== undefined
|
|
481
|
+
? SUCCESSOR_REMEDIATION
|
|
482
|
+
: SUCCESSOR_UNBOUND_REMEDIATION,
|
|
350
483
|
};
|
|
351
484
|
}
|
|
352
485
|
const base = {
|
|
@@ -381,13 +514,16 @@ export async function createSuccessorInstance(client, request, usage) {
|
|
|
381
514
|
: "scalar",
|
|
382
515
|
}),
|
|
383
516
|
success: false,
|
|
384
|
-
state: "
|
|
517
|
+
state: "INDETERMINATE",
|
|
385
518
|
elapsedMs: 0,
|
|
386
519
|
pollCount: 0,
|
|
387
520
|
stage: "future-wait",
|
|
388
|
-
|
|
521
|
+
outcome: "indeterminate",
|
|
522
|
+
creationPostAttempted: true,
|
|
523
|
+
cleanupEligible: false,
|
|
389
524
|
error: "DSS returned no instance-creation future ID, so the successor instance cannot be verified.",
|
|
390
|
-
remediation:
|
|
525
|
+
remediation: SUCCESSOR_UNBOUND_REMEDIATION,
|
|
526
|
+
recoveryCommands: [`dss app instances ${appId}`,],
|
|
391
527
|
};
|
|
392
528
|
}
|
|
393
529
|
else {
|
|
@@ -408,6 +544,7 @@ export async function createSuccessorInstance(client, request, usage) {
|
|
|
408
544
|
pollCount: 0,
|
|
409
545
|
stage: "future-wait",
|
|
410
546
|
cleanupEligible: true,
|
|
547
|
+
recoveryCommands: [`dss future wait ${jobId}`, `dss app instances ${appId}`,],
|
|
411
548
|
remediation: SUCCESSOR_REMEDIATION,
|
|
412
549
|
...safeErrorSummary(error),
|
|
413
550
|
};
|
|
@@ -451,6 +588,9 @@ export async function createSuccessorInstance(client, request, usage) {
|
|
|
451
588
|
if (!waited.success) {
|
|
452
589
|
return failure(waited.state, "future-wait", {
|
|
453
590
|
error: "The instance-creation future did not complete successfully.",
|
|
591
|
+
recoveryCommands: jobId !== undefined
|
|
592
|
+
? [`dss future wait ${jobId}`, `dss app instances ${appId}`,]
|
|
593
|
+
: [`dss app instances ${appId}`,],
|
|
454
594
|
});
|
|
455
595
|
}
|
|
456
596
|
try {
|
|
@@ -12,7 +12,7 @@ export declare function isFullRevision(value: unknown): boolean;
|
|
|
12
12
|
export declare function gitFullRevision(packageRoot: string | undefined): string | undefined;
|
|
13
13
|
export declare function gitRevision(packageRoot: string | undefined): string | undefined;
|
|
14
14
|
export type CliLoadSource = "source" | "dist";
|
|
15
|
-
export type CliRuntime = "bun"
|
|
15
|
+
export type CliRuntime = "bun";
|
|
16
16
|
export declare function detectLoadSource(modulePath?: string): CliLoadSource;
|
|
17
17
|
export declare function buildMetadataRevision(packageRoot: string | undefined): string | undefined;
|
|
18
18
|
/**
|
|
@@ -37,6 +37,7 @@ 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. */
|
|
40
41
|
export declare function detectRuntime(): CliRuntime;
|
|
41
42
|
export declare function buildVersionPayload(input: {
|
|
42
43
|
packageVersion: string;
|
package/dist/src/cli/version.js
CHANGED
|
@@ -182,8 +182,9 @@ 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. */
|
|
185
186
|
export function detectRuntime() {
|
|
186
|
-
return
|
|
187
|
+
return "bun";
|
|
187
188
|
}
|
|
188
189
|
export function buildVersionPayload(input) {
|
|
189
190
|
const isDist = input.loadSource === "dist";
|
package/dist/src/cli.js
CHANGED
|
@@ -1241,8 +1241,11 @@ function buildErrorReport(err) {
|
|
|
1241
1241
|
ok: false,
|
|
1242
1242
|
error: err.message,
|
|
1243
1243
|
code: err.code,
|
|
1244
|
-
category: "
|
|
1244
|
+
category: err.code === "target_absence_unverifiable"
|
|
1245
|
+
? "permission_or_environment"
|
|
1246
|
+
: "usage",
|
|
1245
1247
|
exitCode,
|
|
1248
|
+
...(err.code === "target_absence_unverifiable" ? { retryable: false, } : {}),
|
|
1246
1249
|
...(err.hint ? { hint: err.hint, } : {}),
|
|
1247
1250
|
...(err.details ? { details: err.details, } : {}),
|
|
1248
1251
|
...context,
|
package/dist/src/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync, } from "node:fs";
|
|
2
2
|
import { basename, } from "node:path";
|
|
3
3
|
import { getCACertificates, } from "node:tls";
|
|
4
4
|
import { Value, } from "@sinclair/typebox/value";
|
|
@@ -479,7 +479,7 @@ export class DataikuClient {
|
|
|
479
479
|
});
|
|
480
480
|
}
|
|
481
481
|
async uploadResponse(path, filePath, fileName) {
|
|
482
|
-
const fileBlob =
|
|
482
|
+
const fileBlob = Bun.file(filePath);
|
|
483
483
|
const formData = new FormData();
|
|
484
484
|
formData.append("file", fileBlob, fileName ?? basename(filePath));
|
|
485
485
|
return this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type DataikuErrorCategory = "not_found" | "forbidden" | "validation" | "transient" | "unknown";
|
|
2
|
-
export type StableErrorCode = "usage_error" | "unknown_flag" | "missing_required_arg" | "missing_required_flag" | "invalid_flag_value" | "conflicting_input_sources" | "invalid_enum" | "not_found" | "permission_denied" | "validation_failed" | "transient" | "long_running_failure" | "command_result_failure" | "assertion_failed" | "ambiguous_outcome" | "internal_error";
|
|
2
|
+
export type StableErrorCode = "usage_error" | "unknown_flag" | "missing_required_arg" | "missing_required_flag" | "invalid_flag_value" | "conflicting_input_sources" | "invalid_enum" | "not_found" | "permission_denied" | "target_absence_unverifiable" | "validation_failed" | "transient" | "long_running_failure" | "command_result_failure" | "assertion_failed" | "ambiguous_outcome" | "internal_error";
|
|
3
3
|
export declare function dataikuErrorCode(category: DataikuErrorCategory): StableErrorCode;
|
|
4
4
|
export interface DataikuErrorTaxonomy {
|
|
5
5
|
category: DataikuErrorCategory;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { resolve, } from "node:path";
|
|
3
|
-
import { Readable, } from "node:stream";
|
|
4
|
-
import { pipeline, } from "node:stream/promises";
|
|
1
|
+
import * as nodePath from "node:path";
|
|
5
2
|
import { FolderDetailsSchema, FolderItemArraySchema, FolderSummaryArraySchema, } from "../schemas.js";
|
|
6
3
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
7
4
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
@@ -72,10 +69,11 @@ export class FoldersResource extends BaseResource {
|
|
|
72
69
|
const normalizedPath = normalizeRemotePath(path);
|
|
73
70
|
const pEnc = encodeURIComponent(normalizedPath);
|
|
74
71
|
const res = await this.client.stream(`/public/api/projects/${this.enc(opts?.projectKey)}/managedfolders/${fEnc}/contents/${pEnc}`);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
72
|
+
if (!res.body)
|
|
73
|
+
throw new Error("folders.download response did not include a body");
|
|
74
|
+
const dest = opts?.localPath
|
|
75
|
+
?? nodePath.resolve(process.cwd(), inferDownloadFileName(normalizedPath));
|
|
76
|
+
await Bun.write(dest, new Response(res.body), { createPath: false, });
|
|
79
77
|
return dest;
|
|
80
78
|
}
|
|
81
79
|
upload(folderId, path, localPath, projectKey) {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createWriteStream, } from "node:fs";
|
|
2
|
-
import { Writable, } from "node:stream";
|
|
3
1
|
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
4
2
|
import { ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, } from "../schemas.js";
|
|
5
3
|
import { normalizeFlowGraph, } from "../utils/flow-map.js";
|
|
@@ -159,14 +157,7 @@ export class ProjectsResource extends BaseResource {
|
|
|
159
157
|
const res = await this.postStream(`/public/api/projects/${encodeURIComponent(projectKey)}/export`, options ?? {});
|
|
160
158
|
if (!res.body)
|
|
161
159
|
throw new Error("projects.exportArchive response did not include a body");
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
await res.body.pipeTo(Writable.toWeb(file));
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
file.destroy();
|
|
168
|
-
throw error;
|
|
169
|
-
}
|
|
160
|
+
await Bun.write(filePath, new Response(res.body), { createPath: false, });
|
|
170
161
|
}
|
|
171
162
|
/** Upload a project archive and return its temporary import handle. */
|
|
172
163
|
async prepareProjectImport(filePath) {
|
package/dist/src/skill.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { execFileSync, } from "node:child_process";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
@@ -61,24 +60,11 @@ export const AGENTS = {
|
|
|
61
60
|
content: skillContent,
|
|
62
61
|
},
|
|
63
62
|
};
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Agent detection
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
function binaryExists(name) {
|
|
68
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
69
|
-
try {
|
|
70
|
-
execFileSync(cmd, [name,], { stdio: "pipe", });
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
63
|
export function detectAgents() {
|
|
78
64
|
const home = os.homedir();
|
|
79
65
|
const found = [];
|
|
80
66
|
for (const [id, def,] of Object.entries(AGENTS)) {
|
|
81
|
-
const hasBinary =
|
|
67
|
+
const hasBinary = Bun.which(def.binary) !== null;
|
|
82
68
|
const hasConfigDir = fs.existsSync(path.join(home, def.configDir));
|
|
83
69
|
if (hasBinary && (!def.configDirRequired || hasConfigDir)) {
|
|
84
70
|
found.push({ id, def, via: "binary", });
|
|
@@ -90,9 +76,6 @@ export function detectAgents() {
|
|
|
90
76
|
return found;
|
|
91
77
|
}
|
|
92
78
|
// ---------------------------------------------------------------------------
|
|
93
|
-
// Skill installation
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
79
|
// Workspace root detection
|
|
97
80
|
// ---------------------------------------------------------------------------
|
|
98
81
|
const WORKSPACE_MARKERS = [".git",];
|
|
@@ -17,7 +17,7 @@ export interface ProjectArchiveInspection {
|
|
|
17
17
|
* Inspect a DSS project export ZIP archive without extracting anything to
|
|
18
18
|
* disk: enumerate every central-directory record, validate member paths
|
|
19
19
|
* independently, stream every file member through a size/CRC-32 check with
|
|
20
|
-
* bounded memory (manual `
|
|
20
|
+
* bounded memory (manual `Bun.hash.crc32`, covering data-descriptor
|
|
21
21
|
* records), parse `export-manifest.json` and the source project key, and run
|
|
22
22
|
* the generic dangling-reference/orphan-member graph checks. Identity,
|
|
23
23
|
* history and machine-specific residue is reported only as warnings carrying
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import StreamZip from "node-stream-zip";
|
|
2
2
|
import { stat, } from "node:fs/promises";
|
|
3
|
-
import { crc32, } from "node:zlib";
|
|
4
3
|
import { ClientValidationError, } from "../errors.js";
|
|
5
4
|
const MANIFEST_MEMBER = "export-manifest.json";
|
|
6
5
|
const DATASETS_ROOT = "project_config/datasets";
|
|
@@ -106,7 +105,7 @@ function isUnsafeMemberPath(name) {
|
|
|
106
105
|
/**
|
|
107
106
|
* Stream one member through a bounded-memory CRC-32/size check. The library's
|
|
108
107
|
* own verification is bypassed for data-descriptor records (bit 3 set), so the
|
|
109
|
-
* manual `
|
|
108
|
+
* manual `Bun.hash.crc32` computation covers every record uniformly.
|
|
110
109
|
*
|
|
111
110
|
* When `accumulate` is set, buffered content never exceeds `maxBytes`: the
|
|
112
111
|
* buffer is dropped as soon as the cap is reached while the member is still
|
|
@@ -130,7 +129,7 @@ function streamMember(zip, entry, accumulate, maxBytes = Number.POSITIVE_INFINIT
|
|
|
130
129
|
stream.on("data", (chunk) => {
|
|
131
130
|
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
132
131
|
bytes += buffer.length;
|
|
133
|
-
crc = crc32(buffer, crc) >>> 0;
|
|
132
|
+
crc = Bun.hash.crc32(buffer, crc) >>> 0;
|
|
134
133
|
if (!content || truncated)
|
|
135
134
|
return;
|
|
136
135
|
if (bytes > maxBytes) {
|
|
@@ -307,7 +306,7 @@ function residueIssue(member, counts) {
|
|
|
307
306
|
* Inspect a DSS project export ZIP archive without extracting anything to
|
|
308
307
|
* disk: enumerate every central-directory record, validate member paths
|
|
309
308
|
* independently, stream every file member through a size/CRC-32 check with
|
|
310
|
-
* bounded memory (manual `
|
|
309
|
+
* bounded memory (manual `Bun.hash.crc32`, covering data-descriptor
|
|
311
310
|
* records), parse `export-manifest.json` and the source project key, and run
|
|
312
311
|
* the generic dangling-reference/orphan-member graph checks. Identity,
|
|
313
312
|
* history and machine-specific residue is reported only as warnings carrying
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { createHash, } from "node:crypto";
|
|
2
1
|
export function sha256Hex(value) {
|
|
3
|
-
return
|
|
2
|
+
return new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
4
3
|
}
|
|
5
4
|
/** Locale-independent UTF-16 code-unit order for deterministic machine output. */
|
|
6
5
|
export function compareStrings(a, b) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dataiku-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Dataiku DSS SDK and CLI for programmatic access to DSS REST APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -19,15 +19,14 @@
|
|
|
19
19
|
"packages/types/dist/"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"
|
|
23
|
-
"bun": ">=1.3.14"
|
|
22
|
+
"bun": ">=1.4.0"
|
|
24
23
|
},
|
|
25
24
|
"scripts": {
|
|
26
|
-
"build": "tsc &&
|
|
27
|
-
"prepack": "
|
|
28
|
-
"prepublishOnly": "
|
|
29
|
-
"check": "tsc --noEmit &&
|
|
30
|
-
"version": "
|
|
25
|
+
"build": "tsc && bun scripts/generate-build-metadata.mjs && tsc -p packages/types/tsconfig.json",
|
|
26
|
+
"prepack": "bun scripts/sync-plugin-version.mjs --check && tsc && bun scripts/generate-build-metadata.mjs && tsc -p packages/types/tsconfig.json",
|
|
27
|
+
"prepublishOnly": "bun scripts/sync-plugin-version.mjs --check && tsc && bun scripts/generate-build-metadata.mjs && tsc -p packages/types/tsconfig.json",
|
|
28
|
+
"check": "tsc --noEmit && bun scripts/sync-plugin-version.mjs --check",
|
|
29
|
+
"version": "bun scripts/sync-plugin-version.mjs && git add plugin.json",
|
|
31
30
|
"lint": "oxlint src/",
|
|
32
31
|
"lint:fix": "oxlint --fix src/",
|
|
33
32
|
"format": "dprint fmt",
|
|
@@ -41,7 +40,7 @@
|
|
|
41
40
|
"test:integration:rigorous:mutating": "cross-env RUN_DATAIKU_INTEGRATION=1 RUN_DATAIKU_INTEGRATION_MUTATING=1 bun test tests/integration-rigorous.test.ts"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
|
-
"@types/bun": "^1.
|
|
43
|
+
"@types/bun": "^1.4.0",
|
|
45
44
|
"cross-env": "^10.1.0",
|
|
46
45
|
"dprint": "^0.55.2",
|
|
47
46
|
"js-tiktoken": "1.0.21",
|
|
@@ -57,7 +56,7 @@
|
|
|
57
56
|
"bundledDependencies": [
|
|
58
57
|
"@sinclair/typebox"
|
|
59
58
|
],
|
|
60
|
-
"license": "
|
|
59
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
61
60
|
"repository": {
|
|
62
61
|
"type": "git",
|
|
63
62
|
"url": "https://github.com/clssck/dataiku-sdk.git"
|
package/plugin.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "dataiku-sdk",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"description": "Dataiku DSS SDK and agent-oriented CLI for programmatic access to DSS REST APIs.",
|
|
6
6
|
"homepage": "https://github.com/clssck/dataiku-sdk",
|
|
7
7
|
"repository": "https://github.com/clssck/dataiku-sdk",
|
|
8
|
-
"license": "
|
|
8
|
+
"license": "LicenseRef-Dataiku-SDK-Limited-Use-1.0",
|
|
9
9
|
"keywords": [
|
|
10
10
|
"dataiku",
|
|
11
11
|
"dss",
|