UNPKG

vercel

Version:

The command-line interface for Vercel

473 lines (468 loc) • 15.2 kB
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 { AGENT_REASON, AGENT_STATUS } from "./chunk-E3NE4SKN.js"; import { getLinkedProject } from "./chunk-X775BOSL.js"; import { buildCommandWithYes, outputAgentError } from "./chunk-ULXHXZCZ.js"; import { getFlagsSpecification, getGlobalFlagsOnlyFromArgs, parseArguments, printError } from "./chunk-4GQQJY5Y.js"; import { getCommandName, getCommandNamePlain } from "./chunk-UGXBNJMO.js"; import { output_manager_default } from "./chunk-ZQKJVHXY.js"; import { require_source } from "./chunk-S7KYDPEM.js"; import { __toESM } from "./chunk-TZ2YI2VH.js"; // src/util/routes/types.ts var ROUTE_TYPE_LABELS = { rewrite: "Rewrite", redirect: "Redirect", set_status: "Set Status", transform: "Transform" }; function getRouteTypeLabel(rule) { if (!rule.routeType) return "-"; return ROUTE_TYPE_LABELS[rule.routeType] ?? "-"; } var SRC_SYNTAX_LABELS = { equals: "Exact Match", "path-to-regexp": "Pattern", regex: "Regex" }; function getSrcSyntaxLabel(rule) { const syntax = rule.srcSyntax ?? "regex"; return SRC_SYNTAX_LABELS[syntax]; } // src/commands/routes/shared.ts var import_chalk = __toESM(require_source(), 1); function withGlobalFlags(client, commandTemplate) { const flags = getGlobalFlagsOnlyFromArgs(client.argv.slice(2)); return getCommandNamePlain(`${commandTemplate} ${flags.join(" ")}`.trim()); } function shellQuoteRouteIdentifierForSuggestion(identifier) { if (!identifier.includes(" ")) { return identifier; } if (!identifier.includes("'")) { return `'${identifier}'`; } if (!identifier.includes('"')) { return `"${identifier}"`; } return `"${identifier.replace(/"/g, '\\"')}"`; } async function parseSubcommandArgs(argv, command, client) { let parsedArgs; const flagsSpecification = getFlagsSpecification(command.options); try { parsedArgs = parseArguments(argv, flagsSpecification); } catch (err) { if (client?.nonInteractive) { const rawMessage = err instanceof Error ? err.message : String(err); const flags = getGlobalFlagsOnlyFromArgs(client.argv.slice(2)); let message = rawMessage; let next = [ { command: getCommandNamePlain( `routes ${command.name} ${flags.join(" ")}`.trim() ), when: "fix flags and retry" } ]; const aiIdx = argv.indexOf("--ai"); const aiMissingValue = aiIdx !== -1 && (aiIdx === argv.length - 1 || argv[aiIdx + 1] !== void 0 && argv[aiIdx + 1].startsWith("-")); const isAiArgError = aiMissingValue || /option requires argument.*--ai|--ai.*requires argument/i.test( rawMessage ); if (isAiArgError && (command.name === "add" || command.name === "edit")) { message = command.name === "add" ? '--ai requires a description. Example: routes add --ai <description> --yes --non-interactive. Replace <description> with your text; use shell quotes if it contains spaces (e.g. --ai "Redirect /old to /new").' : "--ai requires a description. Example: routes edit <name-or-id> --ai <description> --yes --non-interactive. Replace placeholders; use shell quotes for <description> if it contains spaces."; next = [ { command: withGlobalFlags( client, command.name === "add" ? `routes add --ai <description> --yes` : `routes edit <name-or-id> --ai <description> --yes` ), when: "replace <description> with your text (quote it in the shell if it contains spaces), then run" }, { command: getCommandNamePlain( `routes ${command.name} --help ${flags.join(" ")}`.trim() ), when: "see all flags" } ]; } outputAgentError( client, { status: AGENT_STATUS.ERROR, reason: AGENT_REASON.INVALID_ARGUMENTS, message, next }, 1 ); return 1; } printError(err); return 1; } return parsedArgs; } async function ensureProjectLink(client) { const link = await getLinkedProject(client); if (link.status === "error") { return link.exitCode; } else if (link.status === "not_linked") { if (client.nonInteractive) { const flags = getGlobalFlagsOnlyFromArgs(client.argv.slice(2)); const cmd = getCommandNamePlain(`link ${flags.join(" ")}`.trim()); outputAgentError( client, { status: AGENT_STATUS.ERROR, reason: AGENT_REASON.NOT_LINKED, userActionRequired: true, message: "Your codebase is not linked to a Vercel project. Run link first, then retry routes commands.", next: [ { command: cmd, when: "to link this directory to a project" } ] }, 1 ); return 1; } output_manager_default.error( `Your codebase isn't linked to a project on Vercel. Run ${getCommandName("link")} to begin.` ); return 1; } client.config.currentTeam = link.org.type === "team" ? link.org.id : void 0; return link; } async function confirmAction(client, skipConfirmation, message, details) { if (skipConfirmation) return true; if (client.nonInteractive) { outputAgentError(client, { status: AGENT_STATUS.ERROR, reason: AGENT_REASON.CONFIRMATION_REQUIRED, message: `${message} Re-run with --yes to confirm.`, next: [ { command: buildCommandWithYes(client.argv), when: "re-run with --yes to confirm" } ] }); process.exit(1); return false; } if (details) { output_manager_default.print(` ${details} `); } return await client.input.confirm(message, false); } function validateRequiredArgs(args, required) { for (let i = 0; i < required.length; i++) { if (!args[i]) { return `Missing required argument: ${required[i]}`; } } return null; } function formatCondition(condition) { const parts = [import_chalk.default.gray(`[${condition.type}]`)]; if (condition.key) { parts.push(import_chalk.default.cyan(condition.key)); } if (condition.value !== void 0) { const formatted = typeof condition.value === "string" ? condition.value : JSON.stringify(condition.value); parts.push(condition.key ? `= ${formatted}` : formatted); } return parts.join(" "); } var TRANSFORM_TYPE_LABELS = { "request.headers": "Request Header", "request.query": "Request Query", "response.headers": "Response Header" }; var TRANSFORM_OP_LABELS = { set: "set", append: "append", delete: "delete" }; function formatTransform(transform, includeType = true) { const opLabel = TRANSFORM_OP_LABELS[transform.op] ?? transform.op; const key = typeof transform.target.key === "string" ? transform.target.key : JSON.stringify(transform.target.key); const parts = []; if (includeType) { const typeLabel = TRANSFORM_TYPE_LABELS[transform.type] ?? transform.type; parts.push(import_chalk.default.gray(`[${typeLabel}]`)); } parts.push(import_chalk.default.yellow(opLabel), import_chalk.default.cyan(key)); if (transform.args !== void 0 && transform.op !== "delete") { const argsStr = Array.isArray(transform.args) ? transform.args.join(", ") : transform.args; parts.push(`= ${argsStr}`); } return parts.join(" "); } function parsePosition(position) { if (position === "start") { return { placement: "start" }; } if (position === "end") { return { placement: "end" }; } if (position.startsWith("after:")) { const referenceId = position.slice(6); if (!referenceId) { throw new Error('Position "after:" requires a route ID'); } return { placement: "after", referenceId }; } if (position.startsWith("before:")) { const referenceId = position.slice(7); if (!referenceId) { throw new Error('Position "before:" requires a route ID'); } return { placement: "before", referenceId }; } throw new Error( `Invalid position: "${position}". Use: start, end, after:<id>, or before:<id>` ); } async function offerAutoPromote(client, projectId, version, hadExistingStagingVersion, opts) { const { default: updateRouteVersion } = await import("./update-route-version-E3V47KNI.js"); const { default: stamp } = await import("./stamp-ENIXDCRO.js"); output_manager_default.print( ` ${import_chalk.default.gray(`This change is staged. Run ${import_chalk.default.cyan(getCommandName("routes publish"))} to make it live, or ${import_chalk.default.cyan(getCommandName("routes discard-staging"))} to undo.`)} ` ); if (!hadExistingStagingVersion && !opts.skipPrompts) { output_manager_default.print("\n"); const shouldPromote = await client.input.confirm( "This is the only staged change. Promote to production now?", false ); if (shouldPromote) { const promoteStamp = stamp(); output_manager_default.spinner("Promoting to production"); try { await updateRouteVersion(client, projectId, version.id, "promote", { teamId: opts.teamId }); output_manager_default.log( `${import_chalk.default.cyan("Promoted")} to production ${import_chalk.default.gray(promoteStamp())}` ); } catch (e) { const err = e; output_manager_default.error( `Failed to promote to production: ${err.message || "Unknown error"}` ); } } } else if (hadExistingStagingVersion) { output_manager_default.warn( `There are other staged changes. Review with ${import_chalk.default.cyan(getCommandName("routes list --diff"))} before promoting.` ); } } function printDiffSummary(routes, maxDisplay = 10) { const displayRoutes = routes.slice(0, maxDisplay); for (const route of displayRoutes) { const symbol = getDiffSymbol(route); const label = getDiffLabel(route); const routeType = getRouteTypeLabel(route); output_manager_default.print( ` ${symbol} ${route.name}${routeType !== "-" ? ` ${import_chalk.default.gray(`(${routeType})`)}` : ""} ${import_chalk.default.gray(`- ${label}`)} ` ); } if (routes.length > maxDisplay) { output_manager_default.print( import_chalk.default.gray(` ... and ${routes.length - maxDisplay} more changes `) ); } } function getDiffSymbol(route) { if (route.action === "+") return import_chalk.default.green("+"); if (route.action === "-") return import_chalk.default.red("-"); return import_chalk.default.yellow("~"); } function getDiffLabel(route) { if (route.action === "+") return "Added"; if (route.action === "-") return "Deleted"; const isReordered = route.previousIndex !== void 0 && route.newIndex !== void 0; if (isReordered) { return `Reordered (${route.previousIndex + 1} \u2192 ${route.newIndex + 1})`; } if (route.previousEnabled !== void 0) { return route.enabled === false ? "Disabled" : "Enabled"; } return "Modified"; } async function resolveRoute(client, routes, identifier) { const byId = routes.find((r) => r.id === identifier); if (byId) return byId; const query = identifier.toLowerCase(); const matches = routes.filter( (r) => r.name.toLowerCase() === query || r.name.toLowerCase().includes(query) || r.id.toLowerCase().includes(query) ); if (matches.length === 0) { return null; } if (matches.length === 1) { return matches[0]; } if (client.nonInteractive) { outputAgentError( client, { status: AGENT_STATUS.ERROR, reason: AGENT_REASON.AMBIGUOUS_ROUTE, message: `Multiple routes match "${identifier}" (${matches.length} matches). Pass an exact route name or ID.`, next: [ { command: withGlobalFlags(client, "routes list"), when: "list routes to get exact id" } ] }, 1 ); return null; } const selectedId = await client.input.select({ message: `Multiple routes match "${identifier}". Select one:`, choices: matches.map((route) => ({ value: route.id, name: `${route.name} ${import_chalk.default.gray(`(${route.route.src})`)}` })) }); return matches.find((r) => r.id === selectedId) ?? null; } async function resolveRoutes(client, routes, identifiers) { const resolved = /* @__PURE__ */ new Map(); for (const identifier of identifiers) { const route = await resolveRoute(client, routes, identifier); if (!route) { if (client.nonInteractive) { outputAgentError( client, { status: AGENT_STATUS.ERROR, reason: AGENT_REASON.NOT_FOUND, message: `No route found matching "${identifier}".`, next: [ { command: withGlobalFlags(client, "routes list"), when: "list routes" } ] }, 1 ); return null; } output_manager_default.error( `No route found matching "${identifier}". Run ${import_chalk.default.cyan( getCommandName("routes list") )} to see all routes.` ); return null; } resolved.set(route.id, route); } return Array.from(resolved.values()); } function findVersionById(versions, identifier) { const matchingVersions = versions.filter((v) => v.id.startsWith(identifier)); if (matchingVersions.length === 0) { return { error: `Version "${identifier}" not found. Run ${import_chalk.default.cyan( getCommandName("routes list-versions") )} to see available versions.` }; } if (matchingVersions.length > 1) { return { error: `Multiple versions match "${identifier}". Please provide a more specific ID: ${matchingVersions.map((v) => ` ${v.id}`).join("\n")}` }; } return { version: matchingVersions[0] }; } // src/util/routes/get-routes.ts async function getRoutes(client, projectId, options = {}) { const { teamId, search, filter, versionId, diff } = options; const query = new URLSearchParams(); if (teamId) query.set("teamId", teamId); if (search) query.set("q", search); if (filter) query.set("filter", filter); if (versionId) query.set("versionId", versionId); if (diff) query.set("diff", "true"); const queryString = query.toString(); const url = `/v1/projects/${projectId}/routes${queryString ? `?${queryString}` : ""}`; const response = await client.fetch(url); return response; } export { getRouteTypeLabel, getSrcSyntaxLabel, withGlobalFlags, shellQuoteRouteIdentifierForSuggestion, parseSubcommandArgs, ensureProjectLink, confirmAction, validateRequiredArgs, formatCondition, TRANSFORM_TYPE_LABELS, formatTransform, parsePosition, offerAutoPromote, printDiffSummary, resolveRoute, resolveRoutes, findVersionById, getRoutes };