vercel
Version:
The command-line interface for Vercel
678 lines (673 loc) • 20.9 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 {
getEnvTargetPlaceholder
} from "./chunk-X775BOSL.js";
import {
yesOption
} 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 {
__toESM
} from "./chunk-TZ2YI2VH.js";
// src/commands/build/command.ts
var buildCommand = {
name: "build",
aliases: [],
description: "Build the project.",
arguments: [],
options: [
{
name: "prod",
description: "Build a production deployment",
shorthand: null,
type: Boolean,
deprecated: false
},
{
name: "target",
shorthand: null,
type: String,
argument: "TARGET",
deprecated: false,
description: "Specify the target environment"
},
{
name: "output",
description: "Directory where built assets will be written to",
shorthand: null,
argument: "DIR",
type: String,
deprecated: false
},
{
...yesOption,
description: "Skip the confirmation prompt about pulling environment variables and project settings when not found locally"
},
{
name: "standalone",
description: "Create a standalone build with all dependencies inlined into function output folders",
shorthand: null,
type: Boolean,
deprecated: false
},
{
name: "id",
description: "Deployment ID to pull environment variables from (e.g. dpl_xxx)",
shorthand: null,
type: String,
argument: "ID",
deprecated: false
}
],
examples: [
{
name: "Build the project",
value: `${packageName} build`
},
{
name: "Build the project in a specific directory",
value: `${packageName} build --cwd ./path-to-project`
},
{
name: "Build with deployment-scoped environment variables",
value: `${packageName} build --id dpl_xxx`
}
]
};
// src/util/agent/auto-install-agentic.ts
var import_chalk = __toESM(require_source(), 1);
import { readFile, writeFile } from "fs/promises";
import { access } from "fs/promises";
import { join } from "path";
import { homedir } from "os";
import { spawn } from "child_process";
import { KNOWN_AGENTS } from "@vercel/detect-agent";
import { z } from "zod";
var PREFS_FILE = "agent-preferences.json";
var CLAUDE_LEGACY_PLUGIN_ID = "vercel-plugin@vercel";
var CLAUDE_OFFICIAL_PLUGIN_ID = "vercel@claude-plugins-official";
var VERCEL_PLUGIN_VERSION_URL = "https://raw.githubusercontent.com/vercel/vercel-plugin/main/.claude-plugin/plugin.json";
var AGENT_TO_TARGET = {
[KNOWN_AGENTS.CLAUDE]: "claude-code",
[KNOWN_AGENTS.COWORK]: "claude-code"
};
function getPluginTargetForAgent(agentName) {
if (!agentName) {
return void 0;
}
if (agentName === KNOWN_AGENTS.CLAUDE || agentName.startsWith("claude-code") || agentName === KNOWN_AGENTS.COWORK) {
return "claude-code";
}
return AGENT_TO_TARGET[agentName];
}
var promptedAtSchema = z.codec(
z.union([z.iso.date(), z.iso.datetime()]),
z.date(),
{
decode: (value) => new Date(value),
encode: (value) => value.toISOString()
}
);
var agentPreferencesSchema = z.object({
pluginDeclined: z.boolean().optional(),
lastPromptedAt: promptedAtSchema.optional()
});
async function fileExists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
async function readPrefs(client) {
return await client.maybeReadConfig(PREFS_FILE, agentPreferencesSchema) ?? {};
}
async function writePrefs(client, prefs) {
try {
await client.writeConfig(PREFS_FILE, agentPreferencesSchema, prefs);
} catch {
}
}
async function getPluginTargets(agentName) {
const targetForAgent = getPluginTargetForAgent(agentName);
if (targetForAgent) {
return [targetForAgent];
}
if (agentName) {
return [];
}
const home = homedir();
const targets = [];
if (await fileExists(join(home, ".claude"))) {
targets.push("claude-code");
}
return targets;
}
async function readClaudeInstalledPluginsFromRegistry() {
try {
const raw = await readFile(
getClaudeInstalledPluginsRegistryPath(),
"utf-8"
);
const data = JSON.parse(raw);
const plugins = data?.plugins ?? {};
const entries = [];
for (const [id, installs] of Object.entries(plugins)) {
if (!Array.isArray(installs))
continue;
for (const install of installs) {
if (!install || typeof install !== "object")
continue;
entries.push({
id,
...install,
enabled: true
});
}
}
return entries;
} catch {
return [];
}
}
function getClaudeInstalledPluginsRegistryPath() {
return join(homedir(), ".claude", "plugins", "installed_plugins.json");
}
async function markStaleClaudePluginInstalls(plugins) {
return Promise.all(
plugins.map(async (plugin) => {
if (plugin.installPath && !await fileExists(plugin.installPath)) {
return { ...plugin, stale: true };
}
return plugin;
})
);
}
async function removeClaudePluginFromRegistry(pluginId) {
try {
const registryPath = getClaudeInstalledPluginsRegistryPath();
const raw = await readFile(registryPath, "utf-8");
const data = JSON.parse(raw);
if (!data.plugins || !(pluginId in data.plugins)) {
return false;
}
delete data.plugins[pluginId];
await writeFile(
registryPath,
`${JSON.stringify(data, null, 2)}
`,
"utf-8"
);
return true;
} catch (err) {
output_manager_default.debug(`Failed to remove Claude plugin registry entry: ${err}`);
return false;
}
}
async function isPluginInstalledForTarget(target) {
if (target === "claude-code") {
const status = await getClaudePluginStatus();
return status.state === "official-only";
}
return false;
}
async function confirm(client, message) {
if (!client.stdin.isTTY) {
return false;
}
return client.input.confirm(message, true);
}
function isSameDay(left, right) {
return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth() && left.getDate() === right.getDate();
}
function wasPromptedToday(prefs) {
return prefs.lastPromptedAt ? isSameDay(prefs.lastPromptedAt, /* @__PURE__ */ new Date()) : false;
}
async function markPromptedToday(client, prefs) {
prefs.lastPromptedAt = /* @__PURE__ */ new Date();
await writePrefs(client, prefs);
}
async function runCommand(command, args) {
return await new Promise((resolve) => {
const child = spawn(command, args, { stdio: "pipe" });
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
stdout += chunk.toString();
});
child.stderr.on("data", (chunk) => {
stderr += chunk.toString();
});
child.on("close", (code) => {
resolve({ exitCode: code ?? 1, stdout, stderr });
});
child.on("error", (err) => {
resolve({ exitCode: 1, stdout, stderr: `${stderr}${String(err)}` });
});
});
}
async function getClaudeInstalledPlugins() {
const result = await runCommand("claude", ["plugins", "list", "--json"]);
if (result.exitCode === 0) {
try {
const parsed = JSON.parse(result.stdout);
if (Array.isArray(parsed)) {
return markStaleClaudePluginInstalls(parsed);
}
} catch (err) {
output_manager_default.debug(`Failed to parse Claude plugin list JSON: ${err}`);
}
} else if (result.stderr.trim().length > 0) {
output_manager_default.debug(
`Failed to run 'claude plugins list --json': ${result.stderr}`
);
}
return markStaleClaudePluginInstalls(
await readClaudeInstalledPluginsFromRegistry()
);
}
async function fetchLatestVercelPluginVersion() {
try {
const response = await fetch(VERCEL_PLUGIN_VERSION_URL);
if (!response.ok) {
output_manager_default.debug(
`Failed to fetch latest Vercel plugin version: ${response.status}`
);
return void 0;
}
const manifest = await response.json();
return typeof manifest.version === "string" ? manifest.version : void 0;
} catch (err) {
output_manager_default.debug(`Failed to fetch latest Vercel plugin version: ${err}`);
return void 0;
}
}
function comparePluginVersions(a, b) {
if (!a && !b)
return 0;
if (!a)
return -1;
if (!b)
return 1;
const parse = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
const left = parse(a);
const right = parse(b);
const maxLength = Math.max(left.length, right.length);
for (let i = 0; i < maxLength; i++) {
const l = left[i] ?? 0;
const r = right[i] ?? 0;
if (l > r)
return 1;
if (l < r)
return -1;
}
return 0;
}
function buildClaudePluginStatus(installedPlugins, latestVersion) {
const legacy = installedPlugins.find(
(plugin) => plugin.id === CLAUDE_LEGACY_PLUGIN_ID
);
const official = installedPlugins.find(
(plugin) => plugin.id === CLAUDE_OFFICIAL_PLUGIN_ID
);
let state = "none";
if (legacy && official)
state = "both";
else if (legacy)
state = "legacy-only";
else if (official)
state = "official-only";
return {
state,
legacy,
official,
latestVersion
};
}
function buildClaudePluginMigrationPlan(status) {
const plan = {
installOfficial: false,
updateOfficial: false,
removeLegacy: false,
removeLegacyMarketplace: false
};
switch (status.state) {
case "none":
plan.installOfficial = true;
break;
case "legacy-only":
plan.installOfficial = true;
plan.removeLegacy = true;
plan.removeLegacyMarketplace = true;
break;
case "both":
plan.removeLegacy = true;
plan.removeLegacyMarketplace = true;
break;
case "official-only":
break;
}
if (status.official?.version && status.latestVersion && comparePluginVersions(status.official.version, status.latestVersion) < 0) {
plan.updateOfficial = true;
}
return plan;
}
function hasClaudeMigrationActions(plan) {
return plan.installOfficial || plan.updateOfficial || plan.removeLegacy || plan.removeLegacyMarketplace;
}
function buildClaudePromptCopy(status, plan) {
if (plan.installOfficial && status.state === "none") {
return {
message: "",
confirm: "Working with Vercel is easier with the Vercel Plugin for Claude Code. Would you like to install it?"
};
}
if (plan.installOfficial && status.state === "legacy-only") {
return {
message: "",
confirm: "Working with Vercel is easier with the latest Vercel Plugin for Claude Code. Would you like to update it?"
};
}
if (status.state === "both" && plan.removeLegacy) {
return {
message: "",
confirm: "Working with Vercel is easier with the latest Vercel Plugin for Claude Code. Would you like to update it?"
};
}
if (plan.updateOfficial) {
const fromVersion = status.official?.version ?? "your current version";
const toVersion = status.latestVersion ?? "the latest version";
return {
message: "",
confirm: `Working with Vercel is easier with the latest Vercel Plugin for Claude Code. Would you like to update from ${fromVersion} to ${toVersion}?`
};
}
return {
message: "The Vercel plugin needs attention in Claude Code before your agent harness is fully up to date.",
confirm: "Apply the Vercel plugin changes for Claude Code?"
};
}
async function runClaudeCommand(spinnerMessage, successMessage, failureMessage, args, options) {
output_manager_default.spinner(spinnerMessage);
const result = await runCommand("claude", args);
output_manager_default.stopSpinner();
if (result.exitCode === 0) {
if (!options?.quietSuccess) {
output_manager_default.success(successMessage);
}
return true;
}
output_manager_default.warn(failureMessage);
output_manager_default.debug(
`Claude command failed: claude ${args.join(" ")}
${result.stderr || result.stdout}`
);
return false;
}
async function removeStaleLegacyClaudePlugin(removeMarketplace) {
output_manager_default.spinner("Removing the stale legacy Vercel Claude plugin...");
const removedRegistryEntry = await removeClaudePluginFromRegistry(
CLAUDE_LEGACY_PLUGIN_ID
);
output_manager_default.stopSpinner();
if (!removedRegistryEntry) {
output_manager_default.warn(
"Could not remove the stale legacy Vercel Claude plugin registry entry."
);
return false;
}
output_manager_default.success("Removed the stale legacy Vercel Claude plugin");
if (removeMarketplace) {
const removedMarketplace = await runClaudeCommand(
"Removing the legacy Vercel marketplace...",
"Removed the legacy Vercel marketplace",
"Removed the stale legacy Vercel plugin, but could not remove the legacy marketplace.",
["plugins", "marketplace", "remove", "vercel"],
{ quietSuccess: true }
);
if (!removedMarketplace) {
output_manager_default.log("Cleanup command: claude plugins marketplace remove vercel");
}
}
return true;
}
async function runClaudeMigration(plan) {
let removedStaleLegacy = false;
if (plan.removeLegacy) {
const statusBeforeInstall = await getClaudePluginStatus();
if (statusBeforeInstall.legacy?.stale) {
removedStaleLegacy = await removeStaleLegacyClaudePlugin(
plan.removeLegacyMarketplace
);
}
}
if (plan.installOfficial) {
const installed = await runClaudeCommand(
"Installing the official Vercel Claude plugin...",
"Updated the Vercel plugin",
"Failed to install the official Vercel Claude plugin.",
["plugins", "install", CLAUDE_OFFICIAL_PLUGIN_ID]
);
if (!installed) {
return;
}
} else if (plan.updateOfficial) {
await runClaudeCommand(
"Updating the official Vercel Claude plugin...",
"Updated the Vercel plugin",
"Failed to update the official Vercel Claude plugin.",
["plugins", "update", CLAUDE_OFFICIAL_PLUGIN_ID]
);
}
const statusAfterInstall = await getClaudePluginStatus();
if (!statusAfterInstall.official) {
output_manager_default.warn(
"Skipping Claude cleanup because the official Vercel plugin is not installed."
);
return;
}
if (plan.removeLegacy && statusAfterInstall.legacy) {
const removedLegacy = await runClaudeCommand(
"Removing the legacy Vercel Claude plugin...",
"Removed the legacy Vercel Claude plugin",
"Installed the official Vercel Claude plugin, but could not remove the legacy install.",
["plugins", "uninstall", CLAUDE_LEGACY_PLUGIN_ID],
{ quietSuccess: true }
);
if (!removedLegacy) {
output_manager_default.log(
`Cleanup command: claude plugins uninstall ${CLAUDE_LEGACY_PLUGIN_ID}`
);
return;
}
}
if (plan.removeLegacyMarketplace && !removedStaleLegacy) {
const finalStatus = await getClaudePluginStatus();
if (!finalStatus.legacy) {
const removedMarketplace = await runClaudeCommand(
"Removing the legacy Vercel marketplace...",
"Removed the legacy Vercel marketplace",
"Removed the legacy Vercel plugin, but could not remove the legacy marketplace.",
["plugins", "marketplace", "remove", "vercel"],
{ quietSuccess: true }
);
if (!removedMarketplace) {
output_manager_default.log("Cleanup command: claude plugins marketplace remove vercel");
}
}
}
}
async function getClaudePluginStatus() {
const [installedPlugins, latestVersion] = await Promise.all([
getClaudeInstalledPlugins(),
fetchLatestVercelPluginVersion()
]);
return buildClaudePluginStatus(installedPlugins, latestVersion);
}
async function applyPluginActions(targets, claudePlan) {
for (const target of targets) {
if (target === "claude-code" && claudePlan) {
await runClaudeMigration(claudePlan);
} else {
output_manager_default.debug(`Skipping unsupported plugin target: ${target}`);
}
}
}
async function autoInstallVercelPlugin(client, options) {
try {
const prefs = await readPrefs(client);
const applyMode = options?.mode === "apply";
if (!prefs.pluginDeclined || applyMode) {
const targets = await getPluginTargets(client.agentName);
const uninstalledTargets = [];
const claudeStatus = targets.includes("claude-code") ? await getClaudePluginStatus() : void 0;
const claudePlan = claudeStatus ? buildClaudePluginMigrationPlan(claudeStatus) : void 0;
for (const target of targets) {
if (target === "claude-code") {
if (claudePlan && hasClaudeMigrationActions(claudePlan)) {
uninstalledTargets.push(target);
}
continue;
}
if (!await isPluginInstalledForTarget(target)) {
uninstalledTargets.push(target);
}
}
if (uninstalledTargets.length > 0) {
if (!applyMode && wasPromptedToday(prefs)) {
return;
}
if (applyMode) {
prefs.pluginDeclined = false;
await writePrefs(client, prefs);
await applyPluginActions(uninstalledTargets, claudePlan);
return;
}
const promptMessages = [];
let confirmMessage = "Install the Vercel plugin?";
if (uninstalledTargets.includes("claude-code") && claudeStatus && claudePlan) {
const claudePrompt = buildClaudePromptCopy(claudeStatus, claudePlan);
promptMessages.push(claudePrompt.message);
confirmMessage = claudePrompt.confirm;
}
const promptMessage = promptMessages.join(" ").trim();
if (promptMessage) {
output_manager_default.log(promptMessage);
}
const accepted = await confirm(client, confirmMessage);
await markPromptedToday(client, prefs);
if (accepted) {
prefs.pluginDeclined = false;
await writePrefs(client, prefs);
await applyPluginActions(uninstalledTargets, claudePlan);
} else {
prefs.pluginDeclined = true;
await writePrefs(client, prefs);
}
}
}
} catch (err) {
output_manager_default.debug(`Auto-install agent tooling failed: ${err}`);
}
}
async function showPluginTipIfNeeded(client) {
try {
const prefs = await readPrefs(client);
if (prefs.pluginDeclined)
return;
const targets = await getPluginTargets();
for (const target of targets) {
if (!await isPluginInstalledForTarget(target)) {
output_manager_default.log(
import_chalk.default.dim(
"Tip: Run `npx plugins add vercel/vercel-plugin` to enhance your agent experience"
)
);
return;
}
}
} catch {
}
}
// src/commands/pull/command.ts
var pullCommand = {
name: "pull",
aliases: [],
description: "Pull latest environment variables and project settings from Vercel. ",
arguments: [
{
name: "project-path",
required: false
}
],
options: [
{
name: "environment",
description: "Deployment environment [development]",
argument: "TARGET",
shorthand: null,
type: String,
deprecated: false
},
{
name: "git-branch",
description: "Specify the Git branch to pull specific Environment Variables for",
argument: "NAME",
shorthand: null,
type: String,
deprecated: false
},
{
name: "prod",
shorthand: null,
type: Boolean,
deprecated: false
},
{
...yesOption,
description: "Skip questions when setting up new project using default scope and settings"
}
],
examples: [
{
name: "Pull the latest Environment Variables and Project Settings from the cloud",
value: `${packageName} pull`
},
{
name: "Pull the latest Environment Variables and Project Settings from the cloud targeting a directory",
value: `${packageName} pull ./path-to-project`
},
{
name: "Pull for a specific environment",
value: `${packageName} pull --environment=${getEnvTargetPlaceholder()}`
},
{
name: "Pull for a preview feature branch",
value: `${packageName} pull --environment=preview --git-branch=feature-branch`
},
{
name: "If you want to download environment variables to a specific file, use `vercel env pull` instead",
value: `${packageName} env pull`
}
]
};
export {
buildCommand,
pullCommand,
autoInstallVercelPlugin,
showPluginTipIfNeeded
};