@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.
- package/README.md +375 -75
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +578 -0
- package/dist/index.d.ts +247 -0
- package/dist/index.js +566 -0
- package/package.json +14 -6
- package/src/cli.ts +61 -0
- package/src/commands/deploy.ts +50 -0
- package/src/commands/init.ts +92 -0
- package/src/config/define-config.ts +20 -0
- package/src/config/loader.ts +95 -0
- package/src/config/schema.ts +108 -0
- package/src/core/executor.ts +50 -0
- package/src/core/tasks/after-build.ts +46 -0
- package/src/core/tasks/alias.ts +37 -0
- package/src/core/tasks/build.ts +50 -0
- package/src/core/tasks/copy-dist.ts +59 -0
- package/src/core/tasks/deploy.ts +54 -0
- package/src/core/tasks/index.ts +144 -0
- package/src/core/tasks/link.ts +49 -0
- package/src/core/tasks/user-commands.ts +31 -0
- package/src/core/vercel.ts +55 -0
- package/src/index.ts +68 -550
- package/src/templates/vercel-deploy-tool.config.ts +129 -0
- package/src/types/index.ts +14 -0
- package/src/utils/type-guards.ts +33 -0
- package/src/utils/vercel-null-config.ts +46 -0
- package/tsconfig.json +3 -1
|
@@ -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
|
+
}
|