@ruan-cat/vercel-deploy-tool 0.12.2 → 1.1.0

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.
@@ -0,0 +1,50 @@
1
+ import { Command } from "commander";
2
+ import { consola } from "consola";
3
+ import { config as dotenvxConfig } from "@dotenvx/dotenvx";
4
+ import { loadConfig } from "../config/loader";
5
+ import { executeDeploymentWorkflow } from "../core/tasks";
6
+
7
+ /**
8
+ * 创建 deploy 命令
9
+ * @description
10
+ * 部署项目到 Vercel
11
+ *
12
+ * @example
13
+ * ```bash
14
+ * vercel-deploy-tool deploy
15
+ * vdt deploy
16
+ * ```
17
+ */
18
+ export function createDeployCommand(): Command {
19
+ const command = new Command("deploy");
20
+
21
+ command
22
+ .description("部署项目到 Vercel")
23
+ .option("--env-path <path>", "指定 dotenv 文件路径,用于覆盖默认环境变量")
24
+ .action(async (options) => {
25
+ try {
26
+ // 允许部署时显式指定 env 文件
27
+ if (options?.envPath) {
28
+ process.env.VERCEL_DEPLOY_TOOL_ENV_PATH = options.envPath;
29
+ dotenvxConfig({ path: options.envPath });
30
+ consola.info(`已从 --env-path 加载环境变量: ${options.envPath}`);
31
+ }
32
+
33
+ consola.start("开始加载配置...");
34
+
35
+ const config = await loadConfig();
36
+
37
+ consola.start("开始执行部署工作流...");
38
+
39
+ await executeDeploymentWorkflow(config);
40
+
41
+ consola.success("部署完成!");
42
+ } catch (error) {
43
+ consola.error("部署失败:");
44
+ consola.error(error);
45
+ process.exit(1);
46
+ }
47
+ });
48
+
49
+ return command;
50
+ }
@@ -0,0 +1,92 @@
1
+ import { Command } from "commander";
2
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { consola } from "consola";
6
+
7
+ // 获取当前模块的目录
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ /**
12
+ * 创建 init 命令
13
+ * @description
14
+ * 初始化配置文件,生成 vercel-deploy-tool.config.ts 模板
15
+ *
16
+ * @example
17
+ * ```bash
18
+ * vercel-deploy-tool init
19
+ * vercel-deploy-tool init --force
20
+ * ```
21
+ */
22
+ export function createInitCommand(): Command {
23
+ const command = new Command("init");
24
+
25
+ command
26
+ .description("初始化配置文件")
27
+ .option("-f, --force", "强制覆盖已存在的文件")
28
+ .action((options) => {
29
+ const cwd = process.cwd();
30
+ const configFile = "vercel-deploy-tool.config.ts";
31
+ const targetPath = join(cwd, configFile);
32
+
33
+ // 检查文件是否已存在
34
+ if (existsSync(targetPath) && !options.force) {
35
+ consola.warn(`配置文件已存在: ${configFile}`);
36
+ consola.info("使用 --force 选项可以覆盖");
37
+ return;
38
+ }
39
+
40
+ // 读取模板文件
41
+ const templatePath = join(__dirname, "..", "templates", configFile);
42
+
43
+ if (!existsSync(templatePath)) {
44
+ consola.error(`模板文件不存在: ${templatePath}`);
45
+ consola.error("请确保 @ruan-cat/vercel-deploy-tool 包已正确安装");
46
+ process.exit(1);
47
+ }
48
+
49
+ const content = readFileSync(templatePath, "utf-8");
50
+
51
+ // 写入配置文件
52
+ writeFileSync(targetPath, content, "utf-8");
53
+ consola.success(`已创建配置文件: ${configFile}`);
54
+
55
+ // 更新 package.json
56
+ const pkgPath = join(cwd, "package.json");
57
+ if (existsSync(pkgPath)) {
58
+ try {
59
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
60
+ if (!pkg.scripts) pkg.scripts = {};
61
+
62
+ // 添加 deploy-vercel 脚本
63
+ pkg.scripts["deploy-vercel"] = "vercel-deploy-tool deploy";
64
+
65
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
66
+ consola.success('已添加脚本: "deploy-vercel"');
67
+ } catch (error) {
68
+ consola.warn("更新 package.json 失败:", error);
69
+ }
70
+ }
71
+
72
+ // 显示后续操作提示
73
+ consola.box(`🎉 初始化完成!
74
+
75
+ 创建的文件:
76
+ • ${configFile} - Vercel 部署配置文件
77
+
78
+ 添加的脚本:
79
+ • deploy-vercel: vercel-deploy-tool deploy
80
+
81
+ 下一步:
82
+ 1. 编辑 ${configFile} 填写你的配置
83
+ 2. 确保环境变量已设置:
84
+ - VERCEL_TOKEN
85
+ - VERCEL_ORG_ID
86
+ - VERCEL_PROJECT_ID
87
+ 3. 运行部署:
88
+ pnpm run deploy-vercel`);
89
+ });
90
+
91
+ return command;
92
+ }
@@ -0,0 +1,20 @@
1
+ import type { VercelDeployConfig } from "./schema";
2
+
3
+ /**
4
+ * 定义配置的辅助函数
5
+ * @description
6
+ * 提供类型提示和智能补全
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { defineConfig } from "@ruan-cat/vercel-deploy-tool";
11
+ *
12
+ * export default defineConfig({
13
+ * vercelProjectName: "my-project",
14
+ * // ...
15
+ * });
16
+ * ```
17
+ */
18
+ export function defineConfig(config: VercelDeployConfig): VercelDeployConfig {
19
+ return config;
20
+ }
@@ -0,0 +1,95 @@
1
+ import { loadConfig as c12LoadConfig } from "c12";
2
+ import { consola } from "consola";
3
+ import { isUndefined } from "lodash-es";
4
+ import { config as dotenvxConfig } from "@dotenvx/dotenvx";
5
+ import { printFormat } from "@ruan-cat/utils";
6
+ import type { VercelDeployConfig } from "./schema";
7
+
8
+ /** 配置文件的文件名称 */
9
+ export const CONFIG_NAME = "vercel-deploy-tool";
10
+
11
+ /** 默认配置 */
12
+ const DEFAULT_CONFIG: VercelDeployConfig = {
13
+ vercelProjectName: "",
14
+ vercelToken: "",
15
+ vercelOrgId: "",
16
+ vercelProjectId: "",
17
+ deployTargets: [],
18
+ };
19
+
20
+ /**
21
+ * 异步加载配置(工厂函数模式)
22
+ * @description
23
+ * 从约定俗成的配置处,获得用户配置文件。不会在模块导入时自动执行,避免 top-level await 引起的执行时警告。
24
+ *
25
+ * 环境变量优先级:
26
+ * 1) 命令行传入的 env-path(通过 VERCEL_DEPLOY_TOOL_ENV_PATH 或 deploy 命令参数注入)
27
+ * 2) Node 进程已有的 process.env
28
+ * 3) c12 默认加载的 .env* 文件
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const config = await loadConfig();
33
+ * ```
34
+ */
35
+ export async function loadConfig(): Promise<VercelDeployConfig> {
36
+ consola.start("开始读取配置文件 vercel-deploy-tool.config.* ...");
37
+
38
+ // 兼容 CLI 的 --env-path,允许指定自定义 dotenv 文件
39
+ const envPath = process.env.VERCEL_DEPLOY_TOOL_ENV_PATH;
40
+ if (envPath) {
41
+ dotenvxConfig({ path: envPath });
42
+ consola.info(`已从 VERCEL_DEPLOY_TOOL_ENV_PATH 加载 dotenv: ${envPath}`);
43
+ }
44
+
45
+ let config: VercelDeployConfig | undefined;
46
+
47
+ const loaded = await c12LoadConfig<VercelDeployConfig>({
48
+ cwd: process.cwd(),
49
+ name: CONFIG_NAME,
50
+ dotenv: true,
51
+ defaults: DEFAULT_CONFIG,
52
+ });
53
+
54
+ config = loaded.config;
55
+
56
+ // 环境变量覆盖
57
+ const vercelOrgId = process.env.VERCEL_ORG_ID;
58
+ const vercelProjectId = process.env.VERCEL_PROJECT_ID;
59
+ const vercelToken = process.env.VERCEL_TOKEN;
60
+
61
+ if (!isUndefined(vercelOrgId)) {
62
+ config.vercelOrgId = vercelOrgId;
63
+ }
64
+ if (!isUndefined(vercelProjectId)) {
65
+ config.vercelProjectId = vercelProjectId;
66
+ }
67
+ if (!isUndefined(vercelToken)) {
68
+ config.vercelToken = vercelToken;
69
+ }
70
+
71
+ consola.success("配置加载完成");
72
+ consola.box(printFormat(config));
73
+
74
+ return config;
75
+ }
76
+
77
+ let cachedConfigPromise: Promise<VercelDeployConfig> | null = null;
78
+
79
+ /**
80
+ * 导出配置获取函数(带缓存)
81
+ * @description
82
+ * 首次调用会触发加载,后续复用结果。避免 top-level await。
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * import { getConfig } from "@ruan-cat/vercel-deploy-tool";
87
+ * const config = await getConfig();
88
+ * ```
89
+ */
90
+ export async function getConfig(): Promise<VercelDeployConfig> {
91
+ if (!cachedConfigPromise) {
92
+ cachedConfigPromise = loadConfig();
93
+ }
94
+ return cachedConfigPromise;
95
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @description
3
+ * 从 drizzle-kit 学的类型验证技巧
4
+ */
5
+ type Verify<T, U extends T> = U;
6
+
7
+ /** 部署目标类型 */
8
+ export type DeployTargetType = "static" | "userCommands";
9
+
10
+ /** 基础配置 */
11
+ export interface DeployTargetBase {
12
+ /** 部署目标分类 */
13
+ type: DeployTargetType;
14
+
15
+ /** 目标的工作目录 */
16
+ targetCWD: `./${string}`;
17
+
18
+ /** 生产环境的访问url */
19
+ url: string[];
20
+
21
+ /**
22
+ * 是否需要vercel的build命令?
23
+ * @description
24
+ * 某些情况下用户已经能够准备好vercel的目录结构,不需要依赖于 `vercel build` 命令完成目录构建
25
+ *
26
+ * 故设计此配置允许用户直接跳过 `vercel build` 命令
27
+ * @default true
28
+ */
29
+ isNeedVercelBuild?: boolean;
30
+ }
31
+
32
+ /**
33
+ * 带有用户命令的配置
34
+ */
35
+ export interface DeployTargetWithUserCommands extends DeployTargetBase {
36
+ type: Verify<DeployTargetType, "userCommands">;
37
+
38
+ /**
39
+ * 用户命令
40
+ * @description
41
+ * 实际部署的构建命令,通常是真实参与部署的命令
42
+ *
43
+ * @example ["pnpm -C=./packages/docs-01-star build:docs"]
44
+ * @example ["pnpm -C=./packages/monorepo-5 build:docs"]
45
+ */
46
+ userCommands: string[];
47
+
48
+ /**
49
+ * 部署输出路径
50
+ *
51
+ * @version 2
52
+ * @description
53
+ * 填写打包目录的路径即可。不包含glob语法。
54
+ * @example "docs/.vitepress/dist"
55
+ * @example "src/.vuepress/dist"
56
+ */
57
+ outputDirectory: string;
58
+
59
+ /**
60
+ * 是否移动打包目录至特定的vercel部署目录?
61
+ * @description
62
+ * 执行完用户命令后,一般会执行文件移动命令,以便于部署
63
+ *
64
+ * 该配置用于控制是否执行文件移动命令
65
+ *
66
+ * @default true
67
+ */
68
+ isCopyDist?: boolean;
69
+ }
70
+
71
+ /** 部署目标的具体项目配置 */
72
+ export type DeployTarget = DeployTargetBase | DeployTargetWithUserCommands;
73
+
74
+ /** Vercel部署工具的配置 */
75
+ export interface VercelDeployConfig {
76
+ /** 项目名称 */
77
+ vercelProjectName: string;
78
+
79
+ /** 用户token */
80
+ vercelToken: string;
81
+
82
+ /** 用户组织id */
83
+ vercelOrgId: string;
84
+
85
+ /** 用户项目id */
86
+ vercelProjectId: string;
87
+
88
+ /**
89
+ * 用户提供的 vercel.json 配置文件
90
+ * @description
91
+ * 有时候用户需要提供自己的一套配置文件
92
+ *
93
+ * 这里提供配置路径
94
+ */
95
+ vercelJsonPath?: string;
96
+
97
+ /** 在build命令阶段后执行的用户命令 */
98
+ afterBuildTasks?: string[];
99
+
100
+ /**
101
+ * 部署目标
102
+ * @description
103
+ * 考虑到可能要部署一揽子的项目,所以这里使用数组
104
+ *
105
+ * 考虑monorepo的情况
106
+ */
107
+ deployTargets: DeployTarget[];
108
+ }
@@ -0,0 +1,50 @@
1
+ import task from "tasuku";
2
+
3
+ export { task };
4
+ export type { TaskFunction } from "tasuku";
5
+
6
+ /**
7
+ * 并行执行任务组
8
+ * @description
9
+ * 封装 tasuku 的 task.group,提供更简洁的 API
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * await executeParallel("Link 项目", [
14
+ * { name: "Link target 1", fn: async () => {...} },
15
+ * { name: "Link target 2", fn: async () => {...} }
16
+ * ]);
17
+ * ```
18
+ */
19
+ export async function executeParallel<T>(
20
+ title: string,
21
+ tasks: Array<{ name: string; fn: () => Promise<T> }>,
22
+ options?: { concurrency?: number },
23
+ ) {
24
+ return await task.group((task) => tasks.map((t) => task(t.name, t.fn)), options);
25
+ }
26
+
27
+ /**
28
+ * 顺序执行任务组
29
+ * @description
30
+ * 逐个执行任务,每个任务完成后再执行下一个
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * await executeSequential("执行构建任务", [
35
+ * { name: "Task 1", fn: async () => {...} },
36
+ * { name: "Task 2", fn: async () => {...} }
37
+ * ]);
38
+ * ```
39
+ */
40
+ export async function executeSequential<T = any>(
41
+ title: string,
42
+ tasks: Array<{ name: string; fn: () => Promise<T> }>,
43
+ ): Promise<T[]> {
44
+ const results: T[] = [];
45
+ for (const t of tasks) {
46
+ const result = await task(t.name, t.fn);
47
+ results.push(result.result);
48
+ }
49
+ return results;
50
+ }
@@ -0,0 +1,46 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { consola } from "consola";
3
+ import { isUndefined, isEmpty } from "lodash-es";
4
+ import type { VercelDeployConfig } from "../../config/schema";
5
+
6
+ /**
7
+ * 创建 AfterBuild 任务列表
8
+ * @description
9
+ * 在 build 命令阶段后执行的用户命令
10
+ */
11
+ export function createAfterBuildTasks(config: VercelDeployConfig) {
12
+ const afterBuildTasks = config.afterBuildTasks;
13
+
14
+ if (isUndefined(afterBuildTasks) || isEmpty(afterBuildTasks)) {
15
+ return [
16
+ {
17
+ name: "AfterBuild: 无任务",
18
+ fn: async (): Promise<string | undefined> => {
19
+ consola.warn("当前没有有意义的 afterBuildTasks 任务配置");
20
+ return undefined;
21
+ },
22
+ },
23
+ ];
24
+ }
25
+
26
+ return afterBuildTasks.map((command) => ({
27
+ name: `AfterBuild: ${command}`,
28
+ fn: async (): Promise<string | undefined> => {
29
+ consola.start(`开始 afterBuild 任务: ${command}`);
30
+
31
+ const result = spawnSync(command, [], {
32
+ encoding: "utf-8",
33
+ stdio: "inherit",
34
+ shell: true,
35
+ });
36
+
37
+ if (result.error) {
38
+ consola.error(`afterBuild 任务失败: ${command}`);
39
+ throw result.error;
40
+ }
41
+
42
+ consola.success(`完成 afterBuild 任务: ${command}`);
43
+ return result.stdout;
44
+ },
45
+ }));
46
+ }
@@ -0,0 +1,37 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { concat } from "lodash-es";
3
+ import { consola } from "consola";
4
+ import type { VercelDeployConfig } from "../../config/schema";
5
+ import { createVercelSpawnOptions, getVercelTokenArg, getVercelScopeArg } from "../vercel";
6
+
7
+ /**
8
+ * 创建 Alias 任务
9
+ * @description
10
+ * 旨在于封装类似于这样的命令:
11
+ *
12
+ * vc alias set deployment-url.vercel.app custom-domain.com -t TOKEN --scope ORG_ID
13
+ *
14
+ * @see https://vercel.community/t/deployment-via-gitlab-ci-to-dev-domain/523/3
15
+ */
16
+ export function createAliasTask(config: VercelDeployConfig, vercelUrl: string, userUrl: string) {
17
+ return {
18
+ name: `Alias: ${userUrl}`,
19
+ fn: async () => {
20
+ const args = concat(["alias", "set", vercelUrl, userUrl], getVercelTokenArg(config), getVercelScopeArg(config));
21
+
22
+ consola.start(`开始别名任务: ${userUrl}`);
23
+
24
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
25
+
26
+ if (result.error) {
27
+ consola.error(`别名任务失败: ${userUrl}`);
28
+ throw result.error;
29
+ }
30
+
31
+ consola.success(`完成别名任务,可用的别名地址为:`);
32
+ consola.box(`https://${userUrl}`);
33
+
34
+ return result.stdout;
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,50 @@
1
+ import fs from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import { concat } from "lodash-es";
5
+ import { consola } from "consola";
6
+ import type { VercelDeployConfig, DeployTarget } from "../../config/schema";
7
+ import { createVercelSpawnOptions, getVercelTokenArg, getVercelLocalConfigArg, getTargetCWDArg } from "../vercel";
8
+
9
+ /**
10
+ * 创建 Build 任务
11
+ * @description
12
+ * 旨在于封装类似于这样的命令:
13
+ *
14
+ * vc build --yes --prod --cwd=./packages/docs -A ./vercel.null.json -t TOKEN
15
+ */
16
+ export function createBuildTask(config: VercelDeployConfig, target: DeployTarget) {
17
+ return {
18
+ name: `Build: ${target.targetCWD}`,
19
+ fn: async () => {
20
+ const targetPath = resolve(target.targetCWD);
21
+
22
+ if (!fs.existsSync(targetPath)) {
23
+ const err = new Error(`目标目录不存在,请先构建: ${target.targetCWD}`);
24
+ consola.error(err.message);
25
+ throw err;
26
+ }
27
+
28
+ const args = concat(
29
+ ["build"],
30
+ ["--yes"],
31
+ ["--prod"],
32
+ getTargetCWDArg(target),
33
+ getVercelLocalConfigArg(),
34
+ getVercelTokenArg(config),
35
+ );
36
+
37
+ consola.start(`开始 build 任务: ${target.targetCWD}`);
38
+
39
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
40
+
41
+ if (result.error) {
42
+ consola.error(`build 任务失败: ${target.targetCWD}`);
43
+ throw result.error;
44
+ }
45
+
46
+ consola.success(`完成 build 任务: ${target.targetCWD}`);
47
+ return result.stdout;
48
+ },
49
+ };
50
+ }
@@ -0,0 +1,59 @@
1
+ import { resolve } from "node:path";
2
+ import { rmSync, mkdirSync, cpSync } from "node:fs";
3
+ import { consola } from "consola";
4
+ import type { DeployTargetWithUserCommands } from "../../config/schema";
5
+ import { VERCEL_OUTPUT_STATIC } from "../../utils/vercel-null-config";
6
+
7
+ /**
8
+ * 创建文件复制任务列表
9
+ * @description
10
+ * 针对单个部署目标,生成一系列移动目录的任务:
11
+ * 1. 删除目录
12
+ * 2. 新建目录
13
+ * 3. 复制粘贴
14
+ */
15
+ export function createCopyDistTasks(target: DeployTargetWithUserCommands) {
16
+ const targetCWD = target.targetCWD;
17
+ const outputDirectory = target.outputDirectory;
18
+
19
+ /**
20
+ * 路径拼接工具
21
+ * @private
22
+ * 仅考虑为内部使用,不是通用工具
23
+ */
24
+ function joinPath<T extends string>(dir: T) {
25
+ return resolve(process.cwd(), targetCWD, dir);
26
+ }
27
+
28
+ const pathVercelOutputStatic = joinPath(VERCEL_OUTPUT_STATIC);
29
+ const pathOutputDirectory = joinPath(outputDirectory);
30
+
31
+ return [
32
+ {
33
+ name: `删除目录: ${pathVercelOutputStatic}`,
34
+ fn: async () => {
35
+ consola.start(`开始删除文件任务: ${pathVercelOutputStatic}`);
36
+ rmSync(pathVercelOutputStatic, { recursive: true, force: true });
37
+ consola.success(`删除该路径的文件: ${pathVercelOutputStatic}`);
38
+ },
39
+ },
40
+ {
41
+ name: `创建目录: ${pathVercelOutputStatic}`,
42
+ fn: async () => {
43
+ consola.start(`开始创建文件夹任务: ${pathVercelOutputStatic}`);
44
+ mkdirSync(pathVercelOutputStatic, { recursive: true });
45
+ consola.success(`创建的新目录为: ${pathVercelOutputStatic}`);
46
+ },
47
+ },
48
+ {
49
+ name: `复制文件: ${pathOutputDirectory} -> ${pathVercelOutputStatic}`,
50
+ fn: async () => {
51
+ consola.start(`开始文件复制任务`);
52
+ consola.info(`从 ${pathOutputDirectory} 开始`);
53
+ consola.info(`复制到 ${pathVercelOutputStatic} 内`);
54
+ cpSync(pathOutputDirectory, pathVercelOutputStatic, { recursive: true });
55
+ consola.success(`完成文件复制任务`);
56
+ },
57
+ },
58
+ ];
59
+ }
@@ -0,0 +1,54 @@
1
+ import fs from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import { concat } from "lodash-es";
5
+ import { consola } from "consola";
6
+ import type { VercelDeployConfig, DeployTarget } from "../../config/schema";
7
+ import { createVercelSpawnOptions, getVercelTokenArg, getTargetCWDArg } from "../vercel";
8
+
9
+ /**
10
+ * 创建 Deploy 任务
11
+ * @description
12
+ * 旨在于封装类似于这样的命令:
13
+ *
14
+ * vc deploy --yes --prebuilt --prod --cwd=./packages/docs -t TOKEN
15
+ */
16
+ export function createDeployTask(config: VercelDeployConfig, target: DeployTarget) {
17
+ return {
18
+ name: `Deploy: ${target.targetCWD}`,
19
+ fn: async () => {
20
+ const targetPath = resolve(target.targetCWD);
21
+
22
+ if (!fs.existsSync(targetPath)) {
23
+ const err = new Error(`目标目录不存在,请先构建: ${target.targetCWD}`);
24
+ consola.error(err.message);
25
+ throw err;
26
+ }
27
+
28
+ const args = concat(
29
+ ["deploy"],
30
+ ["--yes"],
31
+ ["--prebuilt"],
32
+ ["--prod"],
33
+ getTargetCWDArg(target),
34
+ getVercelTokenArg(config),
35
+ );
36
+
37
+ consola.start(`开始部署任务: ${target.targetCWD}`);
38
+
39
+ const result = spawnSync("vercel", args, createVercelSpawnOptions("pipe"));
40
+
41
+ if (result.error) {
42
+ consola.error(`部署失败了: ${target.targetCWD}`);
43
+ consola.error(result.error);
44
+ throw result.error;
45
+ }
46
+
47
+ const vercelUrl = result.stdout.toString().trim();
48
+ consola.success(`完成部署任务,生成的url为:`);
49
+ consola.box(vercelUrl);
50
+
51
+ return vercelUrl;
52
+ },
53
+ };
54
+ }