vercel
Version:
The command-line interface for Vercel
1,600 lines (1,584 loc) • 75.1 kB
JavaScript
import { createRequire as __createRequire } from 'node:module';
import { fileURLToPath as __fileURLToPath } from 'node:url';
import { dirname as __dirname_ } from 'node:path';
const require = __createRequire(import.meta.url);
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __dirname_(__filename);
import {
getUpdateCommand
} from "./chunk-FUBTAFL2.js";
import {
getGlobalPathConfig,
login
} from "./chunk-F2UPASLT.js";
import {
apiCommand,
listSubcommand2 as listSubcommand,
loginCommand
} from "./chunk-UVFXUXOZ.js";
import {
help
} from "./chunk-MMF4BVAP.js";
import {
TelemetryClient
} from "./chunk-4OEA5ILS.js";
import {
require_ms
} from "./chunk-CO5D46AG.js";
import {
getFlagsSpecification,
parseArguments,
printError,
require_strip_ansi
} from "./chunk-4GQQJY5Y.js";
import {
packageName
} from "./chunk-UGXBNJMO.js";
import {
output_manager_default
} from "./chunk-ZQKJVHXY.js";
import {
require_source
} from "./chunk-S7KYDPEM.js";
import {
__commonJS,
__toESM
} from "./chunk-TZ2YI2VH.js";
// ../../node_modules/.pnpm/jaro-winkler@0.2.8/node_modules/jaro-winkler/index.js
var require_jaro_winkler = __commonJS({
"../../node_modules/.pnpm/jaro-winkler@0.2.8/node_modules/jaro-winkler/index.js"(exports, module) {
(function(root) {
"use strict";
function extend(a, b) {
for (var property in b) {
if (b.hasOwnProperty(property)) {
a[property] = b[property];
}
}
return a;
}
function distance2(s1, s2, options) {
var m = 0;
var defaults = { caseSensitive: true };
var settings = extend(defaults, options);
var i;
var j;
if (s1.length === 0 || s2.length === 0) {
return 0;
}
if (!settings.caseSensitive) {
s1 = s1.toUpperCase();
s2 = s2.toUpperCase();
}
if (s1 === s2) {
return 1;
}
var range = Math.floor(Math.max(s1.length, s2.length) / 2) - 1;
var s1Matches = new Array(s1.length);
var s2Matches = new Array(s2.length);
for (i = 0; i < s1.length; i++) {
var low = i >= range ? i - range : 0;
var high = i + range <= s2.length - 1 ? i + range : s2.length - 1;
for (j = low; j <= high; j++) {
if (s1Matches[i] !== true && s2Matches[j] !== true && s1[i] === s2[j]) {
++m;
s1Matches[i] = s2Matches[j] = true;
break;
}
}
}
if (m === 0) {
return 0;
}
var k = 0;
var numTrans = 0;
for (i = 0; i < s1.length; i++) {
if (s1Matches[i] === true) {
for (j = k; j < s2.length; j++) {
if (s2Matches[j] === true) {
k = j + 1;
break;
}
}
if (s1[i] !== s2[j]) {
++numTrans;
}
}
}
var weight = (m / s1.length + m / s2.length + (m - numTrans / 2) / m) / 3;
var l = 0;
var p = 0.1;
if (weight > 0.7) {
while (s1[l] === s2[l] && l < 4) {
++l;
}
weight = weight + l * p * (1 - weight);
}
return weight;
}
if (typeof define === "function" && define.amd) {
define([], function() {
return distance2;
});
} else if (typeof exports === "object") {
module.exports = distance2;
} else {
root.distance = distance2;
}
})(exports);
}
});
// src/util/openapi/openapi-cache.ts
import { join } from "path";
import { readFile, writeFile, mkdir } from "fs/promises";
// src/util/openapi/constants.ts
var OPENAPI_URL = "https://openapi.vercel.sh/";
var CACHE_FILE = "openapi-spec.json";
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
var FETCH_TIMEOUT_MS = 10 * 1e3;
// src/util/openapi/openapi-cache.ts
var OpenApiCache = class {
constructor() {
this.spec = null;
this.cachePath = join(getGlobalPathConfig(), CACHE_FILE);
}
/**
* Check if the spec has been loaded
*/
get isLoaded() {
return this.spec !== null;
}
/**
* Load the OpenAPI spec, using cache if available and fresh.
* Returns true if successful, false otherwise.
*/
async load(forceRefresh = false) {
if (!forceRefresh) {
const cached = await this.readCache();
if (cached && !this.isExpired(cached.fetchedAt)) {
output_manager_default.debug("Using cached OpenAPI spec");
this.spec = cached.spec;
return true;
}
}
try {
output_manager_default.debug("Fetching OpenAPI spec from " + OPENAPI_URL);
this.spec = await this.fetchSpec();
await this.saveCache(this.spec);
return true;
} catch (err) {
output_manager_default.debug(`Failed to fetch OpenAPI spec: ${err}`);
const stale = await this.readCache();
if (stale) {
output_manager_default.debug("Using stale cached OpenAPI spec");
this.spec = stale.spec;
return true;
}
return false;
}
}
/**
* Load the OpenAPI spec with spinner UI.
* Returns true if successful, false otherwise.
*/
async loadWithSpinner(forceRefresh = false) {
output_manager_default.spinner(
forceRefresh ? "Refreshing API endpoints..." : "Loading API endpoints..."
);
const success = await this.load(forceRefresh);
output_manager_default.stopSpinner();
return success;
}
/**
* Get all available endpoints from the loaded spec, sorted by path then method.
* Throws if spec hasn't been loaded yet.
*/
getEndpoints() {
this.ensureLoaded();
const endpoints = this.extractEndpoints();
return this.sortEndpoints(endpoints);
}
/**
* Extract body fields from a requestBody schema.
* Throws if spec hasn't been loaded yet.
*/
getBodyFields(endpoint) {
this.ensureLoaded();
if (!endpoint.requestBody?.content)
return [];
const jsonContent = endpoint.requestBody.content["application/json"];
if (!jsonContent?.schema)
return [];
const schema = this.resolveSchemaRef(jsonContent.schema);
if (!schema?.properties)
return [];
const requiredFields = new Set(schema.required || []);
const fields = [];
for (const [name, propSchema] of Object.entries(schema.properties)) {
const resolvedProp = this.resolveSchemaRef(propSchema);
let enumValues = resolvedProp?.enum || propSchema.enum;
if (!enumValues && (resolvedProp?.type === "array" || propSchema.type === "array")) {
const items = resolvedProp?.items || propSchema.items;
if (items) {
const resolvedItems = this.resolveSchemaRef(items);
enumValues = resolvedItems?.enum || items.enum;
}
}
fields.push({
name,
required: requiredFields.has(name),
description: resolvedProp?.description || propSchema.description,
type: resolvedProp?.type || propSchema.type,
enumValues
});
}
fields.sort((a, b) => {
if (a.required !== b.required) {
return a.required ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
return fields;
}
/**
* Extract `x-vercel-cli.displayColumns` from the 200 response schema of an
* endpoint. Returns `null` when the spec has no display hint.
*/
getDisplayColumns(endpoint) {
this.ensureLoaded();
const pathItem = this.spec.paths[endpoint.path];
if (!pathItem)
return null;
const operation = pathItem[endpoint.method.toLowerCase()];
if (!operation?.responses)
return null;
const ok = operation.responses["200"] || operation.responses["201"];
if (!ok)
return null;
const jsonContent = ok.content?.["application/json"];
if (!jsonContent?.schema)
return null;
return this.findDisplayColumns(jsonContent.schema);
}
findDisplayColumns(schema) {
const xCli = schema["x-vercel-cli"];
if (xCli?.displayColumns)
return xCli.displayColumns;
for (const key of ["oneOf", "allOf", "anyOf"]) {
const variants = schema[key];
if (variants) {
for (const sub of variants) {
const found = this.findDisplayColumns(sub);
if (found)
return found;
}
}
}
return null;
}
// ─────────────────────────────────────────────────────────────────────────────
// Private methods
// ─────────────────────────────────────────────────────────────────────────────
/**
* Ensure the spec is loaded before accessing it
*/
ensureLoaded() {
if (!this.spec) {
throw new Error(
"OpenAPI spec not loaded. Call load() or loadWithSpinner() first."
);
}
}
/**
* Read cached spec from disk
*/
async readCache() {
try {
const content = await readFile(this.cachePath, "utf-8");
return JSON.parse(content);
} catch {
return null;
}
}
/**
* Save spec to disk cache
*/
async saveCache(spec) {
const cached = {
fetchedAt: Date.now(),
spec
};
const dir = join(this.cachePath, "..");
await mkdir(dir, { recursive: true });
await writeFile(this.cachePath, JSON.stringify(cached));
output_manager_default.debug("Saved OpenAPI spec to cache");
}
/**
* Fetch OpenAPI spec from remote with timeout
*/
async fetchSpec() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
try {
const response = await fetch(OPENAPI_URL, { signal: controller.signal });
if (!response.ok) {
throw new Error(`Failed to fetch OpenAPI spec: ${response.status}`);
}
return await response.json();
} finally {
clearTimeout(timeoutId);
}
}
/**
* Check if cached spec is expired
*/
isExpired(fetchedAt) {
return Date.now() - fetchedAt > CACHE_TTL_MS;
}
/**
* Sort endpoints by path, then by method
*/
sortEndpoints(endpoints) {
return endpoints.sort((a, b) => {
const pathCompare = a.path.localeCompare(b.path);
if (pathCompare !== 0)
return pathCompare;
return a.method.localeCompare(b.method);
});
}
/**
* Extract all available endpoints from the spec
*/
extractEndpoints() {
const endpoints = [];
for (const [path, pathItem] of Object.entries(this.spec.paths)) {
const methods = ["get", "post", "put", "patch", "delete"];
for (const method of methods) {
const operation = pathItem[method];
if (operation) {
const pathParams = pathItem.parameters || [];
const opParams = operation.parameters || [];
const allParams = [...pathParams, ...opParams];
endpoints.push({
path,
method: method.toUpperCase(),
summary: operation.summary || pathItem.summary || "",
description: operation.description || pathItem.description || "",
operationId: operation.operationId || "",
tags: operation.tags || [],
parameters: allParams,
requestBody: operation.requestBody
});
}
}
}
return endpoints;
}
/**
* Resolve a $ref to its actual schema
*/
resolveSchemaRef(schema) {
if (!schema)
return void 0;
if (schema.$ref) {
const match = schema.$ref.match(/^#\/components\/schemas\/(.+)$/);
if (match && this.spec.components?.schemas) {
const resolved = this.spec.components.schemas[match[1]];
return this.resolveSchemaRef(resolved);
}
return void 0;
}
if (schema.allOf && schema.allOf.length > 0) {
const merged = { type: "object", properties: {}, required: [] };
for (const subSchema of schema.allOf) {
const resolved = this.resolveSchemaRef(subSchema);
if (resolved) {
if (resolved.properties) {
merged.properties = {
...merged.properties,
...resolved.properties
};
}
if (resolved.required) {
merged.required = [
...merged.required || [],
...resolved.required
];
}
}
}
return merged;
}
return schema;
}
};
// src/util/openapi/matches-cli-api-tag.ts
async function matchesCliApiTag(tagHint) {
if (!tagHint || tagHint.startsWith("-") || tagHint.includes("/")) {
return false;
}
const cache = new OpenApiCache();
const loaded = await cache.load();
if (!loaded) {
return false;
}
const endpoints = cache.getEndpoints();
const lower = tagHint.toLowerCase();
return endpoints.some((ep) => ep.tags.some((t) => t.toLowerCase() === lower));
}
async function resolveOpenApiTagForProjectsCli() {
if (await matchesCliApiTag("projects")) {
return "projects";
}
if (await matchesCliApiTag("project")) {
return "project";
}
return null;
}
async function resolveOpenApiTagForTeamsCli() {
if (await matchesCliApiTag("teams")) {
return "teams";
}
if (await matchesCliApiTag("team")) {
return "team";
}
return null;
}
// src/commands/api/index.ts
var import_chalk4 = __toESM(require_source(), 1);
// src/util/telemetry/commands/api/index.ts
var ApiTelemetryClient = class extends TelemetryClient {
trackCliArgumentEndpoint(endpoint) {
if (endpoint) {
const normalized = this.normalizeEndpoint(endpoint);
this.trackCliArgument({
arg: "endpoint",
value: normalized
});
}
}
trackCliArgumentOperationId(operationId) {
if (operationId) {
this.trackCliArgument({
arg: "operationId",
value: operationId
});
}
}
trackCliOptionMethod(method) {
if (method) {
const validMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"];
const upperMethod = method.toUpperCase();
const value = validMethods.includes(upperMethod) ? upperMethod : this.redactedValue;
this.trackCliOption({
option: "method",
value
});
}
}
trackCliOptionField(fields) {
if (fields && fields.length > 0) {
this.trackCliOption({
option: "field",
value: this.redactedArgumentsLength(fields)
});
}
}
trackCliOptionRawField(fields) {
if (fields && fields.length > 0) {
this.trackCliOption({
option: "raw-field",
value: this.redactedArgumentsLength(fields)
});
}
}
trackCliOptionHeader(headers) {
if (headers && headers.length > 0) {
this.trackCliOption({
option: "header",
value: this.redactedArgumentsLength(headers)
});
}
}
trackCliOptionInput(input) {
if (input) {
const value = input === "-" ? "stdin" : "file";
this.trackCliOption({
option: "input",
value
});
}
}
trackCliFlagPaginate(value) {
if (value) {
this.trackCliFlag("paginate");
}
}
trackCliFlagInclude(value) {
if (value) {
this.trackCliFlag("include");
}
}
trackCliFlagSilent(value) {
if (value) {
this.trackCliFlag("silent");
}
}
trackCliFlagVerbose(value) {
if (value) {
this.trackCliFlag("verbose");
}
}
trackCliFlagRaw(value) {
if (value) {
this.trackCliFlag("raw");
}
}
trackCliFlagRefresh(value) {
if (value) {
this.trackCliFlag("refresh");
}
}
trackCliOptionGenerate(format) {
if (format) {
const validFormats = ["curl"];
const value = validFormats.includes(format) ? format : this.redactedValue;
this.trackCliOption({
option: "generate",
value
});
}
}
trackCliFlagDangerouslySkipPermissions(value) {
if (value) {
this.trackCliFlag("dangerously-skip-permissions");
}
}
trackCliSubcommandList() {
this.trackCliSubcommand({ subcommand: "list", value: "list" });
}
trackCliOptionFormat(format) {
if (format) {
const validFormats = ["table", "json"];
const value = validFormats.includes(format) ? format : this.redactedValue;
this.trackCliOption({
option: "format",
value
});
}
}
/**
* Normalize endpoint by replacing IDs with placeholders for privacy
*/
normalizeEndpoint(endpoint) {
return endpoint.replace(/\/dpl_[a-zA-Z0-9]+/g, "/:deploymentId").replace(/\/prj_[a-zA-Z0-9]+/g, "/:projectId").replace(/\/team_[a-zA-Z0-9]+/g, "/:teamId").replace(/\/[a-f0-9]{24}/g, "/:id").replace(/\/[a-f0-9-]{36}/g, "/:uuid");
}
};
// src/commands/api/request-builder.ts
import { readFile as readFile2 } from "fs/promises";
import { resolve } from "path";
async function buildRequest(endpoint, flags) {
const headers = {};
let body;
const customHeaders = flags["--header"] || [];
for (const header of customHeaders) {
const colonIndex = header.indexOf(":");
if (colonIndex > 0) {
const key = header.substring(0, colonIndex).trim();
const value = header.substring(colonIndex + 1).trim();
headers[key] = value;
}
}
const fields = flags["--field"] || [];
const rawFields = flags["--raw-field"] || [];
if (fields.length > 0 || rawFields.length > 0) {
body = {};
for (const field of fields) {
const { key, value } = await parseField(field, true);
body[key] = value;
}
for (const field of rawFields) {
const { key, value } = await parseField(field, false);
body[key] = value;
}
}
if (flags["--input"]) {
const inputPath = flags["--input"];
if (inputPath === "-") {
body = await readStdin();
} else {
body = await readFile2(resolve(inputPath), "utf-8");
}
if (typeof body === "string") {
try {
body = JSON.parse(body);
} catch {
}
}
}
let method = flags["--method"]?.toUpperCase() || "GET";
if (!flags["--method"] && body) {
method = "POST";
}
return {
url: endpoint,
method,
headers,
body
};
}
async function parseCliKeyValueField(field, typed) {
return parseField(field, typed);
}
async function parseField(field, typed) {
const eqIndex = field.indexOf("=");
if (eqIndex === -1) {
throw new Error(`Invalid field format: ${field}. Expected key=value`);
}
const key = field.substring(0, eqIndex);
let value = field.substring(eqIndex + 1);
if (typed && typeof value === "string") {
if (value.startsWith("@")) {
const filePath = value.substring(1);
if (filePath === "-") {
value = await readStdin();
} else {
value = await readFile2(resolve(filePath), "utf-8");
}
if (typeof value === "string") {
try {
value = JSON.parse(value);
} catch {
}
}
} else if (value === "true") {
value = true;
} else if (value === "false") {
value = false;
} else if (value === "null") {
value = null;
} else if (/^-?\d+$/.test(value)) {
value = parseInt(value, 10);
} else if (/^-?\d*\.\d+$/.test(value)) {
value = parseFloat(value);
} else if (value.startsWith("[") || value.startsWith("{")) {
try {
value = JSON.parse(value);
} catch {
}
}
}
return { key, value };
}
async function readStdin() {
const chunks = [];
for await (const chunk of process.stdin) {
chunks.push(Buffer.from(chunk));
}
return Buffer.concat(chunks).toString(
"utf-8"
);
}
function formatOutput(data, options) {
if (options.raw) {
if (typeof data === "string") {
return data;
}
return JSON.stringify(data);
}
return JSON.stringify(data, null, 2);
}
function generateCurlCommand(config, baseUrl) {
const parts = ["curl"];
if (config.method !== "GET") {
parts.push(`-X ${config.method}`);
}
parts.push(`-H 'Authorization: Bearer <TOKEN>'`);
for (const [key, value] of Object.entries(config.headers)) {
parts.push(`-H '${key}: ${escapeShellArg(value)}'`);
}
if (config.body) {
const bodyStr = typeof config.body === "string" ? config.body : JSON.stringify(config.body);
parts.push(`-H 'Content-Type: application/json'`);
parts.push(`-d '${escapeShellArg(bodyStr)}'`);
}
const fullUrl = `${baseUrl}${config.url}`;
parts.push(`'${fullUrl}'`);
return parts.join(" \\\n ");
}
function escapeShellArg(str) {
return str.replace(/'/g, "'\\''");
}
// src/commands/api/operation-request-builder.ts
import { readFile as readFile3 } from "fs/promises";
import { resolve as resolve2 } from "path";
var GLOBAL_CLI_QUERY_PARAMS = /* @__PURE__ */ new Set(["teamId", "slug"]);
async function parseOperationKeyValuePairs(endpoint, bodyFields, flags, positionalKeyValues) {
const pathParamNames = new Set(
endpoint.parameters.filter((p) => p.in === "path").map((p) => p.name)
);
const queryParamNames = new Set(
endpoint.parameters.filter((p) => p.in === "query").map((p) => p.name)
);
const headerParamNames = new Set(
endpoint.parameters.filter((p) => p.in === "header").map((p) => p.name)
);
const bodyFieldNames = new Set(bodyFields.map((f) => f.name));
const pathValues = {};
const queryValues = {};
const headerValues = {};
const body = {};
async function dispatchPair(field, typed) {
const eqIndex = field.indexOf("=");
if (eqIndex === -1) {
throw new Error(
`Invalid option "${field}". Expected key=value (or use flags -F / -f).`
);
}
const key = field.slice(0, eqIndex);
const param = endpoint.parameters.find((p) => p.name === key);
if (param?.in === "path") {
const { value } = await parseCliKeyValueField(field, false);
pathValues[key] = String(value);
return;
}
if (param?.in === "query") {
const { value } = await parseCliKeyValueField(field, typed);
queryValues[key] = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
return;
}
if (param?.in === "header") {
const { value } = await parseCliKeyValueField(field, false);
headerValues[key] = String(value);
return;
}
if (param?.in === "cookie") {
throw new Error(
`Option "${key}" is cookie-based; set it via headers instead.`
);
}
if (bodyFieldNames.has(key)) {
const { value } = await parseCliKeyValueField(field, typed);
body[key] = value;
return;
}
if (!param && pathParamNames.has(key)) {
const { value } = await parseCliKeyValueField(field, false);
pathValues[key] = String(value);
return;
}
if (!param && queryParamNames.has(key)) {
const { value } = await parseCliKeyValueField(field, typed);
queryValues[key] = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
return;
}
if (!param && headerParamNames.has(key)) {
const { value } = await parseCliKeyValueField(field, false);
headerValues[key] = String(value);
return;
}
throw new Error(
`Unknown option "${key}" for operation ${endpoint.operationId}. Check the API docs or run \`vercel api ls --format json\`.`
);
}
for (const field of flags["--field"] || []) {
await dispatchPair(field, true);
}
for (const field of flags["--raw-field"] || []) {
await dispatchPair(field, false);
}
for (const field of positionalKeyValues) {
await dispatchPair(field, true);
}
return { pathValues, queryValues, headerValues, body };
}
function getMissingRequiredOperationParams(endpoint, bodyFields, parsed, flags) {
const pathParams = endpoint.parameters.filter((p) => p.in === "path");
const missingPath = pathParams.filter(
(p) => parsed.pathValues[p.name] === void 0
);
const requiredQuery = endpoint.parameters.filter(
(p) => p.in === "query" && p.required && !GLOBAL_CLI_QUERY_PARAMS.has(p.name)
);
const missingQuery = requiredQuery.filter(
(p) => parsed.queryValues[p.name] === void 0
);
const requiredHeader = endpoint.parameters.filter(
(p) => p.in === "header" && p.required
);
const missingHeader = requiredHeader.filter(
(p) => parsed.headerValues[p.name] === void 0
);
const missingBody = bodyFields.filter(
(f) => f.required && parsed.body[f.name] === void 0 && !flags["--input"]
);
return {
path: missingPath,
query: missingQuery,
header: missingHeader,
body: missingBody
};
}
function getUnsetOptionalOperationParams(endpoint, bodyFields, parsed, flags) {
const unsetQuery = endpoint.parameters.filter(
(p) => p.in === "query" && parsed.queryValues[p.name] === void 0 && (!p.required || GLOBAL_CLI_QUERY_PARAMS.has(p.name))
);
const unsetHeader = endpoint.parameters.filter(
(p) => p.in === "header" && !p.required && parsed.headerValues[p.name] === void 0
);
const unsetBody = bodyFields.filter(
(f) => !f.required && parsed.body[f.name] === void 0 && !flags["--input"]
);
return {
query: unsetQuery,
header: unsetHeader,
body: unsetBody
};
}
async function buildRequestForResolvedOperation(endpoint, bodyFields, flags, positionalKeyValues) {
const headers = {};
const customHeaders = flags["--header"] || [];
for (const header of customHeaders) {
const colonIndex = header.indexOf(":");
if (colonIndex > 0) {
const key = header.substring(0, colonIndex).trim();
const value = header.substring(colonIndex + 1).trim();
headers[key] = value;
}
}
const method = (flags["--method"]?.toUpperCase() || endpoint.method).toUpperCase();
const pathParamNames = new Set(
endpoint.parameters.filter((p) => p.in === "path").map((p) => p.name)
);
const parsed = await parseOperationKeyValuePairs(
endpoint,
bodyFields,
flags,
positionalKeyValues
);
const { pathValues, queryValues, headerValues, body } = parsed;
for (const [k, v] of Object.entries(headerValues)) {
headers[k] = v;
}
let urlPath = endpoint.path;
for (const name of pathParamNames) {
const value = pathValues[name];
if (value === void 0) {
throw new Error(
`Missing required path option {${name}} for ${endpoint.operationId}.`
);
}
urlPath = urlPath.replace(`{${name}}`, encodeURIComponent(value));
}
if (/\{[^}]+\}/.test(urlPath)) {
throw new Error(
`Unresolved path placeholders in ${urlPath}. Provide values for all path options.`
);
}
const requiredQuery = endpoint.parameters.filter(
(p) => p.in === "query" && p.required && !GLOBAL_CLI_QUERY_PARAMS.has(p.name)
);
for (const p of requiredQuery) {
if (queryValues[p.name] === void 0) {
throw new Error(
`Missing required query option "${p.name}" for ${endpoint.operationId}.`
);
}
}
const requiredHeader = endpoint.parameters.filter(
(h) => h.in === "header" && h.required
);
for (const h of requiredHeader) {
if (headerValues[h.name] === void 0) {
throw new Error(
`Missing required header option "${h.name}" for ${endpoint.operationId}.`
);
}
}
const requiredBody = bodyFields.filter((f) => f.required);
for (const f of requiredBody) {
if (body[f.name] === void 0 && !flags["--input"]) {
throw new Error(
`Missing required body option "${f.name}" for ${endpoint.operationId}.`
);
}
}
const queryString = new URLSearchParams(queryValues).toString();
if (queryString) {
urlPath += (urlPath.includes("?") ? "&" : "?") + queryString;
}
let finalBody = Object.keys(body).length > 0 ? body : void 0;
if (flags["--input"]) {
const inputPath = flags["--input"];
let inputBody;
if (inputPath === "-") {
inputBody = await readStdin();
} else {
inputBody = await readFile3(resolve2(inputPath), "utf-8");
}
if (typeof inputBody === "string") {
try {
finalBody = JSON.parse(inputBody);
} catch {
finalBody = inputBody;
}
} else {
finalBody = inputBody;
}
}
if (method === "GET" || method === "HEAD") {
finalBody = void 0;
}
return {
url: urlPath,
method,
headers,
body: finalBody
};
}
// src/util/openapi/vercel-cli-table.ts
var import_ms = __toESM(require_ms(), 1);
var import_chalk = __toESM(require_source(), 1);
// src/util/openapi/resolve-by-tag-operation.ts
function resolveEndpointByTagAndOperationId(endpoints, tag, operationHint) {
const tagLower = tag.toLowerCase();
const tagMatches = endpoints.filter(
(ep) => ep.tags.some((t) => t.toLowerCase() === tagLower)
);
if (tagMatches.length === 0) {
return {
ok: false,
reason: "no_tag",
tag,
tagMatches: [],
operationHint
};
}
const withOpId = tagMatches.filter((ep) => ep.operationId.length > 0);
if (withOpId.length === 0) {
return {
ok: false,
reason: "no_operation",
tag,
tagMatches,
operationHint
};
}
const hint = operationHint.trim();
const hintLower = hint.toLowerCase();
const exact = withOpId.filter((ep) => ep.operationId === hint);
if (exact.length === 1) {
return { ok: true, endpoint: exact[0] };
}
if (exact.length > 1) {
return {
ok: false,
reason: "ambiguous_operation",
tag,
tagMatches: exact,
operationHint: hint
};
}
const exactCi = withOpId.filter(
(ep) => ep.operationId.toLowerCase() === hintLower
);
if (exactCi.length === 1) {
return { ok: true, endpoint: exactCi[0] };
}
if (exactCi.length > 1) {
return {
ok: false,
reason: "ambiguous_operation",
tag,
tagMatches: exactCi,
operationHint: hint
};
}
return {
ok: false,
reason: "no_operation",
tag,
tagMatches,
operationHint: hint
};
}
// src/util/openapi/try-openapi-fallback.ts
async function tryOpenApiFallback(client, cliArgs, resolveTag) {
if (!process.env.VERCEL_AUTO_API) {
return null;
}
const operationHint = cliArgs[0];
if (!operationHint || operationHint.startsWith("-")) {
return null;
}
const tag = await resolveTag();
if (!tag) {
return null;
}
const apiFlagsSpec = getFlagsSpecification(apiCommand.options);
let apiParsed;
try {
apiParsed = parseArguments(client.argv.slice(2), apiFlagsSpec, {
permissive: true
});
} catch {
return null;
}
const flags = apiParsed.flags;
if (flags["--dangerously-skip-permissions"]) {
client.dangerouslySkipPermissions = true;
}
if (flags["--help"]) {
return printOperationHelpForTagCommand(flags, tag, operationHint);
}
return runTagOperation(client, {
tag,
operationId: operationHint,
flags,
positionalOperationFields: cliArgs.slice(1)
});
}
// src/commands/api/constants.ts
var API_BASE_URL = "https://api.vercel.com";
// src/commands/api/format-utils.ts
var import_chalk2 = __toESM(require_source(), 1);
function colorizeMethod(method) {
switch (method) {
case "GET":
return import_chalk2.default.cyan(method);
case "POST":
return import_chalk2.default.green(method);
case "PUT":
return import_chalk2.default.yellow(method);
case "PATCH":
return import_chalk2.default.blue(method);
case "DELETE":
return import_chalk2.default.red(method);
default:
return method;
}
}
function colorizeMethodPadded(method, width = 7) {
const colored = colorizeMethod(method);
const padding = " ".repeat(Math.max(0, width - method.length));
return colored + padding;
}
function formatPathParam(paramName) {
return import_chalk2.default.cyan(`{${paramName}}`);
}
function formatTypeHint(type) {
return import_chalk2.default.dim(`[${type}]`);
}
function formatDescription(description) {
if (!description)
return "";
return import_chalk2.default.gray(` (${description})`);
}
// src/commands/api/display-columns.ts
var import_chalk3 = __toESM(require_source(), 1);
function getByPath(obj, path) {
let current = obj;
for (const segment of path.split(".")) {
if (current == null || typeof current !== "object")
return void 0;
current = current[segment];
}
return current;
}
function parseArrayColumns(data, columns) {
const entries = Object.entries(columns);
const first = entries[0];
if (!first)
return null;
const bracketIdx = first[1].indexOf("[].");
if (bracketIdx === -1)
return null;
const arrayKey = first[1].slice(0, bracketIdx);
const rowColumns = {};
for (const [label, path] of entries) {
const prefix = path.slice(0, bracketIdx);
if (prefix !== arrayKey || !path.startsWith(prefix + "[].")) {
return null;
}
rowColumns[label] = path.slice(bracketIdx + 3);
}
const arr = getByPath(data, arrayKey);
if (!Array.isArray(arr))
return null;
return { rows: arr, rowColumns };
}
function formatValue(value) {
if (value === null || value === void 0)
return import_chalk3.default.dim("\u2013");
if (typeof value === "number") {
if (value > 1e12 && value < 2e12) {
return new Date(value).toISOString();
}
return String(value);
}
if (typeof value === "boolean")
return String(value);
if (typeof value === "string")
return value;
return JSON.stringify(value);
}
function renderCard(data, columns) {
const entries = Object.entries(columns);
const maxLabel = Math.max(...entries.map(([label]) => label.length));
const lines = entries.map(([label, path]) => {
const value = getByPath(data, path);
return ` ${import_chalk3.default.gray(label.padEnd(maxLabel))} ${formatValue(value)}`;
});
return lines.join("\n");
}
function renderTable(rows, columns) {
const entries = Object.entries(columns);
const headerRow = entries.map(([label]) => label);
const dataRows = rows.map(
(row) => entries.map(([, path]) => formatValue(getByPath(row, path)))
);
const widths = entries.map(([label], colIdx) => {
const dataMax = dataRows.reduce(
(max, row) => Math.max(max, stripAnsi(row[colIdx]).length),
0
);
return Math.max(label.length, dataMax);
});
const header = headerRow.map((h, i) => import_chalk3.default.bold(h.padEnd(widths[i]))).join(" ");
const body = dataRows.map(
(row) => row.map((cell, i) => {
const pad = widths[i] - stripAnsi(cell).length;
return cell + " ".repeat(Math.max(0, pad));
}).join(" ")
);
return [header, ...body].join("\n");
}
function stripAnsi(str) {
return str.replace(/\x1b\[[0-9;]*m/g, "");
}
// src/commands/api/index.ts
async function api(client) {
const telemetryClient = new ApiTelemetryClient({
opts: { store: client.telemetryEventStore }
});
let parsedArgs;
const flagsSpec = getFlagsSpecification(apiCommand.options);
try {
parsedArgs = parseArguments(client.argv.slice(2), flagsSpec, {
permissive: true
});
} catch (err) {
printError(err);
return 1;
}
const { args, flags } = parsedArgs;
const needHelp = flags["--help"];
const firstArg = args[1];
if (firstArg === "ls" || firstArg === "list") {
const lsFlagsSpec = getFlagsSpecification(listSubcommand.options);
let lsParsedArgs;
try {
lsParsedArgs = parseArguments(client.argv.slice(2), lsFlagsSpec);
} catch (err) {
printError(err);
return 1;
}
const lsFlags = lsParsedArgs.flags;
if (lsFlags["--help"]) {
telemetryClient.trackCliFlagHelp("api", firstArg);
output_manager_default.print(
help(listSubcommand, {
parent: apiCommand,
columns: client.stderr.columns
})
);
return 2;
}
telemetryClient.trackCliSubcommandList();
if (lsFlags["--refresh"])
telemetryClient.trackCliFlagRefresh(true);
if (lsFlags["--format"])
telemetryClient.trackCliOptionFormat(lsFlags["--format"]);
return listEndpoints(
client,
lsFlags["--refresh"] ?? false,
lsFlags["--format"] ?? "table"
);
}
if (needHelp) {
telemetryClient.trackCliFlagHelp("api");
output_manager_default.print(help(apiCommand, { columns: client.stderr.columns }));
return 2;
}
if (flags["--dangerously-skip-permissions"]) {
client.dangerouslySkipPermissions = true;
}
let endpoint;
let selectedMethod;
let selectedBodyFields = [];
if (!firstArg) {
if (client.stdin.isTTY) {
const selected = await promptEndpointSelection(
client,
flags["--refresh"] ?? false
);
if (!selected) {
return 1;
}
endpoint = selected.finalUrl;
selectedMethod = selected.method;
selectedBodyFields = selected.bodyFields;
} else {
output_manager_default.error("Endpoint is required. Usage: vercel api <endpoint>");
return 1;
}
} else {
endpoint = firstArg;
}
if (endpoint && !endpoint.startsWith("/")) {
output_manager_default.error(
`Invalid arguments. Use an API path starting with /, or run \`${packageName} api\` interactively.`
);
return 1;
}
try {
const resolvedUrl = new URL(endpoint, API_BASE_URL);
if (resolvedUrl.origin !== API_BASE_URL) {
output_manager_default.error(
"Invalid endpoint: must be a Vercel API path, not an external URL"
);
return 1;
}
} catch {
output_manager_default.error("Invalid endpoint URL format");
return 1;
}
const finalFlags = { ...flags };
if (selectedMethod && !flags["--method"]) {
finalFlags["--method"] = selectedMethod;
}
if (selectedBodyFields.length > 0) {
const existingFields = finalFlags["--field"] || [];
finalFlags["--field"] = [...existingFields, ...selectedBodyFields];
}
let requestConfig;
try {
requestConfig = await buildRequest(endpoint, finalFlags);
} catch (err) {
printError(err);
return 1;
}
telemetryClient.trackCliArgumentEndpoint(requestConfig.url);
telemetryClient.trackCliArgumentOperationId(void 0);
telemetryClient.trackCliOptionMethod(flags["--method"]);
telemetryClient.trackCliOptionHeader(flags["--header"]);
telemetryClient.trackCliOptionInput(flags["--input"]);
if (flags["--paginate"])
telemetryClient.trackCliFlagPaginate(true);
if (flags["--include"])
telemetryClient.trackCliFlagInclude(true);
if (flags["--silent"])
telemetryClient.trackCliFlagSilent(true);
if (flags["--verbose"])
telemetryClient.trackCliFlagVerbose(true);
if (flags["--raw"])
telemetryClient.trackCliFlagRaw(true);
if (flags["--refresh"])
telemetryClient.trackCliFlagRefresh(true);
if (flags["--generate"])
telemetryClient.trackCliOptionGenerate(flags["--generate"]);
if (flags["--dangerously-skip-permissions"])
telemetryClient.trackCliFlagDangerouslySkipPermissions(true);
if (flags["--generate"] === "curl") {
const curlCmd = generateCurlCommand(
requestConfig,
"https://api.vercel.com"
);
output_manager_default.log("");
output_manager_default.log("Replace <TOKEN> with your auth token:");
output_manager_default.log("");
client.stdout.write(curlCmd + "\n");
return 0;
}
return executeApiRequest(client, requestConfig, finalFlags);
}
async function printOperationHelpForTagCommand(flags, tag, operationId) {
const openApi = new OpenApiCache();
const loaded = await openApi.loadWithSpinner(flags["--refresh"] ?? false);
if (!loaded) {
output_manager_default.error("Could not load API specification");
return 1;
}
const allEndpoints = openApi.getEndpoints();
const resolved = resolveEndpointByTagAndOperationId(
allEndpoints,
tag,
operationId
);
if (!resolved.ok) {
printTagOperationResolveError(resolved, allEndpoints);
return 1;
}
const ep = resolved.endpoint;
const bodyFields = openApi.getBodyFields(ep);
printOperationHelpDetails(ep, bodyFields, tag);
return 2;
}
function printOperationHelpDetails(ep, bodyFields, tag) {
const lines = [];
lines.push("");
lines.push(import_chalk4.default.bold(ep.operationId || "(operation)"));
const blurb = ep.summary?.trim() || ep.description?.trim();
if (blurb) {
lines.push("");
lines.push(import_chalk4.default.dim(blurb));
}
lines.push("");
lines.push(import_chalk4.default.bold("Options"));
lines.push("");
const pathParams = ep.parameters.filter((p) => p.in === "path");
const orderedParams = [
...pathParams,
...ep.parameters.filter((p) => p.in === "query"),
...ep.parameters.filter((p) => p.in === "header"),
...ep.parameters.filter((p) => p.in === "cookie")
];
for (const p of orderedParams) {
const globalNote = p.in === "query" && GLOBAL_CLI_QUERY_PARAMS.has(p.name) ? import_chalk4.default.dim(" (often set via --scope)") : "";
let reqLabel;
if (p.in === "query") {
reqLabel = p.required && !GLOBAL_CLI_QUERY_PARAMS.has(p.name) ? import_chalk4.default.red("required") : import_chalk4.default.dim("optional");
} else if (p.in === "path") {
reqLabel = p.required !== false ? import_chalk4.default.red("required") : import_chalk4.default.dim("optional");
} else if (p.in === "header") {
reqLabel = p.required ? import_chalk4.default.red("required") : import_chalk4.default.dim("optional");
} else {
reqLabel = p.required ? import_chalk4.default.red("required") : import_chalk4.default.dim("optional");
}
lines.push(
` ${import_chalk4.default.cyan(p.name)} ${reqLabel}${globalNote}${formatDescription(p.description)}`
);
}
for (const f of bodyFields) {
const req = f.required ? import_chalk4.default.red("required") : import_chalk4.default.dim("optional");
const typeHint = f.type ? ` ${formatTypeHint(f.type)}` : "";
lines.push(
` ${import_chalk4.default.cyan(f.name)} ${req}${typeHint}${formatDescription(f.description)}`
);
}
if (orderedParams.length === 0 && bodyFields.length === 0) {
lines.push(import_chalk4.default.dim(" (none)"));
}
lines.push("");
lines.push(import_chalk4.default.bold("Example"));
const exampleSuffix = pathParams.length > 0 ? ` ${pathParams.map((p) => `${p.name}=<value>`).join(" ")}` : "";
lines.push(
import_chalk4.default.dim(` ${packageName} api ${tag} ${ep.operationId}${exampleSuffix}`)
);
lines.push("");
output_manager_default.print(lines.join("\n"));
}
function printMissingOperationParamsHelp(endpoint, missing) {
output_manager_default.error(
`Missing required options for operation ${import_chalk4.default.bold(endpoint.operationId)}.`
);
output_manager_default.log(
import_chalk4.default.dim(
`Pass each as key=value after the operationId, or use -F key=value. Example: \`${packageName} api ${endpoint.tags[0] ?? "tag"} ${endpoint.operationId} idOrName=my-project\``
)
);
output_manager_default.log("");
output_manager_default.log(import_chalk4.default.bold("Options"));
output_manager_default.log("");
for (const p of missing.path) {
output_manager_default.log(` ${import_chalk4.default.cyan(p.name)}${formatDescription(p.description)}`);
}
for (const p of missing.header) {
output_manager_default.log(` ${import_chalk4.default.cyan(p.name)}${formatDescription(p.description)}`);
}
for (const p of missing.query) {
output_manager_default.log(` ${import_chalk4.default.cyan(p.name)}${formatDescription(p.description)}`);
}
for (const f of missing.body) {
const typeHint = f.type ? ` ${formatTypeHint(f.type)}` : "";
output_manager_default.log(
` ${import_chalk4.default.cyan(f.name)}${typeHint}${formatDescription(f.description)}`
);
}
output_manager_default.log("");
}
async function promptMissingParamsForTagOperation(client, endpoint, bodyFields, flags, positionalKeyValues) {
const pos = [...positionalKeyValues];
while (true) {
const parsed = await (async () => {
try {
return await parseOperationKeyValuePairs(
endpoint,
bodyFields,
flags,
pos
);
} catch (err) {
printError(err);
return null;
}
})();
if (parsed === null) {
return null;
}
const missing = getMissingRequiredOperationParams(
endpoint,
bodyFields,
parsed,
flags
);
if (missing.path.length === 0 && missing.query.length === 0 && missing.header.length === 0 && missing.body.length === 0) {
break;
}
for (const param of missing.path) {
const value = await client.input.text({
message: `Enter value for ${formatPathParam(param.name)}${formatDescription(param.description)}:`,
validate: createRequiredValidator(param.name)
});
pos.push(`${param.name}=${value}`);
}
for (const param of missing.header) {
const value = await client.input.text({
message: `Enter value for header ${import_chalk4.default.cyan(param.name)}${formatDescription(param.description)}:`,
validate: createRequiredValidator(param.name)
});
pos.push(`${param.name}=${value}`);
}
for (const param of missing.query) {
const value = await client.input.text({
message: `Enter value for ${import_chalk4.default.cyan(param.name)}${formatDescription(param.description)}:`,
validate: createRequiredValidator(param.name)
});
pos.push(`${param.name}=${value}`);
}
for (const field of missing.body) {
const value = await promptForBodyField(client, field, true);
pos.push(`${field.name}=${value}`);
}
}
return promptUnsetOptionalParamsForTagOperation(
client,
endpoint,
bodyFields,
flags,
pos
);
}
async function promptUnsetOptionalParamsForTagOperation(client, endpoint, bodyFields, flags, positionalKeyValues) {
const pos = [...positionalKeyValues];
const parsed = await (async () => {
try {
return await parseOperationKeyValuePairs(
endpoint,
bodyFields,
flags,
pos
);
} catch (err) {
printError(err);
return null;
}
})();
if (parsed === null) {
return null;
}
const unset = getUnsetOptionalOperationParams(
endpoint,
bodyFields,
parsed,
flags
);
if (unset.query.length === 0 && unset.header.length === 0 && unset.body.length === 0) {
return pos;
}
if (unset.query.length > 0) {
const selected = await client.input.checkbox({
message: "Select optional query parameters to include:",
pageSize: 20,
choices: unset.query.map((p) => ({
name: `${import_chalk4.default.cyan(p.name)}${GLOBAL_CLI_QUERY_PARAMS.has(p.name) ? import_chalk4.default.dim(" (team / scope; omit to use CLI default)") : ""}${formatDescription(p.description)}`,
value: p.name
}))
});
for (const paramName of selected) {
const param = unset.query.find((p) => p.name === paramName);
const value = await client.input.text({
message: `Enter value for ${import_chalk4.default.cyan(param.name)}${formatDescription(param.description)}:`,
validate: createRequiredValidator(param.name)
});
pos.push(`${param.name}=${value}`);
}
}
if (unset.header.length > 0) {
const selected = await client.input.checkbox({
message: "Select optional header parameters to include:",
pageSize: 20,
choices: unset.header.map((p) => ({
name: `${import_chalk4.default.cyan(p.name)}${formatDescription(p.description)}`,
value: p.name
}))
});
for (const paramName of selected) {
const param = unset.header.find((p) => p.name === paramName);
const value = await client.input.text({
message: `Enter value for header ${import_chalk4.default.cyan(param.name)}${formatDescription(param.description)}:`,
validate: createRequiredValidator(param.name)
});
pos.push(`${param.name}=${value}`);
}
}
if (unset.body.length > 0) {
const selected = await client.input.checkbox({
message: "Select optional body fields to include:",
pageSize: 20,
choices: unset.body.map((f) => ({
name: `${import_chalk4.default.cyan(f.name)}${f.type ? ` ${formatTypeHint(f.type)}` : ""}${formatDescription(f.description)}`,
value: f.name
}))
});
for (const fieldName of selected) {
const field = unset.body.find((f) => f.name === fieldName);
const value = await promptForBodyField(client, field, true);
pos.push(`${field.name}=${value}`);
}
}
return pos;
}
function printTagOperationResolveError(result, allEndpoints) {
if (result.reason === "no_tag") {
const tags = [...new Set(allEndpoints.flatMap((ep) => ep.tags || []))].sort();
const preview = tags.slice(0, 25).join(", ");
output_manager_default.error(
`No operations use tag "${result.tag}".${tags.length > 0 ? ` Example tags: ${preview}${tags.length > 25 ? ", \u2026" : ""}.` : ""} Run \`vercel api ls --format json\` to inspect tags.`
);
return;
}
if (result.reason === "no_operation") {
const ids = result.tagMatches.map((ep) => ep.operationId).filter(Boolean).sort();
output_manager_default.error(
`No operation matches "${result.operationHint}" under tag "${result.tag}".${ids.length > 0 ? ` Operations include: ${ids.slice(0, 20).join(", ")}${ids.length > 20 ? ", \u2026" : ""}.` : ""}`
);
return;
}
const lines = result.tagMatches.map(
(ep) => ` ${ep.operationId} ${ep.method} ${ep.path}`
);
output_manager_default.error(
`Multiple operations match "${result.operationHint}" under tag "${result.tag}":
${lines.join("\n")}`
);
}
async function executeApiRequest(client, requestConfig, flags, displayColumns, options) {
if (flags["--verbose"]) {
output_manager_default.debug(`Request: ${requestConfig.method} ${requestConfig.url}`);
if (Object.keys(requestConfig.headers).length > 0) {
output_manager_default.debug(`Headers: ${JSON.stringify(requestConfig.headers)}`);
}
if (requestConfig.body) {
output_manager_default.debug(
`Body: ${typeof requestConfig.body === "string" ? requestCo