@vercel/sandbox 1.2.0 → 1.2.1

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.
Files changed (60) hide show
  1. package/dist/api-client/api-client.js +7 -0
  2. package/dist/api-client/api-client.js.map +1 -1
  3. package/dist/version.d.ts +1 -1
  4. package/dist/version.js +1 -1
  5. package/package.json +6 -1
  6. package/.turbo/turbo-build.log +0 -4
  7. package/.turbo/turbo-test.log +0 -24
  8. package/.turbo/turbo-typecheck.log +0 -4
  9. package/CHANGELOG.md +0 -277
  10. package/__mocks__/picocolors.ts +0 -13
  11. package/scripts/inject-version.ts +0 -11
  12. package/src/api-client/api-client.test.ts +0 -228
  13. package/src/api-client/api-client.ts +0 -592
  14. package/src/api-client/api-error.ts +0 -46
  15. package/src/api-client/base-client.ts +0 -171
  16. package/src/api-client/file-writer.ts +0 -90
  17. package/src/api-client/index.ts +0 -2
  18. package/src/api-client/validators.ts +0 -146
  19. package/src/api-client/with-retry.ts +0 -131
  20. package/src/auth/api.ts +0 -31
  21. package/src/auth/error.ts +0 -8
  22. package/src/auth/file.ts +0 -69
  23. package/src/auth/index.ts +0 -9
  24. package/src/auth/infer-scope.test.ts +0 -178
  25. package/src/auth/linked-project.test.ts +0 -86
  26. package/src/auth/linked-project.ts +0 -40
  27. package/src/auth/oauth.ts +0 -333
  28. package/src/auth/poll-for-token.ts +0 -89
  29. package/src/auth/project.ts +0 -92
  30. package/src/auth/zod.ts +0 -16
  31. package/src/command.test.ts +0 -103
  32. package/src/command.ts +0 -287
  33. package/src/constants.ts +0 -1
  34. package/src/index.ts +0 -4
  35. package/src/sandbox.test.ts +0 -171
  36. package/src/sandbox.ts +0 -677
  37. package/src/snapshot.ts +0 -110
  38. package/src/utils/array.ts +0 -15
  39. package/src/utils/consume-readable.ts +0 -12
  40. package/src/utils/decode-base64-url.ts +0 -14
  41. package/src/utils/dev-credentials.test.ts +0 -217
  42. package/src/utils/dev-credentials.ts +0 -196
  43. package/src/utils/get-credentials.test.ts +0 -20
  44. package/src/utils/get-credentials.ts +0 -183
  45. package/src/utils/jwt-expiry.test.ts +0 -125
  46. package/src/utils/jwt-expiry.ts +0 -105
  47. package/src/utils/log.ts +0 -20
  48. package/src/utils/normalizePath.test.ts +0 -114
  49. package/src/utils/normalizePath.ts +0 -33
  50. package/src/utils/resolveSignal.ts +0 -24
  51. package/src/utils/types.test.js +0 -7
  52. package/src/utils/types.ts +0 -23
  53. package/src/version.ts +0 -2
  54. package/test-utils/mock-response.ts +0 -12
  55. package/tsconfig.json +0 -16
  56. package/turbo.json +0 -9
  57. package/typedoc.json +0 -13
  58. package/vercel.json +0 -9
  59. package/vitest.config.ts +0 -9
  60. package/vitest.setup.ts +0 -4
package/src/snapshot.ts DELETED
@@ -1,110 +0,0 @@
1
- import type { SnapshotMetadata } from "./api-client";
2
- import { APIClient } from "./api-client";
3
- import { Credentials, getCredentials } from "./utils/get-credentials";
4
-
5
- /** @inline */
6
- interface GetSnapshotParams {
7
- /**
8
- * Unique identifier of the snapshot.
9
- */
10
- snapshotId: string;
11
- /**
12
- * An AbortSignal to cancel the operation.
13
- */
14
- signal?: AbortSignal;
15
- }
16
-
17
- /**
18
- * A Snapshot is a saved state of a Sandbox that can be used to create new Sandboxes
19
- *
20
- * Use {@link Sandbox.snapshot} or {@link Snapshot.get} to construct.
21
- * @hideconstructor
22
- */
23
- export class Snapshot {
24
- private readonly client: APIClient;
25
-
26
- /**
27
- * Unique ID of this snapshot.
28
- */
29
- public get snapshotId(): string {
30
- return this.snapshot.id;
31
- }
32
-
33
- /**
34
- * The ID the sandbox from which this snapshot was created.
35
- */
36
- public get sourceSandboxId(): string {
37
- return this.snapshot.sourceSandboxId;
38
- }
39
-
40
- /**
41
- * The status of the snapshot.
42
- */
43
- public get status(): SnapshotMetadata["status"] {
44
- return this.snapshot.status;
45
- }
46
-
47
- /**
48
- * Internal metadata about this snapshot.
49
- */
50
- private snapshot: SnapshotMetadata;
51
-
52
- /**
53
- * Create a new Snapshot instance.
54
- *
55
- * @param client - API client used to communicate with the backend
56
- * @param snapshot - Snapshot metadata
57
- */
58
- constructor({
59
- client,
60
- snapshot,
61
- }: {
62
- client: APIClient;
63
- snapshot: SnapshotMetadata;
64
- }) {
65
- this.client = client;
66
- this.snapshot = snapshot;
67
- }
68
-
69
- /**
70
- * Retrieve an existing snapshot.
71
- *
72
- * @param params - Get parameters and optional credentials.
73
- * @returns A promise resolving to the {@link Sandbox}.
74
- */
75
- static async get(
76
- params: GetSnapshotParams | (GetSnapshotParams & Credentials),
77
- ): Promise<Snapshot> {
78
- const credentials = await getCredentials(params);
79
- const client = new APIClient({
80
- teamId: credentials.teamId,
81
- token: credentials.token,
82
- });
83
-
84
- const sandbox = await client.getSnapshot({
85
- snapshotId: params.snapshotId,
86
- signal: params.signal,
87
- });
88
-
89
- return new Snapshot({
90
- client,
91
- snapshot: sandbox.json.snapshot,
92
- });
93
- }
94
-
95
- /**
96
- * Delete this snapshot.
97
- *
98
- * @param opts - Optional parameters.
99
- * @param opts.signal - An AbortSignal to cancel the operation.
100
- * @returns A promise that resolves once the snapshot has been deleted.
101
- */
102
- async delete(opts?: { signal?: AbortSignal }): Promise<void> {
103
- const response = await this.client.deleteSnapshot({
104
- snapshotId: this.snapshot.id,
105
- signal: opts?.signal,
106
- });
107
-
108
- this.snapshot = response.json.snapshot;
109
- }
110
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Returns an array from the given item. If the item is an array it will be
3
- * returned as a it is, otherwise it will be returned as a single item array.
4
- * If the item is undefined or null an empty array will be returned.
5
- *
6
- * @param item The item to convert to an array.
7
- * @returns An array.
8
- */
9
- export function array<T>(item?: null | T | T[]): T[] {
10
- return item !== undefined && item !== null
11
- ? Array.isArray(item)
12
- ? item
13
- : [item]
14
- : [];
15
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Consumes a readable entirely concatenating all content in a single Buffer
3
- * @param readable A Readable stream
4
- */
5
- export function consumeReadable(readable: NodeJS.ReadableStream) {
6
- return new Promise<Buffer>((resolve, reject) => {
7
- const chunks: Buffer[] = [];
8
- readable.on("error", (err) => reject(err));
9
- readable.on("data", (chunk) => chunks.push(chunk));
10
- readable.on("end", () => resolve(Buffer.concat(chunks)));
11
- });
12
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Decode a Base64 URL-encoded string into a JSON object.
3
- *
4
- * @param base64Url - The Base64 URL-encoded string to decode.
5
- * @returns The decoded JSON object or null if decoding fails.
6
- */
7
- export function decodeBase64Url(base64Url: string) {
8
- return JSON.parse(
9
- Buffer.from(
10
- base64Url.replace(/-/g, "+").replace(/_/g, "/"),
11
- "base64",
12
- ).toString("utf8"),
13
- );
14
- }
@@ -1,217 +0,0 @@
1
- import { signInAndGetToken, generateCredentials } from "./dev-credentials";
2
- import { describe, expect, test, vi, beforeEach, type Mock } from "vitest";
3
- import { factory } from "factoree";
4
- import { setTimeout } from "node:timers/promises";
5
- import { DeviceAuthorizationRequest, OAuth } from "../auth";
6
-
7
- vi.mock("picocolors");
8
-
9
- vi.mock("../auth/index", () => ({
10
- getAuth: vi.fn(),
11
- inferScope: vi.fn(),
12
- updateAuthConfig: vi.fn(),
13
- OAuth: vi.fn(),
14
- pollForToken: vi.fn(),
15
- }));
16
-
17
- import * as auth from "../auth/index";
18
-
19
- describe("signInAndGetToken", () => {
20
- test("times out after provided timeout", async () => {
21
- const consoleError = vi.spyOn(console, "error").mockReturnValue();
22
- const promise = signInAndGetToken(
23
- {
24
- getAuth: () => null,
25
- OAuth: async () => {
26
- return createOAuthFactory({
27
- async deviceAuthorizationRequest() {
28
- return createDeviceAuthorizationRequest({
29
- device_code: "device_code",
30
- user_code: "user_code",
31
- verification_uri_complete: `https://example.vercel.sh/device_code?code=user_code`,
32
- verification_uri: "https://example.vercel.sh/device_code",
33
- });
34
- },
35
- });
36
- },
37
- pollForToken: async function* () {
38
- await setTimeout(500);
39
- },
40
- },
41
- `100 milliseconds`,
42
- );
43
-
44
- await expect(promise).rejects.toThrowError(
45
- /Authentication flow timed out after 100 milliseconds./,
46
- );
47
-
48
- const printed = consoleError.mock.calls.map((x) => x.join(" ")).join("\n");
49
- expect(printed).toMatchInlineSnapshot(`
50
- "<yellow><dim>[vercel/sandbox]</dim> No VERCEL_OIDC_TOKEN environment variable found, initiating device authorization flow...
51
- <dim>[vercel/sandbox]</dim> │ <bold>help:</bold> this flow only happens on development environment.
52
- <dim>[vercel/sandbox]</dim> │ In production, make sure to set up a proper token, or set up Vercel OIDC [https://vercel.com/docs/oidc].</yellow>
53
- <blue><dim>[vercel/sandbox]</dim> ╰▶ To authenticate, visit: https://example.vercel.sh/device_code?code=user_code
54
- <dim>[vercel/sandbox]</dim> or visit <italic>https://example.vercel.sh/device_code</italic> and type <bold>user_code</bold>
55
- <dim>[vercel/sandbox]</dim> Press <bold><return></bold> to open in your browser</blue>
56
- <red><dim>[vercel/sandbox]</dim> <bold>error:</bold> Authentication failed: Authentication flow timed out after 100 milliseconds.
57
- <dim>[vercel/sandbox]</dim> │ Make sure to provide a token to avoid prompting an interactive flow.
58
- <dim>[vercel/sandbox]</dim> ╰▶ <bold>help:</bold> Link your project with <italic><dim>\`</dim>npx vercel link<dim>\`</dim></italic> to refresh OIDC token automatically.</red>"
59
- `);
60
- });
61
- });
62
-
63
- const createOAuthFactory = factory<Awaited<OAuth>>();
64
- const createDeviceAuthorizationRequest = factory<DeviceAuthorizationRequest>();
65
-
66
- describe("generateCredentials", () => {
67
- beforeEach(() => {
68
- vi.clearAllMocks();
69
- });
70
-
71
- test("triggers sign-in when auth exists but has no token", async () => {
72
- // Auth object with refreshToken but no token - this was the bug
73
- (auth.getAuth as Mock).mockReturnValue({
74
- refreshToken: "refresh_xxx",
75
- expiresAt: new Date(Date.now() + 100000),
76
- });
77
-
78
- (auth.OAuth as Mock).mockResolvedValue(
79
- createOAuthFactory({
80
- async deviceAuthorizationRequest() {
81
- return createDeviceAuthorizationRequest({
82
- device_code: "device_code",
83
- user_code: "user_code",
84
- verification_uri_complete: "https://vercel.com/device",
85
- verification_uri: "https://vercel.com/device",
86
- });
87
- },
88
- }),
89
- );
90
-
91
- (auth.pollForToken as Mock).mockImplementation(async function* () {
92
- // Simulate successful auth by updating getAuth to return a token
93
- (auth.getAuth as Mock).mockReturnValue({ token: "new_token" });
94
- yield { _tag: "Response" as const };
95
- });
96
-
97
- (auth.inferScope as Mock).mockResolvedValue({
98
- teamId: "team_xxx",
99
- projectId: "prj_xxx",
100
- created: false,
101
- });
102
-
103
- const result = await generateCredentials({});
104
-
105
- expect(auth.pollForToken).toHaveBeenCalled();
106
- expect(result).toEqual({
107
- token: "new_token",
108
- teamId: "team_xxx",
109
- projectId: "prj_xxx",
110
- });
111
- });
112
-
113
- test("triggers sign-in when auth is null", async () => {
114
- (auth.getAuth as Mock).mockReturnValue(null);
115
-
116
- (auth.OAuth as Mock).mockResolvedValue(
117
- createOAuthFactory({
118
- async deviceAuthorizationRequest() {
119
- return createDeviceAuthorizationRequest({
120
- device_code: "device_code",
121
- user_code: "user_code",
122
- verification_uri_complete: "https://vercel.com/device",
123
- verification_uri: "https://vercel.com/device",
124
- });
125
- },
126
- }),
127
- );
128
-
129
- (auth.pollForToken as Mock).mockImplementation(async function* () {
130
- (auth.getAuth as Mock).mockReturnValue({ token: "new_token" });
131
- yield { _tag: "Response" as const };
132
- });
133
-
134
- (auth.inferScope as Mock).mockResolvedValue({
135
- teamId: "team_xxx",
136
- projectId: "prj_xxx",
137
- created: false,
138
- });
139
-
140
- await generateCredentials({});
141
-
142
- expect(auth.pollForToken).toHaveBeenCalled();
143
- });
144
-
145
- test("skips sign-in when auth has valid token", async () => {
146
- (auth.getAuth as Mock).mockReturnValue({ token: "valid_token" });
147
-
148
- (auth.inferScope as Mock).mockResolvedValue({
149
- teamId: "team_xxx",
150
- projectId: "prj_xxx",
151
- created: false,
152
- });
153
-
154
- const result = await generateCredentials({});
155
-
156
- expect(auth.pollForToken).not.toHaveBeenCalled();
157
- expect(auth.OAuth).not.toHaveBeenCalled();
158
- expect(result).toEqual({
159
- token: "valid_token",
160
- teamId: "team_xxx",
161
- projectId: "prj_xxx",
162
- });
163
- });
164
-
165
- test("calls inferScope only once when deriving both teamId and projectId", async () => {
166
- (auth.getAuth as Mock).mockReturnValue({ token: "valid_token" });
167
-
168
- (auth.inferScope as Mock).mockResolvedValue({
169
- teamId: "team_xxx",
170
- projectId: "prj_xxx",
171
- created: false,
172
- });
173
-
174
- await generateCredentials({});
175
-
176
- expect(auth.inferScope).toHaveBeenCalledTimes(1);
177
- });
178
-
179
- test("does not call inferScope when both teamId and projectId are provided", async () => {
180
- (auth.getAuth as Mock).mockReturnValue({ token: "valid_token" });
181
-
182
- const result = await generateCredentials({
183
- teamId: "provided_team",
184
- projectId: "provided_project",
185
- });
186
-
187
- expect(auth.inferScope).not.toHaveBeenCalled();
188
- expect(result).toEqual({
189
- token: "valid_token",
190
- teamId: "provided_team",
191
- projectId: "provided_project",
192
- });
193
- });
194
-
195
- test("calls inferScope with provided teamId when only teamId is given", async () => {
196
- (auth.getAuth as Mock).mockReturnValue({ token: "valid_token" });
197
-
198
- (auth.inferScope as Mock).mockResolvedValue({
199
- teamId: "provided_team",
200
- projectId: "inferred_project",
201
- created: false,
202
- });
203
-
204
- const result = await generateCredentials({ teamId: "provided_team" });
205
-
206
- expect(auth.inferScope).toHaveBeenCalledTimes(1);
207
- expect(auth.inferScope).toHaveBeenCalledWith({
208
- teamId: "provided_team",
209
- token: "valid_token",
210
- });
211
- expect(result).toEqual({
212
- token: "valid_token",
213
- teamId: "provided_team",
214
- projectId: "inferred_project",
215
- });
216
- });
217
- });
@@ -1,196 +0,0 @@
1
- import pico from "picocolors";
2
- import type { Credentials } from "./get-credentials";
3
- import ms from "ms";
4
- import * as Log from "./log";
5
-
6
- async function importAuth() {
7
- const auth = await import("../auth/index");
8
- return auth;
9
- }
10
-
11
- export function shouldPromptForCredentials(): boolean {
12
- return (
13
- process.env.NODE_ENV !== "production" &&
14
- !["1", "true"].includes(process.env.CI || "") &&
15
- process.stdout.isTTY &&
16
- process.stdin.isTTY
17
- );
18
- }
19
-
20
- /**
21
- * Returns cached credentials for the given team/project combination.
22
- *
23
- * @remarks
24
- * The cache is keyed by `teamId` and `projectId`. A new credential generation
25
- * is triggered only when these values change or when a previous attempt failed.
26
- *
27
- * **Important:** Successfully resolved credentials are cached indefinitely and
28
- * will not be refreshed even if the token expires. Cache invalidation only occurs
29
- * on rejection (error). This is intentional for development use cases where
30
- * short-lived sessions don't require proactive token refresh.
31
- */
32
- export const cachedGenerateCredentials = (() => {
33
- let cache:
34
- | [{ teamId?: string; projectId?: string }, Promise<Credentials>]
35
- | null = null;
36
- return async (opts: { projectId?: string; teamId?: string }) => {
37
- if (
38
- !cache ||
39
- cache[0].teamId !== opts.teamId ||
40
- cache[0].projectId !== opts.projectId
41
- ) {
42
- const promise = generateCredentials(opts).catch((err) => {
43
- cache = null;
44
- throw err;
45
- });
46
- cache = [opts, promise];
47
- }
48
- const v = await cache[1];
49
- Log.write(
50
- "warn",
51
- `using inferred credentials team=${v.teamId} project=${v.projectId}`,
52
- );
53
- return v;
54
- };
55
- })();
56
-
57
- /**
58
- * Generates credentials by authenticating and inferring scope.
59
- *
60
- * @internal This is exported for testing purposes. Consider using
61
- * {@link cachedGenerateCredentials} instead, which caches the result
62
- * to avoid redundant authentication flows.
63
- */
64
- export async function generateCredentials(opts: {
65
- teamId?: string;
66
- projectId?: string;
67
- }): Promise<Credentials> {
68
- const { OAuth, pollForToken, getAuth, updateAuthConfig, inferScope } =
69
- await importAuth();
70
- let auth = getAuth();
71
- if (!auth?.token) {
72
- const timeout: ms.StringValue = process.env.VERCEL_URL
73
- ? /* when deployed to vercel we don't want to have a long timeout */ "1 minute"
74
- : "5 minutes";
75
- auth = await signInAndGetToken({ OAuth, pollForToken, getAuth }, timeout);
76
- }
77
- if (
78
- auth?.refreshToken &&
79
- auth.expiresAt &&
80
- auth.expiresAt.getTime() <= Date.now()
81
- ) {
82
- const oauth = await OAuth();
83
- const newToken = await oauth.refreshToken(auth.refreshToken);
84
- auth = {
85
- expiresAt: new Date(Date.now() + newToken.expires_in * 1000),
86
- token: newToken.access_token,
87
- refreshToken: newToken.refresh_token || auth.refreshToken,
88
- };
89
- updateAuthConfig(auth);
90
- }
91
-
92
- if (!auth?.token) {
93
- throw new Error(
94
- [
95
- `Failed to retrieve authentication token.`,
96
- `${pico.bold("hint:")} Set VERCEL_OIDC_TOKEN or provide a Vercel API token.`,
97
- "├▶ Sandbox docs: https://vercel.com/docs/sandbox",
98
- "╰▶ Access tokens: https://vercel.com/kb/guide/how-do-i-use-a-vercel-api-access-token",
99
- ].join("\n"),
100
- );
101
- }
102
-
103
- if (opts.teamId && opts.projectId) {
104
- return {
105
- token: auth.token,
106
- teamId: opts.teamId,
107
- projectId: opts.projectId,
108
- };
109
- }
110
-
111
- const scope = await inferScope({ teamId: opts.teamId, token: auth.token });
112
-
113
- if (scope.created) {
114
- Log.write(
115
- "info",
116
- `Created default project "${scope.projectId}" in team "${scope.teamId}".`,
117
- );
118
- }
119
-
120
- return {
121
- token: auth.token,
122
- teamId: opts.teamId || scope.teamId,
123
- projectId: opts.projectId || scope.projectId,
124
- };
125
- }
126
-
127
- export async function signInAndGetToken(
128
- auth: Pick<
129
- Awaited<ReturnType<typeof importAuth>>,
130
- "OAuth" | "getAuth" | "pollForToken"
131
- >,
132
- timeout: ms.StringValue,
133
- ) {
134
- Log.write("warn", [
135
- `No VERCEL_OIDC_TOKEN environment variable found, initiating device authorization flow...`,
136
- `│ ${pico.bold("help:")} this flow only happens on development environment.`,
137
- `│ In production, make sure to set up a proper token, or set up Vercel OIDC [https://vercel.com/docs/oidc].`,
138
- ]);
139
- const oauth = await auth.OAuth();
140
- const request = await oauth.deviceAuthorizationRequest();
141
- Log.write("info", [
142
- `╰▶ To authenticate, visit: ${request.verification_uri_complete}`,
143
- ` or visit ${pico.italic(request.verification_uri)} and type ${pico.bold(request.user_code)}`,
144
- ` Press ${pico.bold("<return>")} to open in your browser`,
145
- ]);
146
-
147
- let error: Error | undefined;
148
- const generator = auth.pollForToken({ request, oauth });
149
- let done = false;
150
- let spawnedTimeout = setTimeout(() => {
151
- if (done) return;
152
- const message = [
153
- `Authentication flow timed out after ${timeout}.`,
154
- `│ Make sure to provide a token to avoid prompting an interactive flow.`,
155
- `╰▶ ${pico.bold("help:")} Link your project with ${Log.code("npx vercel link")} to refresh OIDC token automatically.`,
156
- ].join("\n");
157
- error = new Error(message);
158
- // Note: generator.return() initiates cooperative cancellation. The generator's
159
- // finally block will abort pending setTimeout calls, but any in-flight HTTP
160
- // request will complete before the generator terminates. This is acceptable
161
- // for this dev-only timeout scenario.
162
- generator.return();
163
- }, ms(timeout));
164
- try {
165
- for await (const event of generator) {
166
- switch (event._tag) {
167
- case "SlowDown":
168
- case "Timeout":
169
- case "Response":
170
- break;
171
- case "Error":
172
- error = event.error;
173
- break;
174
- default:
175
- throw new Error(
176
- `Unknown event type: ${JSON.stringify(event satisfies never)}`,
177
- );
178
- }
179
- }
180
- } finally {
181
- done = true;
182
- clearTimeout(spawnedTimeout);
183
- }
184
-
185
- if (error) {
186
- Log.write(
187
- "error",
188
- `${pico.bold("error:")} Authentication failed: ${error.message}`,
189
- );
190
- throw error;
191
- }
192
-
193
- Log.write("success", `${pico.bold("done!")} Authenticated successfully!`);
194
- const stored = auth.getAuth();
195
- return stored;
196
- }
@@ -1,20 +0,0 @@
1
- import { test, expect, beforeEach } from "vitest";
2
- import {
3
- getCredentials,
4
- LocalOidcContextError,
5
- VercelOidcContextError,
6
- } from "./get-credentials";
7
-
8
- beforeEach(() => {
9
- delete process.env.VERCEL_OIDC_TOKEN;
10
- });
11
-
12
- test("explains how to set up oidc in local", async () => {
13
- delete process.env.VERCEL_URL;
14
- await expect(getCredentials()).rejects.toThrowError(LocalOidcContextError);
15
- });
16
-
17
- test("explains how to set up oidc in vercel", async () => {
18
- process.env.VERCEL_URL = "example.vercel.sh";
19
- await expect(getCredentials()).rejects.toThrowError(VercelOidcContextError);
20
- });