@vercel/sandbox 1.1.4 → 1.1.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +14 -8
- package/.turbo/turbo-typecheck.log +1 -1
- package/CHANGELOG.md +6 -0
- package/__mocks__/picocolors.ts +13 -0
- package/dist/api-client/with-retry.js +1 -1
- package/dist/api-client/with-retry.js.map +1 -1
- package/dist/auth/api.d.ts +6 -0
- package/dist/auth/api.js +28 -0
- package/dist/auth/api.js.map +1 -0
- package/dist/auth/error.d.ts +11 -0
- package/dist/auth/error.js +12 -0
- package/dist/auth/error.js.map +1 -0
- package/dist/auth/file.d.ts +22 -0
- package/dist/auth/file.js +66 -0
- package/dist/auth/file.js.map +1 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +27 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/linked-project.d.ts +10 -0
- package/dist/auth/linked-project.js +69 -0
- package/dist/auth/linked-project.js.map +1 -0
- package/dist/auth/oauth.d.ts +131 -0
- package/dist/auth/oauth.js +269 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/poll-for-token.d.ts +20 -0
- package/dist/auth/poll-for-token.js +66 -0
- package/dist/auth/poll-for-token.js.map +1 -0
- package/dist/auth/project.d.ts +40 -0
- package/dist/auth/project.js +80 -0
- package/dist/auth/project.js.map +1 -0
- package/dist/auth/zod.d.ts +5 -0
- package/dist/auth/zod.js +20 -0
- package/dist/auth/zod.js.map +1 -0
- package/dist/sandbox.js +1 -1
- package/dist/sandbox.js.map +1 -1
- package/dist/utils/dev-credentials.d.ts +37 -0
- package/dist/utils/dev-credentials.js +191 -0
- package/dist/utils/dev-credentials.js.map +1 -0
- package/dist/utils/get-credentials.d.ts +16 -0
- package/dist/utils/get-credentials.js +66 -7
- package/dist/utils/get-credentials.js.map +1 -1
- package/dist/utils/log.d.ts +2 -0
- package/dist/utils/log.js +24 -0
- package/dist/utils/log.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
- package/src/api-client/api-client.test.ts +128 -0
- package/src/api-client/with-retry.ts +1 -1
- package/src/auth/api.ts +31 -0
- package/src/auth/error.ts +8 -0
- package/src/auth/file.ts +69 -0
- package/src/auth/index.ts +9 -0
- package/src/auth/infer-scope.test.ts +178 -0
- package/src/auth/linked-project.test.ts +86 -0
- package/src/auth/linked-project.ts +40 -0
- package/src/auth/oauth.ts +333 -0
- package/src/auth/poll-for-token.ts +89 -0
- package/src/auth/project.ts +92 -0
- package/src/auth/zod.ts +16 -0
- package/src/sandbox.ts +1 -1
- package/src/utils/dev-credentials.test.ts +217 -0
- package/src/utils/dev-credentials.ts +189 -0
- package/src/utils/get-credentials.test.ts +20 -0
- package/src/utils/get-credentials.ts +72 -8
- package/src/utils/log.ts +20 -0
- package/src/version.ts +1 -1
- package/test-utils/mock-response.ts +12 -0
- package/vitest.config.ts +1 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.cachedGenerateCredentials = void 0;
|
|
40
|
+
exports.shouldPromptForCredentials = shouldPromptForCredentials;
|
|
41
|
+
exports.generateCredentials = generateCredentials;
|
|
42
|
+
exports.signInAndGetToken = signInAndGetToken;
|
|
43
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
44
|
+
const ms_1 = __importDefault(require("ms"));
|
|
45
|
+
const Log = __importStar(require("./log"));
|
|
46
|
+
async function importAuth() {
|
|
47
|
+
const auth = await Promise.resolve().then(() => __importStar(require("../auth/index")));
|
|
48
|
+
return auth;
|
|
49
|
+
}
|
|
50
|
+
function shouldPromptForCredentials() {
|
|
51
|
+
return (process.env.NODE_ENV !== "production" &&
|
|
52
|
+
!["1", "true"].includes(process.env.CI || "") &&
|
|
53
|
+
process.stdout.isTTY &&
|
|
54
|
+
process.stdin.isTTY);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns cached credentials for the given team/project combination.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* The cache is keyed by `teamId` and `projectId`. A new credential generation
|
|
61
|
+
* is triggered only when these values change or when a previous attempt failed.
|
|
62
|
+
*
|
|
63
|
+
* **Important:** Successfully resolved credentials are cached indefinitely and
|
|
64
|
+
* will not be refreshed even if the token expires. Cache invalidation only occurs
|
|
65
|
+
* on rejection (error). This is intentional for development use cases where
|
|
66
|
+
* short-lived sessions don't require proactive token refresh.
|
|
67
|
+
*/
|
|
68
|
+
exports.cachedGenerateCredentials = (() => {
|
|
69
|
+
let cache = null;
|
|
70
|
+
return async (opts) => {
|
|
71
|
+
if (!cache ||
|
|
72
|
+
cache[0].teamId !== opts.teamId ||
|
|
73
|
+
cache[0].projectId !== opts.projectId) {
|
|
74
|
+
const promise = generateCredentials(opts).catch((err) => {
|
|
75
|
+
cache = null;
|
|
76
|
+
throw err;
|
|
77
|
+
});
|
|
78
|
+
cache = [opts, promise];
|
|
79
|
+
}
|
|
80
|
+
const v = await cache[1];
|
|
81
|
+
Log.write("warn", `using inferred credentials team=${v.teamId} project=${v.projectId}`);
|
|
82
|
+
return v;
|
|
83
|
+
};
|
|
84
|
+
})();
|
|
85
|
+
/**
|
|
86
|
+
* Generates credentials by authenticating and inferring scope.
|
|
87
|
+
*
|
|
88
|
+
* @internal This is exported for testing purposes. Consider using
|
|
89
|
+
* {@link cachedGenerateCredentials} instead, which caches the result
|
|
90
|
+
* to avoid redundant authentication flows.
|
|
91
|
+
*/
|
|
92
|
+
async function generateCredentials(opts) {
|
|
93
|
+
const { OAuth, pollForToken, getAuth, updateAuthConfig, inferScope } = await importAuth();
|
|
94
|
+
let auth = getAuth();
|
|
95
|
+
if (!auth?.token) {
|
|
96
|
+
const timeout = process.env.VERCEL_URL
|
|
97
|
+
? /* when deployed to vercel we don't want to have a long timeout */ "1 minute"
|
|
98
|
+
: "5 minutes";
|
|
99
|
+
auth = await signInAndGetToken({ OAuth, pollForToken, getAuth }, timeout);
|
|
100
|
+
}
|
|
101
|
+
if (auth?.refreshToken &&
|
|
102
|
+
auth.expiresAt &&
|
|
103
|
+
auth.expiresAt.getTime() <= Date.now()) {
|
|
104
|
+
const oauth = await OAuth();
|
|
105
|
+
const newToken = await oauth.refreshToken(auth.refreshToken);
|
|
106
|
+
auth = {
|
|
107
|
+
expiresAt: new Date(Date.now() + newToken.expires_in * 1000),
|
|
108
|
+
token: newToken.access_token,
|
|
109
|
+
refreshToken: newToken.refresh_token || auth.refreshToken,
|
|
110
|
+
};
|
|
111
|
+
updateAuthConfig(auth);
|
|
112
|
+
}
|
|
113
|
+
if (!auth?.token) {
|
|
114
|
+
throw new Error("Failed to retrieve authentication token.");
|
|
115
|
+
}
|
|
116
|
+
if (opts.teamId && opts.projectId) {
|
|
117
|
+
return {
|
|
118
|
+
token: auth.token,
|
|
119
|
+
teamId: opts.teamId,
|
|
120
|
+
projectId: opts.projectId,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const scope = await inferScope({ teamId: opts.teamId, token: auth.token });
|
|
124
|
+
if (scope.created) {
|
|
125
|
+
Log.write("info", `Created default project "${scope.projectId}" in team "${scope.teamId}".`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
token: auth.token,
|
|
129
|
+
teamId: opts.teamId || scope.teamId,
|
|
130
|
+
projectId: opts.projectId || scope.projectId,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
async function signInAndGetToken(auth, timeout) {
|
|
134
|
+
Log.write("warn", [
|
|
135
|
+
`No VERCEL_OIDC_TOKEN environment variable found, initiating device authorization flow...`,
|
|
136
|
+
`│ ${picocolors_1.default.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 ${picocolors_1.default.italic(request.verification_uri)} and type ${picocolors_1.default.bold(request.user_code)}`,
|
|
144
|
+
` Press ${picocolors_1.default.bold("<return>")} to open in your browser`,
|
|
145
|
+
]);
|
|
146
|
+
let error;
|
|
147
|
+
const generator = auth.pollForToken({ request, oauth });
|
|
148
|
+
let done = false;
|
|
149
|
+
let spawnedTimeout = setTimeout(() => {
|
|
150
|
+
if (done)
|
|
151
|
+
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
|
+
`╰▶ ${picocolors_1.default.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
|
+
}, (0, ms_1.default)(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(`Unknown event type: ${JSON.stringify(event)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
done = true;
|
|
181
|
+
clearTimeout(spawnedTimeout);
|
|
182
|
+
}
|
|
183
|
+
if (error) {
|
|
184
|
+
Log.write("error", `${picocolors_1.default.bold("error:")} Authentication failed: ${error.message}`);
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
Log.write("success", `${picocolors_1.default.bold("done!")} Authenticated successfully!`);
|
|
188
|
+
const stored = auth.getAuth();
|
|
189
|
+
return stored;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=dev-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-credentials.js","sourceRoot":"","sources":["../../src/utils/dev-credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,22 @@ export interface Credentials {
|
|
|
14
14
|
*/
|
|
15
15
|
teamId: string;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Error thrown when OIDC context is not available in local development,
|
|
19
|
+
* therefore we should guide how to ensure it is set up by linking a project
|
|
20
|
+
*/
|
|
21
|
+
export declare class LocalOidcContextError extends Error {
|
|
22
|
+
name: string;
|
|
23
|
+
constructor(cause: unknown);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Error thrown when OIDC context is not available in Vercel environment,
|
|
27
|
+
* therefore we should guide how to set it up.
|
|
28
|
+
*/
|
|
29
|
+
export declare class VercelOidcContextError extends Error {
|
|
30
|
+
name: string;
|
|
31
|
+
constructor(cause: unknown);
|
|
32
|
+
}
|
|
17
33
|
/**
|
|
18
34
|
* Allow to get credentials to access the Vercel API. Credentials can be
|
|
19
35
|
* provided in two different ways:
|
|
@@ -1,10 +1,60 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.schema = void 0;
|
|
3
|
+
exports.schema = exports.VercelOidcContextError = exports.LocalOidcContextError = void 0;
|
|
4
4
|
exports.getCredentials = getCredentials;
|
|
5
5
|
const oidc_1 = require("@vercel/oidc");
|
|
6
6
|
const decode_base64_url_1 = require("./decode-base64-url");
|
|
7
7
|
const zod_1 = require("zod");
|
|
8
|
+
const dev_credentials_1 = require("./dev-credentials");
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when OIDC context is not available in local development,
|
|
11
|
+
* therefore we should guide how to ensure it is set up by linking a project
|
|
12
|
+
*/
|
|
13
|
+
class LocalOidcContextError extends Error {
|
|
14
|
+
constructor(cause) {
|
|
15
|
+
const message = [
|
|
16
|
+
"Could not get credentials from OIDC context.",
|
|
17
|
+
"Please link your Vercel project using `npx vercel link`.",
|
|
18
|
+
"Then, pull an initial OIDC token with `npx vercel env pull`",
|
|
19
|
+
"and retry.",
|
|
20
|
+
].join("\n");
|
|
21
|
+
super(message, { cause });
|
|
22
|
+
this.name = "LocalOidcContextError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.LocalOidcContextError = LocalOidcContextError;
|
|
26
|
+
/**
|
|
27
|
+
* Error thrown when OIDC context is not available in Vercel environment,
|
|
28
|
+
* therefore we should guide how to set it up.
|
|
29
|
+
*/
|
|
30
|
+
class VercelOidcContextError extends Error {
|
|
31
|
+
constructor(cause) {
|
|
32
|
+
const message = [
|
|
33
|
+
"Could not get credentials from OIDC context.",
|
|
34
|
+
"Please make sure OIDC is set up for your project",
|
|
35
|
+
"Read more at https://vercel.com/docs/oidc",
|
|
36
|
+
].join("\n");
|
|
37
|
+
super(message, { cause });
|
|
38
|
+
this.name = "VercelOidcContextError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.VercelOidcContextError = VercelOidcContextError;
|
|
42
|
+
async function getVercelToken(opts) {
|
|
43
|
+
try {
|
|
44
|
+
return getCredentialsFromOIDCToken(await (0, oidc_1.getVercelOidcToken)());
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!(0, dev_credentials_1.shouldPromptForCredentials)()) {
|
|
48
|
+
if (process.env.VERCEL_URL) {
|
|
49
|
+
throw new VercelOidcContextError(error);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw new LocalOidcContextError(error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return await (0, dev_credentials_1.cachedGenerateCredentials)(opts);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
8
58
|
/**
|
|
9
59
|
* Allow to get credentials to access the Vercel API. Credentials can be
|
|
10
60
|
* provided in two different ways:
|
|
@@ -20,12 +70,21 @@ async function getCredentials(params) {
|
|
|
20
70
|
if (credentials) {
|
|
21
71
|
return credentials;
|
|
22
72
|
}
|
|
23
|
-
const oidcToken = await (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
73
|
+
const oidcToken = await getVercelToken({
|
|
74
|
+
teamId: params &&
|
|
75
|
+
typeof params === "object" &&
|
|
76
|
+
"teamId" in params &&
|
|
77
|
+
typeof params.teamId === "string"
|
|
78
|
+
? params.teamId
|
|
79
|
+
: undefined,
|
|
80
|
+
projectId: params &&
|
|
81
|
+
typeof params === "object" &&
|
|
82
|
+
"projectId" in params &&
|
|
83
|
+
typeof params.projectId === "string"
|
|
84
|
+
? params.projectId
|
|
85
|
+
: undefined,
|
|
86
|
+
});
|
|
87
|
+
return oidcToken;
|
|
29
88
|
}
|
|
30
89
|
/**
|
|
31
90
|
* Attempt to extract credentials from the provided parameters. Either all
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-credentials.js","sourceRoot":"","sources":["../../src/utils/get-credentials.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"get-credentials.js","sourceRoot":"","sources":["../../src/utils/get-credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.write = write;
|
|
7
|
+
exports.code = code;
|
|
8
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
+
const colors = {
|
|
10
|
+
warn: picocolors_1.default.yellow,
|
|
11
|
+
error: picocolors_1.default.red,
|
|
12
|
+
success: picocolors_1.default.green,
|
|
13
|
+
info: picocolors_1.default.blue,
|
|
14
|
+
};
|
|
15
|
+
const logPrefix = picocolors_1.default.dim("[vercel/sandbox]");
|
|
16
|
+
function write(level, text) {
|
|
17
|
+
text = Array.isArray(text) ? text.join("\n") : text;
|
|
18
|
+
const prefixed = text.replace(/^/gm, `${logPrefix} `);
|
|
19
|
+
console.error(colors[level](prefixed));
|
|
20
|
+
}
|
|
21
|
+
function code(text) {
|
|
22
|
+
return picocolors_1.default.italic(picocolors_1.default.dim("`") + text + picocolors_1.default.dim("`"));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":";;;;;AAQA,sBAOC;AAED,oBAEC;AAnBD,4DAA8B;AAC9B,MAAM,MAAM,GAAG;IACb,IAAI,EAAE,oBAAI,CAAC,MAAM;IACjB,KAAK,EAAE,oBAAI,CAAC,GAAG;IACf,OAAO,EAAE,oBAAI,CAAC,KAAK;IACnB,IAAI,EAAE,oBAAI,CAAC,IAAI;CAChB,CAAC;AACF,MAAM,SAAS,GAAG,oBAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAC/C,SAAgB,KAAK,CACnB,KAA4C,EAC5C,IAAuB;IAEvB,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,SAAS,GAAG,CAAC,CAAC;IACtD,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,IAAI,CAAC,IAAY;IAC/B,OAAO,oBAAI,CAAC,MAAM,CAAC,oBAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,oBAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.1.
|
|
1
|
+
export declare const VERSION = "1.1.5";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vercel/sandbox",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
"async-retry": "1.3.3",
|
|
14
14
|
"jsonlines": "0.1.1",
|
|
15
15
|
"ms": "2.1.3",
|
|
16
|
+
"picocolors": "^1.1.1",
|
|
16
17
|
"tar-stream": "3.1.7",
|
|
17
18
|
"undici": "^7.16.0",
|
|
19
|
+
"xdg-app-paths": "5.1.0",
|
|
18
20
|
"zod": "3.24.4"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
"@types/node": "22.15.12",
|
|
25
27
|
"@types/tar-stream": "3.1.4",
|
|
26
28
|
"dotenv": "16.5.0",
|
|
29
|
+
"factoree": "^0.1.2",
|
|
27
30
|
"typedoc": "0.28.5",
|
|
28
31
|
"typescript": "5.8.3",
|
|
29
32
|
"vitest": "3.2.1"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { APIClient } from "./api-client";
|
|
3
|
+
import { APIError, StreamError } from "./api-error";
|
|
4
|
+
import { createNdjsonStream } from "../../test-utils/mock-response";
|
|
5
|
+
|
|
6
|
+
describe("APIClient", () => {
|
|
7
|
+
describe("getLogs", () => {
|
|
8
|
+
let client: APIClient;
|
|
9
|
+
let mockFetch: ReturnType<typeof vi.fn>;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockFetch = vi.fn();
|
|
13
|
+
client = new APIClient({
|
|
14
|
+
teamId: "team_123",
|
|
15
|
+
token: "1234",
|
|
16
|
+
fetch: mockFetch,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("yields stdout log lines", async () => {
|
|
21
|
+
const logLines = [
|
|
22
|
+
{ stream: "stdout", data: "hello" },
|
|
23
|
+
{ stream: "stdout", data: "world" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
mockFetch.mockResolvedValue(
|
|
27
|
+
new Response(createNdjsonStream(logLines), {
|
|
28
|
+
headers: { "content-type": "application/x-ndjson" },
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" });
|
|
33
|
+
const results: Array<{ stream: string; data: string }> = [];
|
|
34
|
+
|
|
35
|
+
for await (const log of logs) {
|
|
36
|
+
results.push(log);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(results).toHaveLength(2);
|
|
40
|
+
expect(results[0]).toEqual({ stream: "stdout", data: "hello" });
|
|
41
|
+
expect(results[1]).toEqual({ stream: "stdout", data: "world" });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("yields stderr log lines", async () => {
|
|
45
|
+
const logLines = [{ stream: "stderr", data: "Error" }];
|
|
46
|
+
|
|
47
|
+
mockFetch.mockResolvedValue(
|
|
48
|
+
new Response(createNdjsonStream(logLines), {
|
|
49
|
+
headers: { "content-type": "application/x-ndjson" },
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" });
|
|
54
|
+
const results: Array<{ stream: string; data: string }> = [];
|
|
55
|
+
|
|
56
|
+
for await (const log of logs) {
|
|
57
|
+
results.push(log);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
expect(results).toHaveLength(1);
|
|
61
|
+
expect(results[0]).toEqual({
|
|
62
|
+
stream: "stderr",
|
|
63
|
+
data: "Error",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("throws APIError when content-type is not application/x-ndjson", async () => {
|
|
68
|
+
mockFetch.mockResolvedValue(
|
|
69
|
+
new Response(null, {
|
|
70
|
+
headers: { "content-type": "application/json" },
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" });
|
|
75
|
+
|
|
76
|
+
await expect(async () => {
|
|
77
|
+
for await (const _ of logs) {
|
|
78
|
+
}
|
|
79
|
+
}).rejects.toThrow(APIError);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("throws APIError when response body is null", async () => {
|
|
83
|
+
mockFetch.mockResolvedValue(
|
|
84
|
+
new Response(null, {
|
|
85
|
+
headers: { "content-type": "application/x-ndjson" },
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" });
|
|
90
|
+
|
|
91
|
+
await expect(async () => {
|
|
92
|
+
for await (const _ of logs) {
|
|
93
|
+
}
|
|
94
|
+
}).rejects.toThrow(APIError);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("throws StreamError when error log line is received", async () => {
|
|
98
|
+
const logLines = [
|
|
99
|
+
{ stream: "stdout", data: "some logs" },
|
|
100
|
+
{
|
|
101
|
+
stream: "error",
|
|
102
|
+
data: {
|
|
103
|
+
code: "sandbox_stream_closed",
|
|
104
|
+
message: "Sandbox stream was closed and is not accepting commands.",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
mockFetch.mockResolvedValue(
|
|
110
|
+
new Response(createNdjsonStream(logLines), {
|
|
111
|
+
headers: { "content-type": "application/x-ndjson" },
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" });
|
|
116
|
+
const results: Array<{ stream: string; data: string }> = [];
|
|
117
|
+
|
|
118
|
+
await expect(async () => {
|
|
119
|
+
for await (const log of logs) {
|
|
120
|
+
results.push(log);
|
|
121
|
+
}
|
|
122
|
+
}).rejects.toThrow(StreamError);
|
|
123
|
+
|
|
124
|
+
expect(results).toHaveLength(1);
|
|
125
|
+
expect(results[0]).toEqual({ stream: "stdout", data: "some logs" });
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Options as RetryOptions } from "async-retry";
|
|
2
2
|
import { APIError } from "./api-error";
|
|
3
|
-
import { setTimeout } from "timers/promises";
|
|
3
|
+
import { setTimeout } from "node:timers/promises";
|
|
4
4
|
import retry from "async-retry";
|
|
5
5
|
|
|
6
6
|
export interface RequestOptions {
|
package/src/auth/api.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NotOk } from "./error";
|
|
2
|
+
|
|
3
|
+
export async function fetchApi(opts: {
|
|
4
|
+
token: string;
|
|
5
|
+
endpoint: string;
|
|
6
|
+
method?: string;
|
|
7
|
+
body?: string;
|
|
8
|
+
}): Promise<unknown> {
|
|
9
|
+
const x = await fetch(`https://api.vercel.com${opts.endpoint}`, {
|
|
10
|
+
method: opts.method,
|
|
11
|
+
body: opts.body,
|
|
12
|
+
headers: {
|
|
13
|
+
Authorization: `Bearer ${opts.token}`,
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
if (!x.ok) {
|
|
18
|
+
let message = await x.text();
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const { error } = JSON.parse(message);
|
|
22
|
+
message = `${error.code.toUpperCase()}: ${error.message}`;
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
throw new NotOk({
|
|
26
|
+
responseText: message,
|
|
27
|
+
statusCode: x.status,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return (await x.json()) as unknown;
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export class NotOk extends Error {
|
|
2
|
+
name = "NotOk";
|
|
3
|
+
response: { statusCode: number; responseText: string };
|
|
4
|
+
constructor(response: { statusCode: number; responseText: string }) {
|
|
5
|
+
super(`HTTP ${response.statusCode}: ${response.responseText}`);
|
|
6
|
+
this.response = response;
|
|
7
|
+
}
|
|
8
|
+
}
|
package/src/auth/file.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import XDGAppPaths from "xdg-app-paths";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { json } from "./zod";
|
|
7
|
+
|
|
8
|
+
const ZodDate = z.number().transform((seconds) => new Date(seconds * 1000));
|
|
9
|
+
|
|
10
|
+
const AuthFile = z.object({
|
|
11
|
+
token: z.string().min(1).optional(),
|
|
12
|
+
refreshToken: z.string().min(1).optional(),
|
|
13
|
+
expiresAt: ZodDate.optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const StoredAuthFile = json.pipe(AuthFile);
|
|
17
|
+
|
|
18
|
+
type AuthFile = z.infer<typeof AuthFile>;
|
|
19
|
+
|
|
20
|
+
// Returns whether a directory exists
|
|
21
|
+
const isDirectory = (path: string): boolean => {
|
|
22
|
+
try {
|
|
23
|
+
return fs.lstatSync(path).isDirectory();
|
|
24
|
+
} catch (_) {
|
|
25
|
+
// We don't care which kind of error occured, it isn't a directory anyway.
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Returns in which directory the config should be present
|
|
31
|
+
const getGlobalPathConfig = (): string => {
|
|
32
|
+
const vercelDirectories = XDGAppPaths("com.vercel.cli").dataDirs();
|
|
33
|
+
|
|
34
|
+
const possibleConfigPaths = [
|
|
35
|
+
...vercelDirectories, // latest vercel directory
|
|
36
|
+
path.join(homedir(), ".now"), // legacy config in user's home directory
|
|
37
|
+
...XDGAppPaths("now").dataDirs(), // legacy XDG directory
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// The customPath flag is the preferred location,
|
|
41
|
+
// followed by the vercel directory,
|
|
42
|
+
// followed by the now directory.
|
|
43
|
+
// If none of those exist, use the vercel directory.
|
|
44
|
+
return (
|
|
45
|
+
possibleConfigPaths.find((configPath) => isDirectory(configPath)) ||
|
|
46
|
+
vercelDirectories[0]
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const getAuth = () => {
|
|
51
|
+
try {
|
|
52
|
+
const pathname = path.join(getGlobalPathConfig(), "auth.json");
|
|
53
|
+
return StoredAuthFile.parse(fs.readFileSync(pathname, "utf8"));
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function updateAuthConfig(config: AuthFile): void {
|
|
60
|
+
const pathname = path.join(getGlobalPathConfig(), "auth.json");
|
|
61
|
+
fs.mkdirSync(path.dirname(pathname), { recursive: true });
|
|
62
|
+
const content = {
|
|
63
|
+
token: config.token,
|
|
64
|
+
expiresAt:
|
|
65
|
+
config.expiresAt && Math.round(config.expiresAt.getTime() / 1000),
|
|
66
|
+
refreshToken: config.refreshToken,
|
|
67
|
+
} satisfies z.input<typeof AuthFile>;
|
|
68
|
+
fs.writeFileSync(pathname, JSON.stringify(content) + "\n");
|
|
69
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// This file can also be imported as `@vercel/sandbox/dist/auth`, which is completely fine.
|
|
2
|
+
// The only valid importer of this would be the CLI as we share the same codebase.
|
|
3
|
+
|
|
4
|
+
export * from "./file";
|
|
5
|
+
export type * from "./file";
|
|
6
|
+
export * from "./oauth";
|
|
7
|
+
export type * from "./oauth";
|
|
8
|
+
export { pollForToken } from "./poll-for-token";
|
|
9
|
+
export { inferScope, selectTeam } from "./project";
|