@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.
- package/dist/api-client/api-client.js +7 -0
- package/dist/api-client/api-client.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -24
- package/.turbo/turbo-typecheck.log +0 -4
- package/CHANGELOG.md +0 -277
- package/__mocks__/picocolors.ts +0 -13
- package/scripts/inject-version.ts +0 -11
- package/src/api-client/api-client.test.ts +0 -228
- package/src/api-client/api-client.ts +0 -592
- package/src/api-client/api-error.ts +0 -46
- package/src/api-client/base-client.ts +0 -171
- package/src/api-client/file-writer.ts +0 -90
- package/src/api-client/index.ts +0 -2
- package/src/api-client/validators.ts +0 -146
- package/src/api-client/with-retry.ts +0 -131
- package/src/auth/api.ts +0 -31
- package/src/auth/error.ts +0 -8
- package/src/auth/file.ts +0 -69
- package/src/auth/index.ts +0 -9
- package/src/auth/infer-scope.test.ts +0 -178
- package/src/auth/linked-project.test.ts +0 -86
- package/src/auth/linked-project.ts +0 -40
- package/src/auth/oauth.ts +0 -333
- package/src/auth/poll-for-token.ts +0 -89
- package/src/auth/project.ts +0 -92
- package/src/auth/zod.ts +0 -16
- package/src/command.test.ts +0 -103
- package/src/command.ts +0 -287
- package/src/constants.ts +0 -1
- package/src/index.ts +0 -4
- package/src/sandbox.test.ts +0 -171
- package/src/sandbox.ts +0 -677
- package/src/snapshot.ts +0 -110
- package/src/utils/array.ts +0 -15
- package/src/utils/consume-readable.ts +0 -12
- package/src/utils/decode-base64-url.ts +0 -14
- package/src/utils/dev-credentials.test.ts +0 -217
- package/src/utils/dev-credentials.ts +0 -196
- package/src/utils/get-credentials.test.ts +0 -20
- package/src/utils/get-credentials.ts +0 -183
- package/src/utils/jwt-expiry.test.ts +0 -125
- package/src/utils/jwt-expiry.ts +0 -105
- package/src/utils/log.ts +0 -20
- package/src/utils/normalizePath.test.ts +0 -114
- package/src/utils/normalizePath.ts +0 -33
- package/src/utils/resolveSignal.ts +0 -24
- package/src/utils/types.test.js +0 -7
- package/src/utils/types.ts +0 -23
- package/src/version.ts +0 -2
- package/test-utils/mock-response.ts +0 -12
- package/tsconfig.json +0 -16
- package/turbo.json +0 -9
- package/typedoc.json +0 -13
- package/vercel.json +0 -9
- package/vitest.config.ts +0 -9
- package/vitest.setup.ts +0 -4
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { getVercelOidcToken } from "@vercel/oidc";
|
|
2
|
-
import { decodeBase64Url } from "./decode-base64-url";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
cachedGenerateCredentials,
|
|
6
|
-
shouldPromptForCredentials,
|
|
7
|
-
} from "./dev-credentials";
|
|
8
|
-
|
|
9
|
-
export interface Credentials {
|
|
10
|
-
/**
|
|
11
|
-
* Authentication token for the Vercel API. It could be an OIDC token
|
|
12
|
-
* or a personal access token.
|
|
13
|
-
*/
|
|
14
|
-
token: string;
|
|
15
|
-
/**
|
|
16
|
-
* The ID of the project to associate Sandbox operations.
|
|
17
|
-
*/
|
|
18
|
-
projectId: string;
|
|
19
|
-
/**
|
|
20
|
-
* The ID of the team to associate Sandbox operations.
|
|
21
|
-
*/
|
|
22
|
-
teamId: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Error thrown when OIDC context is not available in local development,
|
|
27
|
-
* therefore we should guide how to ensure it is set up by linking a project
|
|
28
|
-
*/
|
|
29
|
-
export class LocalOidcContextError extends Error {
|
|
30
|
-
name = "LocalOidcContextError";
|
|
31
|
-
constructor(cause: unknown) {
|
|
32
|
-
const message = [
|
|
33
|
-
"Could not get credentials from OIDC context.",
|
|
34
|
-
"Please link your Vercel project using `npx vercel link`.",
|
|
35
|
-
"Then, pull an initial OIDC token with `npx vercel env pull`",
|
|
36
|
-
"and retry.",
|
|
37
|
-
"╰▶ Make sure you are loading `.env.local` correctly, or passing $VERCEL_OIDC_TOKEN directly."
|
|
38
|
-
].join("\n");
|
|
39
|
-
super(message, { cause });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Error thrown when OIDC context is not available in Vercel environment,
|
|
45
|
-
* therefore we should guide how to set it up.
|
|
46
|
-
*/
|
|
47
|
-
export class VercelOidcContextError extends Error {
|
|
48
|
-
name = "VercelOidcContextError";
|
|
49
|
-
constructor(cause: unknown) {
|
|
50
|
-
const message = [
|
|
51
|
-
"Could not get credentials from OIDC context.",
|
|
52
|
-
"Please make sure OIDC is set up for your project",
|
|
53
|
-
"╰▶ Docs: https://vercel.com/docs/oidc",
|
|
54
|
-
].join("\n");
|
|
55
|
-
super(message, { cause });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function getVercelToken(opts: {
|
|
60
|
-
teamId?: string;
|
|
61
|
-
projectId?: string;
|
|
62
|
-
}): Promise<Credentials> {
|
|
63
|
-
try {
|
|
64
|
-
return getCredentialsFromOIDCToken(await getVercelOidcToken());
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (!shouldPromptForCredentials()) {
|
|
67
|
-
if (process.env.VERCEL_URL) {
|
|
68
|
-
throw new VercelOidcContextError(error);
|
|
69
|
-
} else {
|
|
70
|
-
throw new LocalOidcContextError(error);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return await cachedGenerateCredentials(opts);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Allow to get credentials to access the Vercel API. Credentials can be
|
|
79
|
-
* provided in two different ways:
|
|
80
|
-
*
|
|
81
|
-
* 1. By passing an object with the `teamId`, `token`, and `projectId` properties.
|
|
82
|
-
* 2. By using an environment variable VERCEL_OIDC_TOKEN.
|
|
83
|
-
*
|
|
84
|
-
* If both methods are used, the object properties take precedence over the
|
|
85
|
-
* environment variable. If neither method is used, an error is thrown.
|
|
86
|
-
*/
|
|
87
|
-
export async function getCredentials(params?: unknown): Promise<Credentials> {
|
|
88
|
-
const credentials = getCredentialsFromParams(params ?? {});
|
|
89
|
-
if (credentials) {
|
|
90
|
-
return credentials;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const oidcToken = await getVercelToken({
|
|
94
|
-
teamId:
|
|
95
|
-
params &&
|
|
96
|
-
typeof params === "object" &&
|
|
97
|
-
"teamId" in params &&
|
|
98
|
-
typeof params.teamId === "string"
|
|
99
|
-
? params.teamId
|
|
100
|
-
: undefined,
|
|
101
|
-
projectId:
|
|
102
|
-
params &&
|
|
103
|
-
typeof params === "object" &&
|
|
104
|
-
"projectId" in params &&
|
|
105
|
-
typeof params.projectId === "string"
|
|
106
|
-
? params.projectId
|
|
107
|
-
: undefined,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return oidcToken;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Attempt to extract credentials from the provided parameters. Either all
|
|
115
|
-
* required fields (`token`, `teamId`, and `projectId`) must be present
|
|
116
|
-
* or none of them, otherwise an error is thrown.
|
|
117
|
-
*/
|
|
118
|
-
function getCredentialsFromParams(params: unknown): Credentials | null {
|
|
119
|
-
// Type guard: params must be an object
|
|
120
|
-
if (!params || typeof params !== "object") {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const missing = [
|
|
125
|
-
"token" in params && typeof params.token === "string" ? null : "token",
|
|
126
|
-
"teamId" in params && typeof params.teamId === "string" ? null : "teamId",
|
|
127
|
-
"projectId" in params && typeof params.projectId === "string"
|
|
128
|
-
? null
|
|
129
|
-
: "projectId",
|
|
130
|
-
].filter((value) => value !== null);
|
|
131
|
-
|
|
132
|
-
if (missing.length === 0) {
|
|
133
|
-
return {
|
|
134
|
-
token: (params as any).token,
|
|
135
|
-
projectId: (params as any).projectId,
|
|
136
|
-
teamId: (params as any).teamId,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (missing.length < 3) {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`Missing credentials parameters to access the Vercel API: ${missing
|
|
143
|
-
.filter((value) => value !== null)
|
|
144
|
-
.join(", ")}`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Schema to validate the payload of the Vercel OIDC token where we expect
|
|
153
|
-
* to find the `teamId` and `projectId`.
|
|
154
|
-
*/
|
|
155
|
-
export const schema = z.object({
|
|
156
|
-
exp: z.number().optional().describe("Expiry timestamp (seconds since epoch)"),
|
|
157
|
-
iat: z.number().optional().describe("Issued at timestamp"),
|
|
158
|
-
owner_id: z.string(),
|
|
159
|
-
project_id: z.string(),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Extracts credentials from a Vercel OIDC token. The token is expected to be
|
|
164
|
-
* a JWT with a payload that contains `project_id` and `owner_id`.
|
|
165
|
-
*
|
|
166
|
-
* @param token - The Vercel OIDC token.
|
|
167
|
-
* @returns An object containing the token, projectId, and teamId.
|
|
168
|
-
* @throws If the token is invalid or does not contain the required fields.
|
|
169
|
-
*/
|
|
170
|
-
function getCredentialsFromOIDCToken(token: string): Credentials {
|
|
171
|
-
try {
|
|
172
|
-
const payload = schema.parse(decodeBase64Url(token.split(".")[1]));
|
|
173
|
-
return {
|
|
174
|
-
token,
|
|
175
|
-
projectId: payload.project_id,
|
|
176
|
-
teamId: payload.owner_id,
|
|
177
|
-
};
|
|
178
|
-
} catch (error) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Invalid Vercel OIDC token: ${error instanceof Error ? error.message : String(error)}`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { assert, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
-
import { JwtExpiry } from "./jwt-expiry";
|
|
3
|
-
import type { getVercelOidcToken } from "@vercel/oidc";
|
|
4
|
-
|
|
5
|
-
const { getVercelOidcTokenMock } = vi.hoisted(() => {
|
|
6
|
-
return {
|
|
7
|
-
getVercelOidcTokenMock: vi.fn<typeof getVercelOidcToken>(),
|
|
8
|
-
};
|
|
9
|
-
});
|
|
10
|
-
vi.mock("@vercel/oidc", () => ({
|
|
11
|
-
getVercelOidcToken: getVercelOidcTokenMock,
|
|
12
|
-
}));
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
getVercelOidcTokenMock.mockReset();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe("JwtExpiry", () => {
|
|
18
|
-
test("refreshes a token", async () => {
|
|
19
|
-
const token = createMockJWT({
|
|
20
|
-
owner_id: "team1",
|
|
21
|
-
project_id: "proj1",
|
|
22
|
-
});
|
|
23
|
-
getVercelOidcTokenMock.mockImplementationOnce(async () => "hello world");
|
|
24
|
-
const expiry = await JwtExpiry.fromToken(token)?.refresh();
|
|
25
|
-
expect(expiry).toBeInstanceOf(JwtExpiry);
|
|
26
|
-
expect(expiry?.token).toEqual("hello world");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("isValid returns true for tokens without expiry", () => {
|
|
30
|
-
// Mock token without exp field (like OIDC tokens without exp)
|
|
31
|
-
const tokenWithoutExp = createMockJWT({
|
|
32
|
-
owner_id: "team1",
|
|
33
|
-
project_id: "proj1",
|
|
34
|
-
});
|
|
35
|
-
const expiry = JwtExpiry.fromToken(tokenWithoutExp);
|
|
36
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
37
|
-
expect(expiry.isValid()).toBe(false); // No exp field means malformed JWT
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("isValid returns true for unexpired tokens", () => {
|
|
41
|
-
const futureTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
|
|
42
|
-
const tokenValid = createMockJWT({
|
|
43
|
-
owner_id: "team1",
|
|
44
|
-
project_id: "proj1",
|
|
45
|
-
exp: futureTime,
|
|
46
|
-
});
|
|
47
|
-
const expiry = JwtExpiry.fromToken(tokenValid);
|
|
48
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
49
|
-
expect(expiry.isValid()).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("isValid returns false for expired tokens", () => {
|
|
53
|
-
const pastTime = Math.floor(Date.now() / 1000) - 3600; // 1 hour ago
|
|
54
|
-
const tokenExpired = createMockJWT({
|
|
55
|
-
owner_id: "team1",
|
|
56
|
-
project_id: "proj1",
|
|
57
|
-
exp: pastTime,
|
|
58
|
-
});
|
|
59
|
-
const expiry = JwtExpiry.fromToken(tokenExpired);
|
|
60
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
61
|
-
expect(expiry.isValid()).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("isValid returns false for tokens expiring within buffer time", () => {
|
|
65
|
-
const soonTime = Math.floor(Date.now() / 1000) + 120; // 2 minutes from now
|
|
66
|
-
const tokenExpiringSoon = createMockJWT({
|
|
67
|
-
owner_id: "team1",
|
|
68
|
-
project_id: "proj1",
|
|
69
|
-
exp: soonTime,
|
|
70
|
-
});
|
|
71
|
-
const expiry = JwtExpiry.fromToken(tokenExpiringSoon);
|
|
72
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
73
|
-
expect(expiry.isValid(5)).toBe(false); // 5 minute buffer
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("isValid returns false for malformed JWT tokens", () => {
|
|
77
|
-
const expiry = JwtExpiry.fromToken("header.invalid-payload.signature");
|
|
78
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
79
|
-
expect(expiry.isValid()).toBe(false);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("getExpiryDate returns correct expiry date", () => {
|
|
83
|
-
const expTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
|
|
84
|
-
const token = createMockJWT({
|
|
85
|
-
owner_id: "team1",
|
|
86
|
-
project_id: "proj1",
|
|
87
|
-
exp: expTime,
|
|
88
|
-
});
|
|
89
|
-
const expiry = JwtExpiry.fromToken(token);
|
|
90
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
91
|
-
expect(expiry.getExpiryDate()).toEqual(new Date(expTime * 1000));
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("getExpiryDate returns null for tokens without expiry", () => {
|
|
95
|
-
const token = createMockJWT({ owner_id: "team1", project_id: "proj1" });
|
|
96
|
-
const expiry = JwtExpiry.fromToken(token);
|
|
97
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
98
|
-
expect(expiry.getExpiryDate()).toBeNull();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("getExpiryDate returns null for malformed tokens", () => {
|
|
102
|
-
const token = "hello.world.hey";
|
|
103
|
-
const expiry = JwtExpiry.fromToken(token);
|
|
104
|
-
assert(expiry, "Expiry should not be null for valid JWT");
|
|
105
|
-
expect(expiry.getExpiryDate()).toBeNull();
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("returns null for non-JWT style tokens", () => {
|
|
109
|
-
expect(JwtExpiry.fromToken("personal-access-token")).toBeNull();
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Helper function to create mock JWT tokens for testing
|
|
114
|
-
function createMockJWT(payload: any): string {
|
|
115
|
-
const header = { typ: "JWT", alg: "HS256" };
|
|
116
|
-
const encodedHeader = Buffer.from(JSON.stringify(header)).toString(
|
|
117
|
-
"base64url",
|
|
118
|
-
);
|
|
119
|
-
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString(
|
|
120
|
-
"base64url",
|
|
121
|
-
);
|
|
122
|
-
const signature = "mock-signature";
|
|
123
|
-
|
|
124
|
-
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
125
|
-
}
|
package/src/utils/jwt-expiry.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { decodeBase64Url } from "./decode-base64-url";
|
|
3
|
-
import { schema } from "./get-credentials";
|
|
4
|
-
import { getVercelOidcToken } from "@vercel/oidc";
|
|
5
|
-
import ms from "ms";
|
|
6
|
-
|
|
7
|
-
/** Time buffer before token expiry to consider it invalid (in milliseconds) */
|
|
8
|
-
const BUFFER_MS = ms("5m");
|
|
9
|
-
|
|
10
|
-
export class OidcRefreshError extends Error {
|
|
11
|
-
name = "OidcRefreshError";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Expiry implementation for JWT tokens (OIDC tokens).
|
|
16
|
-
* Parses the JWT once and provides fast expiry validation.
|
|
17
|
-
*/
|
|
18
|
-
export class JwtExpiry {
|
|
19
|
-
private expiryTime: number | null; // Unix timestamp in seconds
|
|
20
|
-
readonly payload?: Readonly<z.infer<typeof schema>>;
|
|
21
|
-
|
|
22
|
-
static fromToken(token: string): JwtExpiry | null {
|
|
23
|
-
if (!isJwtFormat(token)) {
|
|
24
|
-
return null;
|
|
25
|
-
} else {
|
|
26
|
-
return new JwtExpiry(token);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Creates a new JWT expiry checker.
|
|
32
|
-
*
|
|
33
|
-
* @param token - The JWT token to parse
|
|
34
|
-
*/
|
|
35
|
-
constructor(readonly token: string) {
|
|
36
|
-
try {
|
|
37
|
-
const tokenContents = token.split(".")[1];
|
|
38
|
-
this.payload = schema.parse(decodeBase64Url(tokenContents));
|
|
39
|
-
this.expiryTime = this.payload.exp || null;
|
|
40
|
-
} catch {
|
|
41
|
-
// Malformed token - treat as expired to trigger refresh
|
|
42
|
-
this.expiryTime = 0;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Checks if the JWT token is valid (not expired).
|
|
48
|
-
* @returns true if token is valid, false if expired or expiring soon
|
|
49
|
-
*/
|
|
50
|
-
isValid(): boolean {
|
|
51
|
-
if (this.expiryTime === null) {
|
|
52
|
-
return false; // No expiry means malformed JWT
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const now = Math.floor(Date.now() / 1000);
|
|
56
|
-
const buffer = BUFFER_MS / 1000;
|
|
57
|
-
return now + buffer < this.expiryTime;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Gets the expiry date of the JWT token.
|
|
62
|
-
*
|
|
63
|
-
* @returns Date object representing when the token expires, or null if no expiry
|
|
64
|
-
*/
|
|
65
|
-
getExpiryDate(): Date | null {
|
|
66
|
-
return this.expiryTime ? new Date(this.expiryTime * 1000) : null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Refreshes the JWT token by fetching a new OIDC token.
|
|
71
|
-
*
|
|
72
|
-
* @returns Promise resolving to a new JwtExpiry instance with fresh token
|
|
73
|
-
*/
|
|
74
|
-
async refresh(): Promise<JwtExpiry> {
|
|
75
|
-
try {
|
|
76
|
-
const freshToken = await getVercelOidcToken();
|
|
77
|
-
return new JwtExpiry(freshToken);
|
|
78
|
-
} catch (cause) {
|
|
79
|
-
throw new OidcRefreshError("Failed to refresh OIDC token", {
|
|
80
|
-
cause,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Refreshes the JWT token if it's expired or expiring soon.
|
|
87
|
-
*/
|
|
88
|
-
async tryRefresh(): Promise<JwtExpiry | null> {
|
|
89
|
-
if (this.isValid()) {
|
|
90
|
-
return null; // Still valid, no need to refresh
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return this.refresh();
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Checks if a token follows JWT format (has 3 parts separated by dots).
|
|
99
|
-
*
|
|
100
|
-
* @param token - The token to check
|
|
101
|
-
* @returns true if token appears to be a JWT, false otherwise
|
|
102
|
-
*/
|
|
103
|
-
function isJwtFormat(token: string): boolean {
|
|
104
|
-
return token.split(".").length === 3;
|
|
105
|
-
}
|
package/src/utils/log.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import pico from "picocolors";
|
|
2
|
-
const colors = {
|
|
3
|
-
warn: pico.yellow,
|
|
4
|
-
error: pico.red,
|
|
5
|
-
success: pico.green,
|
|
6
|
-
info: pico.blue,
|
|
7
|
-
};
|
|
8
|
-
const logPrefix = pico.dim("[vercel/sandbox]");
|
|
9
|
-
export function write(
|
|
10
|
-
level: "warn" | "error" | "info" | "success",
|
|
11
|
-
text: string | string[],
|
|
12
|
-
) {
|
|
13
|
-
text = Array.isArray(text) ? text.join("\n") : text;
|
|
14
|
-
const prefixed = text.replace(/^/gm, `${logPrefix} `);
|
|
15
|
-
console.error(colors[level](prefixed));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function code(text: string) {
|
|
19
|
-
return pico.italic(pico.dim("`") + text + pico.dim("`"));
|
|
20
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { normalizePath } from "./normalizePath";
|
|
3
|
-
|
|
4
|
-
describe("normalizePath", () => {
|
|
5
|
-
it("handles base cases", () => {
|
|
6
|
-
expect(
|
|
7
|
-
normalizePath({
|
|
8
|
-
filePath: "foo.txt",
|
|
9
|
-
cwd: "/vercel/sandbox",
|
|
10
|
-
extractDir: "/",
|
|
11
|
-
}),
|
|
12
|
-
).toBe("vercel/sandbox/foo.txt");
|
|
13
|
-
expect(
|
|
14
|
-
normalizePath({
|
|
15
|
-
filePath: "foo/bar/baz.txt",
|
|
16
|
-
cwd: "/vercel/sandbox",
|
|
17
|
-
extractDir: "/",
|
|
18
|
-
}),
|
|
19
|
-
).toBe("vercel/sandbox/foo/bar/baz.txt");
|
|
20
|
-
expect(
|
|
21
|
-
normalizePath({ filePath: "bar.txt", cwd: "/", extractDir: "/" }),
|
|
22
|
-
).toBe("bar.txt");
|
|
23
|
-
expect(
|
|
24
|
-
normalizePath({
|
|
25
|
-
filePath: "/some/other/dir/foo.txt",
|
|
26
|
-
cwd: "/bar",
|
|
27
|
-
extractDir: "/",
|
|
28
|
-
}),
|
|
29
|
-
).toBe("some/other/dir/foo.txt");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("handles base cases (extract dir)", () => {
|
|
33
|
-
expect(
|
|
34
|
-
normalizePath({
|
|
35
|
-
filePath: "foo.txt",
|
|
36
|
-
cwd: "/vercel/sandbox",
|
|
37
|
-
extractDir: "/vercel",
|
|
38
|
-
}),
|
|
39
|
-
).toBe("sandbox/foo.txt");
|
|
40
|
-
expect(
|
|
41
|
-
normalizePath({
|
|
42
|
-
filePath: "foo/bar/baz.txt",
|
|
43
|
-
cwd: "/vercel/sandbox",
|
|
44
|
-
extractDir: "/vercel",
|
|
45
|
-
}),
|
|
46
|
-
).toBe("sandbox/foo/bar/baz.txt");
|
|
47
|
-
|
|
48
|
-
// TODO: Should this be allowed?
|
|
49
|
-
expect(
|
|
50
|
-
normalizePath({ filePath: "bar.txt", cwd: "/", extractDir: "/vercel" }),
|
|
51
|
-
).toBe("../bar.txt");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("handles normalization", () => {
|
|
55
|
-
expect(
|
|
56
|
-
normalizePath({
|
|
57
|
-
filePath: "/resolves/../this/stuff/foo.txt",
|
|
58
|
-
cwd: "/bar",
|
|
59
|
-
extractDir: "/",
|
|
60
|
-
}),
|
|
61
|
-
).toBe("this/stuff/foo.txt");
|
|
62
|
-
expect(
|
|
63
|
-
normalizePath({
|
|
64
|
-
filePath: "/handles//extra-slashes",
|
|
65
|
-
cwd: "/",
|
|
66
|
-
extractDir: "/",
|
|
67
|
-
}),
|
|
68
|
-
).toBe("handles/extra-slashes");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("resolves relative paths", () => {
|
|
72
|
-
expect(
|
|
73
|
-
normalizePath({
|
|
74
|
-
filePath: "/../../../foo.txt",
|
|
75
|
-
cwd: "/",
|
|
76
|
-
extractDir: "/",
|
|
77
|
-
}),
|
|
78
|
-
).toBe("foo.txt");
|
|
79
|
-
expect(
|
|
80
|
-
normalizePath({
|
|
81
|
-
filePath: "../../../../foo.txt",
|
|
82
|
-
cwd: "/vercel/sandbox",
|
|
83
|
-
extractDir: "/",
|
|
84
|
-
}),
|
|
85
|
-
).toBe("foo.txt");
|
|
86
|
-
expect(
|
|
87
|
-
normalizePath({
|
|
88
|
-
filePath: "../foo.txt",
|
|
89
|
-
cwd: "/vercel/sandbox",
|
|
90
|
-
extractDir: "/",
|
|
91
|
-
}),
|
|
92
|
-
).toBe("vercel/foo.txt");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("validates the cwd", () => {
|
|
96
|
-
expect(() => {
|
|
97
|
-
normalizePath({
|
|
98
|
-
filePath: "doesn't matter",
|
|
99
|
-
cwd: "relative/root",
|
|
100
|
-
extractDir: "/",
|
|
101
|
-
});
|
|
102
|
-
}).toThrow("cwd dir must be absolute");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("validates the cwd", () => {
|
|
106
|
-
expect(() => {
|
|
107
|
-
normalizePath({
|
|
108
|
-
filePath: "doesn't matter",
|
|
109
|
-
cwd: "/",
|
|
110
|
-
extractDir: "some/relative/path",
|
|
111
|
-
});
|
|
112
|
-
}).toThrow("extractDir must be absolute");
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Normalize a path and make it relative to `params.extractDir` for inclusion
|
|
5
|
-
* in our tar archives.
|
|
6
|
-
*
|
|
7
|
-
* Relative paths are first resolved to `params.cwd`.
|
|
8
|
-
* Absolute paths are normalized and resolved relative to `params.extractDir`.
|
|
9
|
-
*
|
|
10
|
-
* In addition, paths are normalized so consecutive slashes are removed and
|
|
11
|
-
* stuff like `../..` is resolved appropriately.
|
|
12
|
-
*
|
|
13
|
-
* This function always returns a path relative to `params.extractDir`.
|
|
14
|
-
*/
|
|
15
|
-
export function normalizePath(params: {
|
|
16
|
-
filePath: string;
|
|
17
|
-
cwd: string;
|
|
18
|
-
extractDir: string;
|
|
19
|
-
}) {
|
|
20
|
-
if (!path.posix.isAbsolute(params.cwd)) {
|
|
21
|
-
throw new Error("cwd dir must be absolute");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (!path.posix.isAbsolute(params.extractDir)) {
|
|
25
|
-
throw new Error("extractDir must be absolute");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const basePath = path.posix.isAbsolute(params.filePath)
|
|
29
|
-
? path.posix.normalize(params.filePath)
|
|
30
|
-
: path.posix.join(params.cwd, params.filePath);
|
|
31
|
-
|
|
32
|
-
return path.posix.relative(params.extractDir, basePath);
|
|
33
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const linuxSignalMapping = {
|
|
2
|
-
SIGHUP: 1,
|
|
3
|
-
SIGINT: 2,
|
|
4
|
-
SIGQUIT: 3,
|
|
5
|
-
SIGKILL: 9,
|
|
6
|
-
SIGTERM: 15,
|
|
7
|
-
SIGCONT: 18,
|
|
8
|
-
SIGSTOP: 19,
|
|
9
|
-
} as const;
|
|
10
|
-
|
|
11
|
-
type CommonLinuxSignals = keyof typeof linuxSignalMapping;
|
|
12
|
-
|
|
13
|
-
export type Signal = CommonLinuxSignals | number;
|
|
14
|
-
|
|
15
|
-
export function resolveSignal(signal: Signal): number {
|
|
16
|
-
if (typeof signal === "number") {
|
|
17
|
-
return signal;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (signal in linuxSignalMapping) {
|
|
21
|
-
return linuxSignalMapping[signal];
|
|
22
|
-
}
|
|
23
|
-
throw new Error(`Unknown signal name: ${String(signal)}`);
|
|
24
|
-
}
|
package/src/utils/types.test.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { expect, it } from "vitest";
|
|
2
|
-
import { getPrivateParams } from "./types";
|
|
3
|
-
|
|
4
|
-
it("getPrivateParams filters unknown params", async () => {
|
|
5
|
-
const result = getPrivateParams({ foo: 123, __someParam: "abc" });
|
|
6
|
-
expect(result).toEqual({ __someParam: "abc" });
|
|
7
|
-
});
|
package/src/utils/types.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility type that extends a type to accept private parameters.
|
|
3
|
-
*
|
|
4
|
-
* The private parameters can then be extracted out of the object using
|
|
5
|
-
* `getPrivateParams`.
|
|
6
|
-
*/
|
|
7
|
-
export type WithPrivate<T> = T & {
|
|
8
|
-
[K in `__${string}`]?: unknown;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Extract private parameters out of an object.
|
|
13
|
-
*/
|
|
14
|
-
export const getPrivateParams = (params?: object) => {
|
|
15
|
-
const privateEntries = Object.entries(params ?? {}).filter(([k]) =>
|
|
16
|
-
k.startsWith("__"),
|
|
17
|
-
);
|
|
18
|
-
return Object.fromEntries(privateEntries) as {
|
|
19
|
-
[K in keyof typeof params as K extends `__${string}`
|
|
20
|
-
? K
|
|
21
|
-
: never]: (typeof params)[K];
|
|
22
|
-
};
|
|
23
|
-
};
|
package/src/version.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export function createNdjsonStream(
|
|
2
|
-
lines: object[],
|
|
3
|
-
): ReadableStream<Uint8Array> {
|
|
4
|
-
const encoder = new TextEncoder();
|
|
5
|
-
const ndjson = lines.map((line) => JSON.stringify(line)).join("\n") + "\n";
|
|
6
|
-
return new ReadableStream({
|
|
7
|
-
start(controller) {
|
|
8
|
-
controller.enqueue(encoder.encode(ndjson));
|
|
9
|
-
controller.close();
|
|
10
|
-
},
|
|
11
|
-
});
|
|
12
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"outDir": "./dist",
|
|
4
|
-
"esModuleInterop": true,
|
|
5
|
-
"skipLibCheck": true,
|
|
6
|
-
"lib": ["ESNext"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"target": "ES2020",
|
|
9
|
-
"declaration": true,
|
|
10
|
-
"sourceMap": true,
|
|
11
|
-
"module": "commonjs",
|
|
12
|
-
"resolveJsonModule": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*.ts"],
|
|
15
|
-
"exclude": ["src/**/*.test.ts", "vitest.config.ts", "vitest.setup.ts"]
|
|
16
|
-
}
|