@miosa/sdk 1.2.3 → 1.2.5
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 +63 -39
- package/dist/index.d.ts +201 -182
- package/dist/index.js +433 -309
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +5 -5
- package/src/index.ts +16 -11
- package/src/resources/admin.ts +0 -11
- package/src/resources/api-keys.ts +0 -16
- package/src/resources/custom_domains.ts +1 -1
- package/src/resources/deployments.test.ts +127 -51
- package/src/resources/deployments.ts +326 -125
- package/src/resources/devices.test.ts +92 -0
- package/src/resources/devices.ts +291 -0
- package/src/resources/sandboxes.test.ts +2 -0
- package/src/resources/sandboxes.ts +4 -0
- package/src/resources/tenant.ts +19 -101
- package/src/resources/webhooks.ts +54 -39
- package/src/types.ts +5 -5
- package/src/resources/docker-deploy.test.ts +0 -102
- package/src/resources/docker-deploy.ts +0 -183
- package/src/resources/governance.test.ts +0 -355
- package/src/resources/governance.ts +0 -528
- package/src/resources/phase1.test.ts +0 -187
- package/src/resources/quotas.ts +0 -77
- package/src/resources/sandbox-processes.ts +0 -112
- package/src/resources/sandbox-shares.ts +0 -83
- package/src/resources/tenant-events.ts +0 -32
- package/src/resources/workspaces.ts +0 -285
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
|
|
15
15
|
import type { HttpClient } from "../http.js";
|
|
16
|
-
import type { DockerDeployHostData } from "./docker-deploy.js";
|
|
17
16
|
|
|
18
17
|
// ── Branded IDs ─────────────────────────────────────────────────────────────
|
|
19
18
|
|
|
@@ -121,35 +120,6 @@ export interface DeploymentData {
|
|
|
121
120
|
updated_at?: string;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
export interface DockerDeployDoctorCheck {
|
|
125
|
-
name: string;
|
|
126
|
-
ok: boolean;
|
|
127
|
-
message: string;
|
|
128
|
-
details?: Record<string, unknown>;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface DockerDeployDoctorProbe {
|
|
132
|
-
url: string;
|
|
133
|
-
ok: boolean;
|
|
134
|
-
status?: number;
|
|
135
|
-
error?: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface DockerDeployDoctorResult {
|
|
139
|
-
ok: boolean;
|
|
140
|
-
deployment: DeploymentData;
|
|
141
|
-
host?: DockerDeployHostData;
|
|
142
|
-
checks: DockerDeployDoctorCheck[];
|
|
143
|
-
probe?: DockerDeployDoctorProbe;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export interface DockerDeployDoctorParams {
|
|
147
|
-
probePath?: string;
|
|
148
|
-
probe_path?: string;
|
|
149
|
-
timeoutMs?: number;
|
|
150
|
-
timeout_ms?: number;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
123
|
export type DeploymentDatabaseRequest =
|
|
154
124
|
| boolean
|
|
155
125
|
| {
|
|
@@ -311,12 +281,118 @@ export interface DeploymentCreateParams extends ExternalAttribution {
|
|
|
311
281
|
auto_deploy?: boolean;
|
|
312
282
|
autoDeploy?: boolean;
|
|
313
283
|
database?: DeploymentDatabaseRequest;
|
|
284
|
+
docker_deploy_template_id?: string;
|
|
285
|
+
dockerDeployTemplateId?: string;
|
|
314
286
|
metadata?: Record<string, unknown>;
|
|
315
287
|
idempotencyKey?: string;
|
|
316
288
|
}
|
|
317
289
|
|
|
318
290
|
export interface DockerDeployCreateParams extends DeploymentCreateParams {}
|
|
319
291
|
|
|
292
|
+
export interface DockerDeployHostData {
|
|
293
|
+
id: string;
|
|
294
|
+
tenant_id?: string;
|
|
295
|
+
workspace_id?: string | null;
|
|
296
|
+
computer_id?: string | null;
|
|
297
|
+
fleet_node_id?: string | null;
|
|
298
|
+
runtime_base_url?: string | null;
|
|
299
|
+
status?: string;
|
|
300
|
+
appliance_status?: string | null;
|
|
301
|
+
appliance_version?: string | null;
|
|
302
|
+
appliance_image?: string | null;
|
|
303
|
+
agent_configured?: boolean;
|
|
304
|
+
last_health_check_at?: string | null;
|
|
305
|
+
created_at?: string;
|
|
306
|
+
updated_at?: string;
|
|
307
|
+
metadata?: Record<string, unknown>;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface DockerDeployHostListParams {
|
|
311
|
+
workspace_id?: string;
|
|
312
|
+
workspaceId?: string;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface DockerDeployHostEnsureParams {
|
|
316
|
+
workspace_id: string;
|
|
317
|
+
workspaceId?: string;
|
|
318
|
+
region?: string;
|
|
319
|
+
size?: string;
|
|
320
|
+
appliance_image?: string;
|
|
321
|
+
applianceImage?: string;
|
|
322
|
+
metadata?: Record<string, unknown>;
|
|
323
|
+
idempotencyKey?: string;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface DockerDeployTemplateData {
|
|
327
|
+
id: string;
|
|
328
|
+
name: string;
|
|
329
|
+
summary?: string;
|
|
330
|
+
description?: string;
|
|
331
|
+
category?: string;
|
|
332
|
+
runtime?: string;
|
|
333
|
+
framework?: string;
|
|
334
|
+
build_command?: string | null;
|
|
335
|
+
run_command?: string | null;
|
|
336
|
+
ports?: number[];
|
|
337
|
+
env?: Record<string, string>;
|
|
338
|
+
files?: Record<string, string>;
|
|
339
|
+
design_md?: string | null;
|
|
340
|
+
design_source_url?: string | null;
|
|
341
|
+
design_sources?: string[];
|
|
342
|
+
design_reference_presets?: DockerDeployDesignReferencePreset[];
|
|
343
|
+
metadata?: Record<string, unknown>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export interface DockerDeployDesignReferencePreset {
|
|
347
|
+
id: string;
|
|
348
|
+
name: string;
|
|
349
|
+
summary: string;
|
|
350
|
+
best_for?: string[];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface DockerDeployTemplateListParams {
|
|
354
|
+
category?: string;
|
|
355
|
+
runtime?: string;
|
|
356
|
+
framework?: string;
|
|
357
|
+
includePreview?: boolean;
|
|
358
|
+
include_preview?: boolean;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface DockerDeployDoctorParams {
|
|
362
|
+
/** Probe the deployment public URL after checking control-plane metadata. Default: true. */
|
|
363
|
+
probe?: boolean;
|
|
364
|
+
/** Path to request on the public URL. Default: "/". */
|
|
365
|
+
probePath?: string;
|
|
366
|
+
/** Public URL probe timeout in milliseconds. Default: 20_000. */
|
|
367
|
+
timeoutMs?: number;
|
|
368
|
+
/** Test hook or custom runtime fetch implementation. */
|
|
369
|
+
fetchImpl?: typeof fetch;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export interface DockerDeployDoctorCheck {
|
|
373
|
+
name: string;
|
|
374
|
+
ok: boolean;
|
|
375
|
+
message: string;
|
|
376
|
+
data?: Record<string, unknown>;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface DockerDeployDoctorProbe {
|
|
380
|
+
url: string;
|
|
381
|
+
status: number | null;
|
|
382
|
+
ok: boolean;
|
|
383
|
+
responseSnippet: string;
|
|
384
|
+
gatewayJson?: boolean;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export interface DockerDeployDoctorResult {
|
|
388
|
+
ok: boolean;
|
|
389
|
+
deployment: DeploymentData;
|
|
390
|
+
host?: DockerDeployHostData;
|
|
391
|
+
publicUrl?: string | null;
|
|
392
|
+
checks: DockerDeployDoctorCheck[];
|
|
393
|
+
probe?: DockerDeployDoctorProbe;
|
|
394
|
+
}
|
|
395
|
+
|
|
320
396
|
export interface DeploymentUpdateParams {
|
|
321
397
|
name?: string;
|
|
322
398
|
branch?: string;
|
|
@@ -363,6 +439,10 @@ export interface PublishParams extends ExternalAttribution {
|
|
|
363
439
|
healthCheckPath?: string;
|
|
364
440
|
/** @deprecated Reserved for dynamic runtime publish. */
|
|
365
441
|
health_check_path?: string;
|
|
442
|
+
deploymentType?: DeploymentProduct | string;
|
|
443
|
+
deployment_type?: DeploymentProduct | string;
|
|
444
|
+
dockerDeployTemplateId?: string;
|
|
445
|
+
docker_deploy_template_id?: string;
|
|
366
446
|
/** @deprecated Reserved for managed data-service binding. */
|
|
367
447
|
dataServices?: string[];
|
|
368
448
|
/** @deprecated Reserved for managed data-service binding. */
|
|
@@ -459,26 +539,49 @@ function stripUndefined(
|
|
|
459
539
|
|
|
460
540
|
function dockerDeployMetadata(
|
|
461
541
|
metadata: Record<string, unknown> | undefined,
|
|
542
|
+
templateId?: string,
|
|
462
543
|
): Record<string, unknown> {
|
|
463
|
-
return {
|
|
544
|
+
return stripUndefined({
|
|
464
545
|
...(metadata ?? {}),
|
|
465
546
|
deployment_product: "docker_deploy",
|
|
466
|
-
|
|
547
|
+
docker_deploy_template_id: templateId,
|
|
548
|
+
});
|
|
467
549
|
}
|
|
468
550
|
|
|
469
|
-
function
|
|
470
|
-
return (
|
|
471
|
-
deployment.deployment_product ??
|
|
472
|
-
deployment.metadata?.["deployment_product"]
|
|
473
|
-
);
|
|
551
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
552
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
474
553
|
}
|
|
475
554
|
|
|
476
|
-
function
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
555
|
+
function stringValue(value: unknown): string | undefined {
|
|
556
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function runtimeMetadata(
|
|
560
|
+
metadata: Record<string, unknown>,
|
|
561
|
+
): Record<string, unknown> | undefined {
|
|
562
|
+
const runtime = metadata.runtime;
|
|
563
|
+
return isRecord(runtime) ? runtime : undefined;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function publicProbeUrl(publicUrl: string, probePath: string): string {
|
|
567
|
+
const url = new URL(publicUrl);
|
|
568
|
+
const normalizedPath = probePath.startsWith("/") ? probePath : `/${probePath}`;
|
|
569
|
+
url.pathname = normalizedPath;
|
|
570
|
+
return url.toString();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function looksLikeMiosaGatewayJson(body: string): boolean {
|
|
574
|
+
try {
|
|
575
|
+
const parsed = JSON.parse(body) as unknown;
|
|
576
|
+
return (
|
|
577
|
+
isRecord(parsed) &&
|
|
578
|
+
parsed.ok === true &&
|
|
579
|
+
typeof parsed.run_id === "string" &&
|
|
580
|
+
Object.keys(parsed).every((key) => ["ok", "run_id"].includes(key))
|
|
581
|
+
);
|
|
582
|
+
} catch {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
482
585
|
}
|
|
483
586
|
|
|
484
587
|
function addDoctorCheck(
|
|
@@ -486,21 +589,11 @@ function addDoctorCheck(
|
|
|
486
589
|
name: string,
|
|
487
590
|
ok: boolean,
|
|
488
591
|
message: string,
|
|
489
|
-
|
|
592
|
+
data?: Record<string, unknown>,
|
|
490
593
|
): void {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
function hostHealthy(host: DockerDeployHostData | undefined): boolean {
|
|
495
|
-
return Boolean(
|
|
496
|
-
host && host.status === "active" && host.appliance_status === "healthy",
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
function probeUrl(publicUrl: string, probePath: string): string {
|
|
501
|
-
const url = new URL(publicUrl);
|
|
502
|
-
url.pathname = probePath.startsWith("/") ? probePath : `/${probePath}`;
|
|
503
|
-
return url.toString();
|
|
594
|
+
const check: DockerDeployDoctorCheck = { name, ok, message };
|
|
595
|
+
if (data !== undefined) check.data = data;
|
|
596
|
+
checks.push(check);
|
|
504
597
|
}
|
|
505
598
|
|
|
506
599
|
// ── Sub-resources ──────────────────────────────────────────────────────────
|
|
@@ -698,6 +791,8 @@ export class Deployments {
|
|
|
698
791
|
run_command: params.runCommand ?? params.run_command,
|
|
699
792
|
auto_deploy: params.autoDeploy ?? params.auto_deploy,
|
|
700
793
|
database: params.database,
|
|
794
|
+
docker_deploy_template_id:
|
|
795
|
+
params.dockerDeployTemplateId ?? params.docker_deploy_template_id,
|
|
701
796
|
metadata: params.metadata,
|
|
702
797
|
...attributionBody(params),
|
|
703
798
|
});
|
|
@@ -717,16 +812,55 @@ export class Deployments {
|
|
|
717
812
|
async createDockerDeploy(
|
|
718
813
|
params: DockerDeployCreateParams,
|
|
719
814
|
): Promise<DeploymentData> {
|
|
815
|
+
const templateId =
|
|
816
|
+
params.dockerDeployTemplateId ?? params.docker_deploy_template_id;
|
|
720
817
|
return this.create({
|
|
721
818
|
...params,
|
|
722
|
-
metadata: dockerDeployMetadata(params.metadata),
|
|
819
|
+
metadata: dockerDeployMetadata(params.metadata, templateId),
|
|
723
820
|
});
|
|
724
821
|
}
|
|
725
822
|
|
|
823
|
+
async listDockerDeployHosts(
|
|
824
|
+
params: DockerDeployHostListParams = {},
|
|
825
|
+
): Promise<DockerDeployHostData[]> {
|
|
826
|
+
const data = await this.http.get<unknown>(
|
|
827
|
+
"/docker-deploy/hosts",
|
|
828
|
+
stripUndefined({
|
|
829
|
+
workspace_id: params.workspaceId ?? params.workspace_id,
|
|
830
|
+
}) as Record<string, string | number | boolean | undefined>,
|
|
831
|
+
);
|
|
832
|
+
return listItems<DockerDeployHostData>(data, ["hosts", "items"]);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async ensureDockerDeployHost(
|
|
836
|
+
params: DockerDeployHostEnsureParams,
|
|
837
|
+
): Promise<DockerDeployHostData> {
|
|
838
|
+
const data = await this.http.request<unknown>("/docker-deploy/hosts/ensure", {
|
|
839
|
+
method: "POST",
|
|
840
|
+
body: stripUndefined({
|
|
841
|
+
workspace_id: params.workspaceId ?? params.workspace_id,
|
|
842
|
+
region: params.region,
|
|
843
|
+
size: params.size,
|
|
844
|
+
appliance_image: params.applianceImage ?? params.appliance_image,
|
|
845
|
+
metadata: params.metadata,
|
|
846
|
+
}),
|
|
847
|
+
headers: { "Idempotency-Key": idempotencyKey(params.idempotencyKey) },
|
|
848
|
+
});
|
|
849
|
+
return unwrap(data) as DockerDeployHostData;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async getDockerDeployHost(hostId: string): Promise<DockerDeployHostData> {
|
|
853
|
+
const data = await this.http.get<unknown>(`/docker-deploy/hosts/${hostId}`);
|
|
854
|
+
return unwrap(data) as DockerDeployHostData;
|
|
855
|
+
}
|
|
856
|
+
|
|
726
857
|
/**
|
|
727
|
-
* Verify a Docker Deploy deployment
|
|
728
|
-
*
|
|
729
|
-
*
|
|
858
|
+
* Verify a Docker Deploy deployment end-to-end.
|
|
859
|
+
*
|
|
860
|
+
* This checks the deployment product marker, Docker Deploy host linkage,
|
|
861
|
+
* appliance health, route runtime metadata, and optionally the public URL.
|
|
862
|
+
* It is intentionally agent-friendly: use this after sandbox.deployDocker()
|
|
863
|
+
* or createDockerDeploy() before telling a user the app is live.
|
|
730
864
|
*/
|
|
731
865
|
async doctorDockerDeploy(
|
|
732
866
|
deploymentId: string,
|
|
@@ -734,127 +868,176 @@ export class Deployments {
|
|
|
734
868
|
): Promise<DockerDeployDoctorResult> {
|
|
735
869
|
const checks: DockerDeployDoctorCheck[] = [];
|
|
736
870
|
const deployment = await this.get(deploymentId);
|
|
737
|
-
const metadata = deployment.metadata
|
|
738
|
-
const product =
|
|
739
|
-
|
|
871
|
+
const metadata = isRecord(deployment.metadata) ? deployment.metadata : {};
|
|
872
|
+
const product =
|
|
873
|
+
stringValue(deployment.deployment_product) ??
|
|
874
|
+
stringValue(metadata.deployment_product);
|
|
875
|
+
const hostId =
|
|
876
|
+
stringValue(deployment.docker_deploy_host_id) ??
|
|
877
|
+
stringValue(metadata.docker_deploy_host_id);
|
|
740
878
|
|
|
741
879
|
addDoctorCheck(
|
|
742
880
|
checks,
|
|
743
881
|
"deployment_product",
|
|
744
882
|
product === "docker_deploy",
|
|
745
883
|
product === "docker_deploy"
|
|
746
|
-
? "Deployment is marked
|
|
747
|
-
: `Expected deployment_product=docker_deploy, got ${
|
|
748
|
-
{
|
|
884
|
+
? "Deployment is marked as Docker Deploy."
|
|
885
|
+
: `Expected deployment_product=docker_deploy, got ${product ?? "missing"}.`,
|
|
886
|
+
{ product: product ?? null },
|
|
749
887
|
);
|
|
750
888
|
|
|
751
889
|
addDoctorCheck(
|
|
752
890
|
checks,
|
|
753
891
|
"docker_deploy_host_id",
|
|
754
|
-
|
|
892
|
+
!!hostId,
|
|
755
893
|
hostId
|
|
756
|
-
? "Deployment
|
|
894
|
+
? "Deployment is linked to a Docker Deploy appliance host."
|
|
757
895
|
: "Deployment has no docker_deploy_host_id.",
|
|
758
|
-
{ docker_deploy_host_id: hostId },
|
|
896
|
+
{ docker_deploy_host_id: hostId ?? null },
|
|
759
897
|
);
|
|
760
898
|
|
|
761
899
|
let host: DockerDeployHostData | undefined;
|
|
762
900
|
if (hostId) {
|
|
763
901
|
try {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
902
|
+
host = await this.getDockerDeployHost(hostId);
|
|
903
|
+
const status = host.status ?? null;
|
|
904
|
+
const applianceStatus = host.appliance_status ?? null;
|
|
905
|
+
const badHostStates = new Set(["failed", "error", "destroyed"]);
|
|
906
|
+
const badApplianceStates = new Set(["failed", "error", "unhealthy"]);
|
|
907
|
+
const ok =
|
|
908
|
+
!badHostStates.has(String(status)) &&
|
|
909
|
+
!badApplianceStates.has(String(applianceStatus));
|
|
768
910
|
addDoctorCheck(
|
|
769
911
|
checks,
|
|
770
912
|
"docker_deploy_host_health",
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
? "Docker Deploy host is
|
|
774
|
-
: `Docker Deploy host status=${
|
|
775
|
-
{
|
|
776
|
-
status: host.status,
|
|
777
|
-
appliance_status: host.appliance_status,
|
|
778
|
-
},
|
|
913
|
+
ok,
|
|
914
|
+
ok
|
|
915
|
+
? "Docker Deploy host is not reporting a failed state."
|
|
916
|
+
: `Docker Deploy host is unhealthy: status=${status}, appliance_status=${applianceStatus}.`,
|
|
917
|
+
{ status, appliance_status: applianceStatus },
|
|
779
918
|
);
|
|
780
|
-
} catch (
|
|
919
|
+
} catch (err) {
|
|
781
920
|
addDoctorCheck(
|
|
782
921
|
checks,
|
|
783
922
|
"docker_deploy_host_health",
|
|
784
923
|
false,
|
|
785
|
-
|
|
924
|
+
`Could not fetch Docker Deploy host ${hostId}: ${
|
|
925
|
+
err instanceof Error ? err.message : String(err)
|
|
926
|
+
}`,
|
|
786
927
|
);
|
|
787
928
|
}
|
|
788
929
|
}
|
|
789
930
|
|
|
790
|
-
const runtime = metadata
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
931
|
+
const runtime = runtimeMetadata(metadata);
|
|
932
|
+
const runtimeIp = runtime
|
|
933
|
+
? stringValue(runtime.ip) ?? stringValue(runtime.ip_address)
|
|
934
|
+
: undefined;
|
|
935
|
+
const runtimePort = runtime?.port;
|
|
936
|
+
addDoctorCheck(
|
|
937
|
+
checks,
|
|
938
|
+
"route_runtime",
|
|
939
|
+
!!runtimeIp && runtimePort !== undefined && runtimePort !== null,
|
|
940
|
+
runtimeIp && runtimePort !== undefined && runtimePort !== null
|
|
941
|
+
? "Deployment route metadata points at a runtime target."
|
|
942
|
+
: "Deployment metadata has no runtime ip/port target.",
|
|
943
|
+
{ ip: runtimeIp ?? null, port: runtimePort ?? null },
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
const publicUrl =
|
|
947
|
+
stringValue(deployment.public_url) ?? stringValue(metadata.public_url);
|
|
796
948
|
addDoctorCheck(
|
|
797
949
|
checks,
|
|
798
|
-
"
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
? "Deployment has
|
|
802
|
-
: "Deployment
|
|
803
|
-
|
|
804
|
-
? (runtime as Record<string, unknown>)
|
|
805
|
-
: undefined,
|
|
950
|
+
"public_url",
|
|
951
|
+
!!publicUrl,
|
|
952
|
+
publicUrl
|
|
953
|
+
? "Deployment has a public URL."
|
|
954
|
+
: "Deployment has no public URL to probe.",
|
|
955
|
+
{ public_url: publicUrl ?? null },
|
|
806
956
|
);
|
|
807
957
|
|
|
808
958
|
let probe: DockerDeployDoctorProbe | undefined;
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
const url = probeUrl(publicUrl, path);
|
|
959
|
+
if (params.probe !== false && publicUrl) {
|
|
960
|
+
const fetchImpl = params.fetchImpl ?? fetch;
|
|
961
|
+
const url = publicProbeUrl(publicUrl, params.probePath ?? "/");
|
|
813
962
|
const controller = new AbortController();
|
|
814
|
-
const
|
|
815
|
-
() => controller.abort(),
|
|
816
|
-
params.timeoutMs ?? params.timeout_ms ?? 10_000,
|
|
817
|
-
);
|
|
963
|
+
const timer = setTimeout(() => controller.abort(), params.timeoutMs ?? 20_000);
|
|
818
964
|
try {
|
|
819
|
-
const response = await
|
|
820
|
-
|
|
965
|
+
const response = await fetchImpl(url, {
|
|
966
|
+
headers: { Accept: "*/*" },
|
|
821
967
|
signal: controller.signal,
|
|
822
968
|
});
|
|
823
|
-
|
|
969
|
+
const text = await response.text();
|
|
970
|
+
const gatewayJson = looksLikeMiosaGatewayJson(text);
|
|
971
|
+
const ok = response.ok && !gatewayJson;
|
|
972
|
+
probe = {
|
|
973
|
+
url,
|
|
974
|
+
status: response.status,
|
|
975
|
+
ok,
|
|
976
|
+
responseSnippet: text.slice(0, 500),
|
|
977
|
+
};
|
|
978
|
+
if (gatewayJson) probe.gatewayJson = true;
|
|
824
979
|
addDoctorCheck(
|
|
825
980
|
checks,
|
|
826
981
|
"public_url_probe",
|
|
827
|
-
|
|
828
|
-
|
|
982
|
+
ok,
|
|
983
|
+
ok
|
|
829
984
|
? `Public URL returned HTTP ${response.status}.`
|
|
830
|
-
:
|
|
831
|
-
|
|
985
|
+
: gatewayJson
|
|
986
|
+
? "Public URL returned MIOSA gateway JSON instead of the app."
|
|
987
|
+
: `Public URL returned HTTP ${response.status}.`,
|
|
988
|
+
{ status: response.status, gateway_json: gatewayJson },
|
|
832
989
|
);
|
|
833
|
-
} catch (
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
ok: false,
|
|
837
|
-
error: error instanceof Error ? error.message : String(error),
|
|
838
|
-
};
|
|
990
|
+
} catch (err) {
|
|
991
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
992
|
+
probe = { url, status: null, ok: false, responseSnippet: message };
|
|
839
993
|
addDoctorCheck(
|
|
840
994
|
checks,
|
|
841
995
|
"public_url_probe",
|
|
842
996
|
false,
|
|
843
|
-
|
|
844
|
-
{ url },
|
|
997
|
+
`Public URL probe failed: ${message}`,
|
|
845
998
|
);
|
|
846
999
|
} finally {
|
|
847
|
-
clearTimeout(
|
|
1000
|
+
clearTimeout(timer);
|
|
848
1001
|
}
|
|
849
1002
|
}
|
|
850
1003
|
|
|
851
|
-
|
|
1004
|
+
const result: DockerDeployDoctorResult = {
|
|
852
1005
|
ok: checks.every((check) => check.ok),
|
|
853
1006
|
deployment,
|
|
854
|
-
...(host ? { host } : {}),
|
|
855
1007
|
checks,
|
|
856
|
-
...(probe ? { probe } : {}),
|
|
857
1008
|
};
|
|
1009
|
+
if (host) result.host = host;
|
|
1010
|
+
if (publicUrl !== undefined) result.publicUrl = publicUrl;
|
|
1011
|
+
if (probe) result.probe = probe;
|
|
1012
|
+
return result;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async listDockerDeployTemplates(
|
|
1016
|
+
params: DockerDeployTemplateListParams = {},
|
|
1017
|
+
): Promise<DockerDeployTemplateData[]> {
|
|
1018
|
+
const query: Record<string, string | number | boolean | undefined> = {
|
|
1019
|
+
category: params.category,
|
|
1020
|
+
runtime: params.runtime,
|
|
1021
|
+
framework: params.framework,
|
|
1022
|
+
include_preview: params.includePreview ?? params.include_preview,
|
|
1023
|
+
};
|
|
1024
|
+
const data = await this.http.get<unknown>(
|
|
1025
|
+
"/docker-deploy/templates",
|
|
1026
|
+
stripUndefined(query) as Record<string, string | number | boolean | undefined>,
|
|
1027
|
+
);
|
|
1028
|
+
const templates = unwrap<any>(data);
|
|
1029
|
+
if (Array.isArray(templates)) return templates as DockerDeployTemplateData[];
|
|
1030
|
+
return ((templates?.templates ?? templates?.items ?? templates?.data ?? []) ||
|
|
1031
|
+
[]) as DockerDeployTemplateData[];
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
async getDockerDeployTemplate(
|
|
1035
|
+
templateId: string,
|
|
1036
|
+
): Promise<DockerDeployTemplateData> {
|
|
1037
|
+
const data = await this.http.get<unknown>(
|
|
1038
|
+
`/docker-deploy/templates/${templateId}`,
|
|
1039
|
+
);
|
|
1040
|
+
return unwrap(data) as DockerDeployTemplateData;
|
|
858
1041
|
}
|
|
859
1042
|
|
|
860
1043
|
async update(
|
|
@@ -888,6 +1071,15 @@ export class Deployments {
|
|
|
888
1071
|
output_path: params.outputPath ?? params.output_path,
|
|
889
1072
|
entrypoint: params.entrypoint,
|
|
890
1073
|
promote: params.promote,
|
|
1074
|
+
build_command: params.buildCommand ?? params.build_command,
|
|
1075
|
+
run_command: params.runCommand ?? params.run_command,
|
|
1076
|
+
port: params.port,
|
|
1077
|
+
health_check_path: params.healthCheckPath ?? params.health_check_path,
|
|
1078
|
+
deployment_type: params.deploymentType ?? params.deployment_type,
|
|
1079
|
+
docker_deploy_template_id:
|
|
1080
|
+
params.dockerDeployTemplateId ?? params.docker_deploy_template_id,
|
|
1081
|
+
data_services: params.dataServices ?? params.data_services,
|
|
1082
|
+
...attributionBody(params),
|
|
891
1083
|
});
|
|
892
1084
|
const data = await this.http.request<unknown>(
|
|
893
1085
|
`/deployments/${deploymentId}/publish`,
|
|
@@ -918,6 +1110,15 @@ export class Deployments {
|
|
|
918
1110
|
entrypoint: params.entrypoint,
|
|
919
1111
|
domain: params.domain,
|
|
920
1112
|
custom_domain: params.customDomain ?? params.custom_domain,
|
|
1113
|
+
build_command: params.buildCommand ?? params.build_command,
|
|
1114
|
+
run_command: params.runCommand ?? params.run_command,
|
|
1115
|
+
port: params.port,
|
|
1116
|
+
health_check_path: params.healthCheckPath ?? params.health_check_path,
|
|
1117
|
+
deployment_type: params.deploymentType ?? params.deployment_type,
|
|
1118
|
+
docker_deploy_template_id:
|
|
1119
|
+
params.dockerDeployTemplateId ?? params.docker_deploy_template_id,
|
|
1120
|
+
data_services: params.dataServices ?? params.data_services,
|
|
1121
|
+
...attributionBody(params),
|
|
921
1122
|
});
|
|
922
1123
|
const data = await this.http.request<unknown>(
|
|
923
1124
|
`/sandboxes/${sandboxId}/deploy`,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { HttpClient } from "../http.js";
|
|
3
|
+
import { Devices } from "./devices.js";
|
|
4
|
+
|
|
5
|
+
const mockGet = vi.fn();
|
|
6
|
+
|
|
7
|
+
function makeHttp(): HttpClient {
|
|
8
|
+
const http = {} as HttpClient;
|
|
9
|
+
http.get = mockGet;
|
|
10
|
+
return http;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("Devices", () => {
|
|
18
|
+
it("returns the static agent device catalog", () => {
|
|
19
|
+
const catalog = new Devices(makeHttp()).catalog();
|
|
20
|
+
|
|
21
|
+
expect(catalog.map((device) => device.kind)).toEqual(
|
|
22
|
+
expect.arrayContaining([
|
|
23
|
+
"sandbox_worker",
|
|
24
|
+
"computer",
|
|
25
|
+
"local_device",
|
|
26
|
+
"docker_deploy_host",
|
|
27
|
+
]),
|
|
28
|
+
);
|
|
29
|
+
expect(catalog.find((device) => device.kind === "sandbox_worker")
|
|
30
|
+
?.primaryCommands).toEqual(
|
|
31
|
+
expect.arrayContaining([
|
|
32
|
+
expect.stringContaining("sandboxes.create"),
|
|
33
|
+
expect.stringContaining("sandbox.exec"),
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("normalizes sandboxes and computers into one device list", async () => {
|
|
39
|
+
mockGet
|
|
40
|
+
.mockResolvedValueOnce({
|
|
41
|
+
data: [
|
|
42
|
+
{
|
|
43
|
+
id: "sbx_123",
|
|
44
|
+
name: "builder",
|
|
45
|
+
state: "running",
|
|
46
|
+
ready: true,
|
|
47
|
+
persistent: true,
|
|
48
|
+
preview_url: "https://3000-sbx_123.sandbox.miosa.app",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
.mockResolvedValueOnce({
|
|
53
|
+
data: [
|
|
54
|
+
{
|
|
55
|
+
id: "cmp_123",
|
|
56
|
+
name: "desktop",
|
|
57
|
+
status: "running",
|
|
58
|
+
region: "us-nyc",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await new Devices(makeHttp()).list();
|
|
64
|
+
|
|
65
|
+
expect(mockGet).toHaveBeenNthCalledWith(1, "/sandboxes");
|
|
66
|
+
expect(mockGet).toHaveBeenNthCalledWith(2, "/computers");
|
|
67
|
+
expect(result.errors).toEqual([]);
|
|
68
|
+
expect(result.devices).toEqual(
|
|
69
|
+
expect.arrayContaining([
|
|
70
|
+
expect.objectContaining({ id: "sbx_123", kind: "sandbox_worker" }),
|
|
71
|
+
expect.objectContaining({ id: "cmp_123", kind: "computer" }),
|
|
72
|
+
]),
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("returns partial device inventory when one backing endpoint fails", async () => {
|
|
77
|
+
mockGet
|
|
78
|
+
.mockResolvedValueOnce({
|
|
79
|
+
data: [{ id: "sbx_123", state: "running" }],
|
|
80
|
+
})
|
|
81
|
+
.mockRejectedValueOnce(new Error("HTTP 502"));
|
|
82
|
+
|
|
83
|
+
const result = await new Devices(makeHttp()).list();
|
|
84
|
+
|
|
85
|
+
expect(result.devices).toEqual([
|
|
86
|
+
expect.objectContaining({ id: "sbx_123", kind: "sandbox_worker" }),
|
|
87
|
+
]);
|
|
88
|
+
expect(result.errors).toEqual([
|
|
89
|
+
expect.objectContaining({ source: "computers", retryable: true }),
|
|
90
|
+
]);
|
|
91
|
+
});
|
|
92
|
+
});
|