@lizard-build/sdk 0.1.40 → 0.1.42
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 +58 -258
- package/dist/index.d.mts +87 -56
- package/dist/index.d.ts +87 -56
- package/dist/index.js +83 -47
- package/dist/index.mjs +83 -47
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -114,7 +114,7 @@ declare class AddonsAPI {
|
|
|
114
114
|
memoryMb?: number;
|
|
115
115
|
storageGb?: number;
|
|
116
116
|
}): Promise<Addon>;
|
|
117
|
-
/** Restart an addon
|
|
117
|
+
/** Restart an addon. */
|
|
118
118
|
redeploy(projectId: string, addonId: string): Promise<void>;
|
|
119
119
|
rename(projectId: string, addonId: string, name: string): Promise<unknown>;
|
|
120
120
|
secrets(projectId: string, addonId: string): Promise<Array<{
|
|
@@ -395,7 +395,7 @@ interface ProcessInfo {
|
|
|
395
395
|
/** Signals accepted by {@link Process.kill}. */
|
|
396
396
|
type ProcessSignal = 'SIGTERM' | 'SIGKILL' | 'SIGINT' | 'SIGHUP' | 'SIGQUIT' | 'SIGUSR1' | 'SIGUSR2';
|
|
397
397
|
/**
|
|
398
|
-
* The result of a process execution inside a Lizard
|
|
398
|
+
* The result of a process execution inside a Lizard sandbox.
|
|
399
399
|
*/
|
|
400
400
|
interface ProcessResult {
|
|
401
401
|
stdout: string;
|
|
@@ -418,7 +418,7 @@ interface ProcessOpts {
|
|
|
418
418
|
onPid?: (pid: number) => void;
|
|
419
419
|
}
|
|
420
420
|
/**
|
|
421
|
-
* Runs processes inside a Lizard sandbox
|
|
421
|
+
* Runs processes inside a Lizard sandbox.
|
|
422
422
|
*
|
|
423
423
|
* Access via `sandbox.process`.
|
|
424
424
|
*/
|
|
@@ -427,12 +427,12 @@ declare class Process {
|
|
|
427
427
|
private readonly config;
|
|
428
428
|
constructor(sandboxId: string, config: ConnectionConfig);
|
|
429
429
|
/**
|
|
430
|
-
* Execute a command inside the
|
|
430
|
+
* Execute a command inside the sandbox and wait for it to complete.
|
|
431
431
|
*
|
|
432
432
|
* The command runs in a shell inside the Lizard sandbox and returns
|
|
433
433
|
* stdout, stderr, and the exit code when it finishes.
|
|
434
434
|
*
|
|
435
|
-
* @param cmd Shell command to run inside the
|
|
435
|
+
* @param cmd Shell command to run inside the sandbox.
|
|
436
436
|
* @param opts Optional execution options — environment variables, working
|
|
437
437
|
* directory, user, and timeout.
|
|
438
438
|
*
|
|
@@ -511,7 +511,7 @@ interface FileInfo {
|
|
|
511
511
|
modTime?: number;
|
|
512
512
|
}
|
|
513
513
|
/**
|
|
514
|
-
* Read and write files inside a Lizard sandbox
|
|
514
|
+
* Read and write files inside a Lizard sandbox.
|
|
515
515
|
*
|
|
516
516
|
* Access via `sandbox.fs`.
|
|
517
517
|
*/
|
|
@@ -520,23 +520,24 @@ declare class Fs {
|
|
|
520
520
|
private readonly config;
|
|
521
521
|
constructor(sandboxId: string, config: ConnectionConfig);
|
|
522
522
|
/**
|
|
523
|
-
* Write a file into the
|
|
523
|
+
* Write a file into the sandbox filesystem.
|
|
524
524
|
*
|
|
525
525
|
* Creates parent directories automatically if they don't exist.
|
|
526
|
+
* Byte input must contain valid UTF-8; arbitrary binary uploads are unsupported.
|
|
526
527
|
*
|
|
527
528
|
* @example
|
|
528
529
|
* ```ts
|
|
529
530
|
* await sandbox.fs.write('/app/index.js', 'console.log("hello")')
|
|
530
531
|
* ```
|
|
531
532
|
*
|
|
532
|
-
* @example Write
|
|
533
|
+
* @example Write UTF-8 bytes:
|
|
533
534
|
* ```ts
|
|
534
|
-
* await sandbox.fs.write('/app/
|
|
535
|
+
* await sandbox.fs.write('/app/notes.txt', new TextEncoder().encode('hello'))
|
|
535
536
|
* ```
|
|
536
537
|
*/
|
|
537
538
|
write(path: string, data: string | Uint8Array, opts?: FsOpts): Promise<void>;
|
|
538
539
|
/**
|
|
539
|
-
* Read a file from the
|
|
540
|
+
* Read a file from the sandbox filesystem.
|
|
540
541
|
*
|
|
541
542
|
* @returns The file contents as a UTF-8 string.
|
|
542
543
|
*
|
|
@@ -546,22 +547,23 @@ declare class Fs {
|
|
|
546
547
|
* ```
|
|
547
548
|
*/
|
|
548
549
|
read(path: string, opts?: FsOpts): Promise<string>;
|
|
550
|
+
/** Read a file as bytes. */
|
|
551
|
+
readBytes(path: string, opts?: FsOpts): Promise<Uint8Array>;
|
|
549
552
|
/**
|
|
550
|
-
* List files and directories at the given path inside the
|
|
553
|
+
* List files and directories at the given path inside the sandbox.
|
|
551
554
|
*
|
|
552
555
|
* @example
|
|
553
556
|
* ```ts
|
|
554
557
|
* const entries = await sandbox.fs.list('/app')
|
|
555
558
|
* ```
|
|
556
559
|
*/
|
|
557
|
-
readBytes(path: string, opts?: FsOpts): Promise<Uint8Array>;
|
|
558
560
|
list(path: string, opts?: FsOpts): Promise<FileInfo[]>;
|
|
559
561
|
/**
|
|
560
|
-
* Remove a file or directory from the
|
|
562
|
+
* Remove a file or directory from the sandbox filesystem.
|
|
561
563
|
*/
|
|
562
564
|
remove(path: string, opts?: FsOpts): Promise<void>;
|
|
563
565
|
/**
|
|
564
|
-
* Create a directory (and any missing parents) inside the
|
|
566
|
+
* Create a directory (and any missing parents) inside the sandbox.
|
|
565
567
|
*/
|
|
566
568
|
makeDir(path: string, opts?: FsOpts): Promise<void>;
|
|
567
569
|
/**
|
|
@@ -730,10 +732,9 @@ declare class SandboxClient {
|
|
|
730
732
|
}
|
|
731
733
|
|
|
732
734
|
/**
|
|
733
|
-
* A
|
|
735
|
+
* A Linux sandbox running on Kubernetes.
|
|
734
736
|
*
|
|
735
|
-
*
|
|
736
|
-
* process namespace, restored from a pre-warmed template snapshot.
|
|
737
|
+
* Run commands, read and write files, and expose HTTP ports in a sandbox.
|
|
737
738
|
*
|
|
738
739
|
* Sandboxes are **ephemeral**: killing one, or letting it hit its timeout, discards
|
|
739
740
|
* everything written inside it. State that has to outlive a sandbox belongs on a
|
|
@@ -745,10 +746,14 @@ declare class SandboxClient {
|
|
|
745
746
|
* import { Sandbox } from '@lizard-build/sdk'
|
|
746
747
|
*
|
|
747
748
|
* const sandbox = await Sandbox.create('base', { project: 'my-project' })
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
749
|
+
* try {
|
|
750
|
+
* await sandbox.fs.write('/tmp/hello.txt', 'hello world')
|
|
751
|
+
* const result = await sandbox.process.exec('cat /tmp/hello.txt')
|
|
752
|
+
* if (result.exitCode !== 0) throw new Error(result.stderr)
|
|
753
|
+
* console.log(result.stdout)
|
|
754
|
+
* } finally {
|
|
755
|
+
* await sandbox.kill()
|
|
756
|
+
* }
|
|
752
757
|
* ```
|
|
753
758
|
*
|
|
754
759
|
* @example Carry work across sandboxes with a volume:
|
|
@@ -769,11 +774,11 @@ declare class Sandbox extends SandboxClient {
|
|
|
769
774
|
protected static readonly defaultTemplate: string;
|
|
770
775
|
protected static readonly defaultTimeoutMs: number;
|
|
771
776
|
/**
|
|
772
|
-
* Unique identifier of this sandbox
|
|
777
|
+
* Unique identifier of this sandbox.
|
|
773
778
|
*/
|
|
774
779
|
readonly sandboxId: string;
|
|
775
780
|
/**
|
|
776
|
-
* Read and write files inside the
|
|
781
|
+
* Read and write files inside the sandbox filesystem.
|
|
777
782
|
*
|
|
778
783
|
* @example
|
|
779
784
|
* ```ts
|
|
@@ -783,7 +788,7 @@ declare class Sandbox extends SandboxClient {
|
|
|
783
788
|
*/
|
|
784
789
|
readonly fs: Fs;
|
|
785
790
|
/**
|
|
786
|
-
* Execute processes inside the
|
|
791
|
+
* Execute processes inside the sandbox.
|
|
787
792
|
*
|
|
788
793
|
* @example
|
|
789
794
|
* ```ts
|
|
@@ -807,16 +812,15 @@ declare class Sandbox extends SandboxClient {
|
|
|
807
812
|
/**
|
|
808
813
|
* Create a new Lizard sandbox from the specified template.
|
|
809
814
|
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
* `lizard push`.
|
|
815
|
+
* Template availability and installed tools depend on the platform and region.
|
|
816
|
+
* Use `base` for shell commands or `CodeSandbox` for stateful code execution.
|
|
813
817
|
*
|
|
814
818
|
* @param template Name of the sandbox template to boot from.
|
|
815
819
|
*
|
|
816
820
|
* @example
|
|
817
821
|
* ```ts
|
|
818
822
|
* const sandbox = await Sandbox.create('base', { project: 'my-project' })
|
|
819
|
-
* const
|
|
823
|
+
* const pythonSandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project', timeoutMs: 10 * 60 * 1000 })
|
|
820
824
|
* ```
|
|
821
825
|
*/
|
|
822
826
|
static create(template: string, opts?: SandboxOpts): Promise<Sandbox>;
|
|
@@ -826,11 +830,7 @@ declare class Sandbox extends SandboxClient {
|
|
|
826
830
|
* Verifies the sandbox exists and is reachable, then returns a handle to it.
|
|
827
831
|
* Throws `NotFoundError` if it has been killed or has expired.
|
|
828
832
|
*
|
|
829
|
-
*
|
|
830
|
-
* reconnecting to might be paused. Sandboxes are pods now and pause/resume is a
|
|
831
|
-
* `501` on every one of them, so that call turned every `connect()` into an
|
|
832
|
-
* error against a perfectly healthy sandbox. Connecting does not need to change
|
|
833
|
-
* a sandbox's state, so it no longer tries to.
|
|
833
|
+
* Connecting reads sandbox metadata without changing its state.
|
|
834
834
|
*
|
|
835
835
|
* @example
|
|
836
836
|
* ```ts
|
|
@@ -852,25 +852,21 @@ declare class Sandbox extends SandboxClient {
|
|
|
852
852
|
/**
|
|
853
853
|
* Kill the sandbox and release its resources immediately.
|
|
854
854
|
*
|
|
855
|
-
* @returns `true` if the
|
|
855
|
+
* @returns `true` if the sandbox was terminated, `false` if it was already gone.
|
|
856
856
|
*/
|
|
857
857
|
kill(opts?: ConnectionOpts): Promise<boolean>;
|
|
858
858
|
/**
|
|
859
|
-
*
|
|
859
|
+
* Request sandbox pause. The Kubernetes backend returns HTTP 501.
|
|
860
860
|
*
|
|
861
|
-
* @deprecated
|
|
862
|
-
*
|
|
863
|
-
* checkpoint of the pod, which is not built. To park work across a gap, put it on a
|
|
864
|
-
* {@link Volume} and create a fresh sandbox on that volume later; the volume is the
|
|
865
|
-
* part that is meant to outlive a sandbox.
|
|
861
|
+
* @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
|
|
862
|
+
* and attach it to a new sandbox instead.
|
|
866
863
|
*/
|
|
867
864
|
pause(opts?: ConnectionOpts): Promise<boolean>;
|
|
868
865
|
/**
|
|
869
|
-
*
|
|
866
|
+
* Request sandbox resume. The Kubernetes backend returns HTTP 501.
|
|
870
867
|
*
|
|
871
|
-
* @deprecated
|
|
872
|
-
*
|
|
873
|
-
* calls this, so reconnecting to a running sandbox works without it.
|
|
868
|
+
* @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
|
|
869
|
+
* reconnect to a running sandbox.
|
|
874
870
|
*/
|
|
875
871
|
resume(opts?: ConnectionOpts): Promise<boolean>;
|
|
876
872
|
/**
|
|
@@ -884,23 +880,25 @@ declare class Sandbox extends SandboxClient {
|
|
|
884
880
|
*/
|
|
885
881
|
setTimeout(timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
|
|
886
882
|
/**
|
|
887
|
-
*
|
|
883
|
+
* Register a public HTTPS route and return its hostname without a scheme.
|
|
888
884
|
*
|
|
889
|
-
* Useful for accessing HTTP servers started inside the
|
|
885
|
+
* Useful for accessing HTTP servers started inside the sandbox from your
|
|
890
886
|
* agent or tests without additional tunneling.
|
|
891
887
|
*
|
|
892
888
|
* @example
|
|
893
889
|
* ```ts
|
|
894
|
-
*
|
|
895
|
-
* const
|
|
896
|
-
*
|
|
890
|
+
* // Start an HTTP server on port 3000 before exposing it.
|
|
891
|
+
* const hostname = await sandbox.getHost(3000)
|
|
892
|
+
* console.log(`https://${hostname}`)
|
|
897
893
|
* ```
|
|
898
894
|
*/
|
|
899
895
|
getHost(port: number, opts?: ConnectionOpts): Promise<string>;
|
|
896
|
+
/** Fork is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
900
897
|
fork(opts?: {
|
|
901
898
|
count?: number;
|
|
902
899
|
timeoutMs?: number;
|
|
903
900
|
}): Promise<unknown>;
|
|
901
|
+
/** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
904
902
|
snapshot(name?: string): Promise<unknown>;
|
|
905
903
|
unexpose(port: number): Promise<void>;
|
|
906
904
|
logs(opts?: {
|
|
@@ -908,6 +906,7 @@ declare class Sandbox extends SandboxClient {
|
|
|
908
906
|
signal?: AbortSignal;
|
|
909
907
|
}): AsyncGenerator<StreamEvent, any, any>;
|
|
910
908
|
static snapshots(projectId: string, opts?: ConnectionOpts): Promise<unknown[]>;
|
|
909
|
+
/** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
911
910
|
static restore(snapshotId: string, opts?: ConnectionOpts & {
|
|
912
911
|
timeoutMs?: number;
|
|
913
912
|
}): Promise<Sandbox>;
|
|
@@ -1353,6 +1352,12 @@ interface VolumeInfo {
|
|
|
1353
1352
|
status: string;
|
|
1354
1353
|
attachedTo?: string | null;
|
|
1355
1354
|
createdAt: number;
|
|
1355
|
+
/**
|
|
1356
|
+
* Whether `sizeGb` is actually enforced as a hard limit on the volume's node.
|
|
1357
|
+
* Returned by {@link Volume.resize}; `false` means the size was recorded but the
|
|
1358
|
+
* node cannot enforce it, so writes past it are not refused.
|
|
1359
|
+
*/
|
|
1360
|
+
sizeEnforced?: boolean;
|
|
1356
1361
|
}
|
|
1357
1362
|
interface CreateVolumeOpts extends ConnectionOpts {
|
|
1358
1363
|
/** Whole GB, default 5. New volumes allow 1–50 GB unless the server config sets another maximum. Existing volumes are never resized. */
|
|
@@ -1400,8 +1405,8 @@ declare class Volume {
|
|
|
1400
1405
|
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1401
1406
|
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1402
1407
|
*
|
|
1403
|
-
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1404
|
-
*
|
|
1408
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
|
|
1409
|
+
* so an existing volume keeps its size. Call {@link Volume.resize} to change it.
|
|
1405
1410
|
*/
|
|
1406
1411
|
static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
|
|
1407
1412
|
/** Look a volume up by name or by id. */
|
|
@@ -1409,7 +1414,26 @@ declare class Volume {
|
|
|
1409
1414
|
static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
|
|
1410
1415
|
/** Delete a volume by name or by id, without constructing one first. */
|
|
1411
1416
|
static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
|
|
1417
|
+
/**
|
|
1418
|
+
* Resize a volume, by name or by id, to `sizeGb`.
|
|
1419
|
+
*
|
|
1420
|
+
* The change is in place and online: the size is a quota, so no data is copied,
|
|
1421
|
+
* it completes in well under a second, and a sandbox that has the volume mounted
|
|
1422
|
+
* keeps running and sees the new size immediately. Both growing and shrinking are
|
|
1423
|
+
* allowed, but a shrink must leave at least 10% of the new size free — otherwise
|
|
1424
|
+
* it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
|
|
1425
|
+
*
|
|
1426
|
+
* Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
|
|
1427
|
+
* `volume_resize_in_progress`, `volume_not_provisioned`,
|
|
1428
|
+
* `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
|
|
1429
|
+
* the size is left unchanged).
|
|
1430
|
+
*
|
|
1431
|
+
* @returns The volume's updated info, including `sizeEnforced`.
|
|
1432
|
+
*/
|
|
1433
|
+
static resize(projectId: string, nameOrId: string, sizeGb: number, opts?: ConnectionOpts): Promise<VolumeInfo>;
|
|
1412
1434
|
getInfo(projectId: string): Promise<VolumeInfo>;
|
|
1435
|
+
/** Resize this volume in place — see the static {@link Volume.resize}. */
|
|
1436
|
+
resize(projectId: string, sizeGb: number): Promise<VolumeInfo>;
|
|
1413
1437
|
delete(projectId: string): Promise<void>;
|
|
1414
1438
|
}
|
|
1415
1439
|
|
|
@@ -1509,6 +1533,7 @@ declare class Lizard {
|
|
|
1509
1533
|
getOrCreate: (name: string, opts?: CreateVolumeOpts) => Promise<Volume>;
|
|
1510
1534
|
get: (nameOrId: string, opts?: ConnectionOpts) => Promise<Volume>;
|
|
1511
1535
|
list: (opts?: ConnectionOpts) => Promise<VolumeInfo[]>;
|
|
1536
|
+
resize: (nameOrId: string, sizeGb: number, opts?: ConnectionOpts) => Promise<VolumeInfo>;
|
|
1512
1537
|
delete: (nameOrId: string, opts?: ConnectionOpts) => Promise<void>;
|
|
1513
1538
|
};
|
|
1514
1539
|
/**
|
|
@@ -1558,6 +1583,13 @@ interface Account {
|
|
|
1558
1583
|
declare function resolveProjectId(ref: string, config: ConnectionConfig): Promise<string>;
|
|
1559
1584
|
|
|
1560
1585
|
declare class LizardError extends Error {
|
|
1586
|
+
/** HTTP status of the failed API call, when the error came from one. */
|
|
1587
|
+
status?: number;
|
|
1588
|
+
/**
|
|
1589
|
+
* The server's machine-readable error code, when it sent one — e.g.
|
|
1590
|
+
* `'volume_too_full_to_shrink'`. Branch on this rather than on `message`.
|
|
1591
|
+
*/
|
|
1592
|
+
code?: string;
|
|
1561
1593
|
constructor(message: string);
|
|
1562
1594
|
}
|
|
1563
1595
|
/** Config was saved, but one or more deploy/restart actions failed. Never retry blindly. */
|
|
@@ -1602,7 +1634,7 @@ type OutputItem = {
|
|
|
1602
1634
|
message: string;
|
|
1603
1635
|
traceback: string;
|
|
1604
1636
|
};
|
|
1605
|
-
/** Error
|
|
1637
|
+
/** Error returned in Execution.error when executed code raises an exception. */
|
|
1606
1638
|
declare class ExecutionError extends Error {
|
|
1607
1639
|
readonly name: string;
|
|
1608
1640
|
readonly traceback: string;
|
|
@@ -1637,7 +1669,7 @@ interface RunCodeOpts {
|
|
|
1637
1669
|
context?: CodeContext;
|
|
1638
1670
|
/** Extra environment variables available to the code. */
|
|
1639
1671
|
envs?: Record<string, string>;
|
|
1640
|
-
/**
|
|
1672
|
+
/** Client timeout in milliseconds. No client timer is set when omitted. */
|
|
1641
1673
|
timeoutMs?: number;
|
|
1642
1674
|
onStdout?: (data: string) => void;
|
|
1643
1675
|
onStderr?: (data: string) => void;
|
|
@@ -1659,7 +1691,7 @@ interface CreateContextOpts {
|
|
|
1659
1691
|
*
|
|
1660
1692
|
* @example
|
|
1661
1693
|
* ```ts
|
|
1662
|
-
* import { CodeSandbox } from 'lizard/
|
|
1694
|
+
* import { CodeSandbox } from '@lizard-build/sdk'
|
|
1663
1695
|
*
|
|
1664
1696
|
* const sandbox = await CodeSandbox.create({ project: 'my-project' })
|
|
1665
1697
|
*
|
|
@@ -1690,8 +1722,7 @@ declare class CodeSandbox extends Sandbox {
|
|
|
1690
1722
|
*
|
|
1691
1723
|
* @returns Execution result with stdout, stderr, results, and any error.
|
|
1692
1724
|
*
|
|
1693
|
-
*
|
|
1694
|
-
* default context exists — which cannot happen in normal usage.
|
|
1725
|
+
* Code errors appear in `execution.error`; request failures throw.
|
|
1695
1726
|
*
|
|
1696
1727
|
* @example
|
|
1697
1728
|
* ```ts
|
package/dist/index.js
CHANGED
|
@@ -112,17 +112,23 @@ var TimeoutError = class extends LizardError {
|
|
|
112
112
|
};
|
|
113
113
|
async function handleApiError(res) {
|
|
114
114
|
let message;
|
|
115
|
+
let code;
|
|
115
116
|
try {
|
|
116
117
|
const body = await res.json();
|
|
117
118
|
message = body.error ?? res.statusText;
|
|
119
|
+
if (typeof body.code === "string") code = body.code;
|
|
118
120
|
} catch {
|
|
119
121
|
message = res.statusText;
|
|
120
122
|
}
|
|
121
|
-
|
|
122
|
-
if (res.status ===
|
|
123
|
-
if (res.status ===
|
|
124
|
-
if (res.status ===
|
|
125
|
-
|
|
123
|
+
let err;
|
|
124
|
+
if (res.status === 401 || res.status === 403) err = new AuthenticationError(message);
|
|
125
|
+
else if (res.status === 404) err = new NotFoundError(message);
|
|
126
|
+
else if (res.status === 409) err = new ConflictError(message);
|
|
127
|
+
else if (res.status === 408 || res.status === 504) err = new TimeoutError(message);
|
|
128
|
+
else err = new LizardError(`API error ${res.status}: ${message}`);
|
|
129
|
+
err.status = res.status;
|
|
130
|
+
err.code = code;
|
|
131
|
+
throw err;
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
// src/platform/client.ts
|
|
@@ -501,12 +507,12 @@ var Process = class {
|
|
|
501
507
|
this.config = config;
|
|
502
508
|
}
|
|
503
509
|
/**
|
|
504
|
-
* Execute a command inside the
|
|
510
|
+
* Execute a command inside the sandbox and wait for it to complete.
|
|
505
511
|
*
|
|
506
512
|
* The command runs in a shell inside the Lizard sandbox and returns
|
|
507
513
|
* stdout, stderr, and the exit code when it finishes.
|
|
508
514
|
*
|
|
509
|
-
* @param cmd Shell command to run inside the
|
|
515
|
+
* @param cmd Shell command to run inside the sandbox.
|
|
510
516
|
* @param opts Optional execution options — environment variables, working
|
|
511
517
|
* directory, user, and timeout.
|
|
512
518
|
*
|
|
@@ -638,18 +644,19 @@ var Fs = class {
|
|
|
638
644
|
this.config = config;
|
|
639
645
|
}
|
|
640
646
|
/**
|
|
641
|
-
* Write a file into the
|
|
647
|
+
* Write a file into the sandbox filesystem.
|
|
642
648
|
*
|
|
643
649
|
* Creates parent directories automatically if they don't exist.
|
|
650
|
+
* Byte input must contain valid UTF-8; arbitrary binary uploads are unsupported.
|
|
644
651
|
*
|
|
645
652
|
* @example
|
|
646
653
|
* ```ts
|
|
647
654
|
* await sandbox.fs.write('/app/index.js', 'console.log("hello")')
|
|
648
655
|
* ```
|
|
649
656
|
*
|
|
650
|
-
* @example Write
|
|
657
|
+
* @example Write UTF-8 bytes:
|
|
651
658
|
* ```ts
|
|
652
|
-
* await sandbox.fs.write('/app/
|
|
659
|
+
* await sandbox.fs.write('/app/notes.txt', new TextEncoder().encode('hello'))
|
|
653
660
|
* ```
|
|
654
661
|
*/
|
|
655
662
|
async write(path, data, opts) {
|
|
@@ -662,7 +669,7 @@ var Fs = class {
|
|
|
662
669
|
if (!res.ok) await handleApiError(res);
|
|
663
670
|
}
|
|
664
671
|
/**
|
|
665
|
-
* Read a file from the
|
|
672
|
+
* Read a file from the sandbox filesystem.
|
|
666
673
|
*
|
|
667
674
|
* @returns The file contents as a UTF-8 string.
|
|
668
675
|
*
|
|
@@ -679,14 +686,7 @@ var Fs = class {
|
|
|
679
686
|
if (!res.ok) await handleApiError(res);
|
|
680
687
|
return res.text();
|
|
681
688
|
}
|
|
682
|
-
/**
|
|
683
|
-
* List files and directories at the given path inside the microVM.
|
|
684
|
-
*
|
|
685
|
-
* @example
|
|
686
|
-
* ```ts
|
|
687
|
-
* const entries = await sandbox.fs.list('/app')
|
|
688
|
-
* ```
|
|
689
|
-
*/
|
|
689
|
+
/** Read a file as bytes. */
|
|
690
690
|
async readBytes(path, opts) {
|
|
691
691
|
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`);
|
|
692
692
|
url.searchParams.set("path", path);
|
|
@@ -695,6 +695,14 @@ var Fs = class {
|
|
|
695
695
|
if (!res.ok) await handleApiError(res);
|
|
696
696
|
return new Uint8Array(await res.arrayBuffer());
|
|
697
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* List files and directories at the given path inside the sandbox.
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* ```ts
|
|
703
|
+
* const entries = await sandbox.fs.list('/app')
|
|
704
|
+
* ```
|
|
705
|
+
*/
|
|
698
706
|
async list(path, opts) {
|
|
699
707
|
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/list`);
|
|
700
708
|
url.searchParams.set("path", path);
|
|
@@ -704,7 +712,7 @@ var Fs = class {
|
|
|
704
712
|
return res.json();
|
|
705
713
|
}
|
|
706
714
|
/**
|
|
707
|
-
* Remove a file or directory from the
|
|
715
|
+
* Remove a file or directory from the sandbox filesystem.
|
|
708
716
|
*/
|
|
709
717
|
async remove(path, opts) {
|
|
710
718
|
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files`, {
|
|
@@ -715,7 +723,7 @@ var Fs = class {
|
|
|
715
723
|
if (!res.ok) await handleApiError(res);
|
|
716
724
|
}
|
|
717
725
|
/**
|
|
718
|
-
* Create a directory (and any missing parents) inside the
|
|
726
|
+
* Create a directory (and any missing parents) inside the sandbox.
|
|
719
727
|
*/
|
|
720
728
|
async makeDir(path, opts) {
|
|
721
729
|
await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
|
|
@@ -943,11 +951,7 @@ var Sandbox = class extends SandboxClient {
|
|
|
943
951
|
* Verifies the sandbox exists and is reachable, then returns a handle to it.
|
|
944
952
|
* Throws `NotFoundError` if it has been killed or has expired.
|
|
945
953
|
*
|
|
946
|
-
*
|
|
947
|
-
* reconnecting to might be paused. Sandboxes are pods now and pause/resume is a
|
|
948
|
-
* `501` on every one of them, so that call turned every `connect()` into an
|
|
949
|
-
* error against a perfectly healthy sandbox. Connecting does not need to change
|
|
950
|
-
* a sandbox's state, so it no longer tries to.
|
|
954
|
+
* Connecting reads sandbox metadata without changing its state.
|
|
951
955
|
*
|
|
952
956
|
* @example
|
|
953
957
|
* ```ts
|
|
@@ -972,29 +976,25 @@ var Sandbox = class extends SandboxClient {
|
|
|
972
976
|
/**
|
|
973
977
|
* Kill the sandbox and release its resources immediately.
|
|
974
978
|
*
|
|
975
|
-
* @returns `true` if the
|
|
979
|
+
* @returns `true` if the sandbox was terminated, `false` if it was already gone.
|
|
976
980
|
*/
|
|
977
981
|
async kill(opts) {
|
|
978
982
|
return SandboxClient.killSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
979
983
|
}
|
|
980
984
|
/**
|
|
981
|
-
*
|
|
985
|
+
* Request sandbox pause. The Kubernetes backend returns HTTP 501.
|
|
982
986
|
*
|
|
983
|
-
* @deprecated
|
|
984
|
-
*
|
|
985
|
-
* checkpoint of the pod, which is not built. To park work across a gap, put it on a
|
|
986
|
-
* {@link Volume} and create a fresh sandbox on that volume later; the volume is the
|
|
987
|
-
* part that is meant to outlive a sandbox.
|
|
987
|
+
* @deprecated Unsupported on Kubernetes. Save files to a {@link Volume}
|
|
988
|
+
* and attach it to a new sandbox instead.
|
|
988
989
|
*/
|
|
989
990
|
async pause(opts) {
|
|
990
991
|
return SandboxClient.pauseSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
991
992
|
}
|
|
992
993
|
/**
|
|
993
|
-
*
|
|
994
|
+
* Request sandbox resume. The Kubernetes backend returns HTTP 501.
|
|
994
995
|
*
|
|
995
|
-
* @deprecated
|
|
996
|
-
*
|
|
997
|
-
* calls this, so reconnecting to a running sandbox works without it.
|
|
996
|
+
* @deprecated Unsupported on Kubernetes. Use {@link Sandbox.connect} to
|
|
997
|
+
* reconnect to a running sandbox.
|
|
998
998
|
*/
|
|
999
999
|
async resume(opts) {
|
|
1000
1000
|
return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
@@ -1014,25 +1014,27 @@ var Sandbox = class extends SandboxClient {
|
|
|
1014
1014
|
return SandboxClient.setTimeoutSandbox(this.sandboxId, timeoutMs, this.resolveOpts(opts));
|
|
1015
1015
|
}
|
|
1016
1016
|
/**
|
|
1017
|
-
*
|
|
1017
|
+
* Register a public HTTPS route and return its hostname without a scheme.
|
|
1018
1018
|
*
|
|
1019
|
-
* Useful for accessing HTTP servers started inside the
|
|
1019
|
+
* Useful for accessing HTTP servers started inside the sandbox from your
|
|
1020
1020
|
* agent or tests without additional tunneling.
|
|
1021
1021
|
*
|
|
1022
1022
|
* @example
|
|
1023
1023
|
* ```ts
|
|
1024
|
-
*
|
|
1025
|
-
* const
|
|
1026
|
-
*
|
|
1024
|
+
* // Start an HTTP server on port 3000 before exposing it.
|
|
1025
|
+
* const hostname = await sandbox.getHost(3000)
|
|
1026
|
+
* console.log(`https://${hostname}`)
|
|
1027
1027
|
* ```
|
|
1028
1028
|
*/
|
|
1029
1029
|
async getHost(port, opts) {
|
|
1030
1030
|
const { hostname } = await SandboxClient.exposeSandboxPort(this.sandboxId, port, this.resolveOpts(opts));
|
|
1031
1031
|
return hostname;
|
|
1032
1032
|
}
|
|
1033
|
+
/** Fork is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
1033
1034
|
fork(opts = {}) {
|
|
1034
1035
|
return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/fork`, { count: opts.count ?? 1, timeoutMs: opts.timeoutMs ?? 0 });
|
|
1035
1036
|
}
|
|
1037
|
+
/** Snapshot creation is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
1036
1038
|
snapshot(name) {
|
|
1037
1039
|
return new PlatformClient(this.resolveOpts()).post(`/api/sandboxes/${this.sandboxId}/snapshot`, { name });
|
|
1038
1040
|
}
|
|
@@ -1045,6 +1047,7 @@ var Sandbox = class extends SandboxClient {
|
|
|
1045
1047
|
static snapshots(projectId, opts) {
|
|
1046
1048
|
return new PlatformClient(opts ?? {}).get(`/api/projects/${projectId}/snapshots`);
|
|
1047
1049
|
}
|
|
1050
|
+
/** Snapshot restore is unsupported on Kubernetes; the backend returns HTTP 501. */
|
|
1048
1051
|
static async restore(snapshotId, opts) {
|
|
1049
1052
|
const result = await new PlatformClient(opts ?? {}).post(`/api/sandbox-snapshots/${encodeURIComponent(snapshotId)}/fork`, { timeoutMs: opts?.timeoutMs ?? 0 });
|
|
1050
1053
|
return new this({ ...opts, sandboxId: result.sandboxId ?? result.id });
|
|
@@ -1157,7 +1160,7 @@ var AddonsAPI = class {
|
|
|
1157
1160
|
});
|
|
1158
1161
|
return this.get(projectId, addonId);
|
|
1159
1162
|
}
|
|
1160
|
-
/** Restart an addon
|
|
1163
|
+
/** Restart an addon. */
|
|
1161
1164
|
redeploy(projectId, addonId) {
|
|
1162
1165
|
return this.client.post(`/api/projects/${projectId}/addons/${addonId}/redeploy`, {});
|
|
1163
1166
|
}
|
|
@@ -1568,8 +1571,8 @@ var Volume = class _Volume {
|
|
|
1568
1571
|
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1569
1572
|
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1570
1573
|
*
|
|
1571
|
-
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1572
|
-
*
|
|
1574
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
|
|
1575
|
+
* so an existing volume keeps its size. Call {@link Volume.resize} to change it.
|
|
1573
1576
|
*/
|
|
1574
1577
|
static async getOrCreate(projectId, name, opts) {
|
|
1575
1578
|
const config = new ConnectionConfig(opts);
|
|
@@ -1609,6 +1612,32 @@ var Volume = class _Volume {
|
|
|
1609
1612
|
});
|
|
1610
1613
|
if (!res.ok) await handleApiError(res);
|
|
1611
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Resize a volume, by name or by id, to `sizeGb`.
|
|
1617
|
+
*
|
|
1618
|
+
* The change is in place and online: the size is a quota, so no data is copied,
|
|
1619
|
+
* it completes in well under a second, and a sandbox that has the volume mounted
|
|
1620
|
+
* keeps running and sees the new size immediately. Both growing and shrinking are
|
|
1621
|
+
* allowed, but a shrink must leave at least 10% of the new size free — otherwise
|
|
1622
|
+
* it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
|
|
1623
|
+
*
|
|
1624
|
+
* Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
|
|
1625
|
+
* `volume_resize_in_progress`, `volume_not_provisioned`,
|
|
1626
|
+
* `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
|
|
1627
|
+
* the size is left unchanged).
|
|
1628
|
+
*
|
|
1629
|
+
* @returns The volume's updated info, including `sizeEnforced`.
|
|
1630
|
+
*/
|
|
1631
|
+
static async resize(projectId, nameOrId, sizeGb, opts) {
|
|
1632
|
+
const config = new ConnectionConfig(opts);
|
|
1633
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
|
|
1634
|
+
method: "PATCH",
|
|
1635
|
+
headers: config.headers,
|
|
1636
|
+
body: JSON.stringify({ sizeGb })
|
|
1637
|
+
});
|
|
1638
|
+
if (!res.ok) await handleApiError(res);
|
|
1639
|
+
return res.json();
|
|
1640
|
+
}
|
|
1612
1641
|
async getInfo(projectId) {
|
|
1613
1642
|
const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
|
|
1614
1643
|
headers: this.config.headers
|
|
@@ -1616,6 +1645,13 @@ var Volume = class _Volume {
|
|
|
1616
1645
|
if (!res.ok) await handleApiError(res);
|
|
1617
1646
|
return res.json();
|
|
1618
1647
|
}
|
|
1648
|
+
/** Resize this volume in place — see the static {@link Volume.resize}. */
|
|
1649
|
+
async resize(projectId, sizeGb) {
|
|
1650
|
+
return _Volume.resize(projectId, this.volumeId, sizeGb, {
|
|
1651
|
+
apiKey: this.config.apiKey,
|
|
1652
|
+
apiUrl: this.config.apiUrl
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1619
1655
|
async delete(projectId) {
|
|
1620
1656
|
const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
|
|
1621
1657
|
method: "DELETE",
|
|
@@ -1649,6 +1685,7 @@ var Lizard = class {
|
|
|
1649
1685
|
getOrCreate: async (name, opts) => Volume.getOrCreate(await this.projectId(), name, { ...await this.connectionOpts(), ...opts }),
|
|
1650
1686
|
get: async (nameOrId, opts) => Volume.get(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts }),
|
|
1651
1687
|
list: async (opts) => Volume.list(await this.projectId(), { ...await this.connectionOpts(), ...opts }),
|
|
1688
|
+
resize: async (nameOrId, sizeGb, opts) => Volume.resize(await this.projectId(), nameOrId, sizeGb, { ...await this.connectionOpts(), ...opts }),
|
|
1652
1689
|
delete: async (nameOrId, opts) => Volume.delete(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts })
|
|
1653
1690
|
};
|
|
1654
1691
|
this.config = new ConnectionConfig(opts);
|
|
@@ -1759,8 +1796,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
1759
1796
|
*
|
|
1760
1797
|
* @returns Execution result with stdout, stderr, results, and any error.
|
|
1761
1798
|
*
|
|
1762
|
-
*
|
|
1763
|
-
* default context exists — which cannot happen in normal usage.
|
|
1799
|
+
* Code errors appear in `execution.error`; request failures throw.
|
|
1764
1800
|
*
|
|
1765
1801
|
* @example
|
|
1766
1802
|
* ```ts
|