@ruan-cat/vercel-deploy-tool 1.0.0 → 1.2.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 CHANGED
@@ -99,6 +99,13 @@ pnpm run deploy-vercel
99
99
  npx vercel-deploy-tool deploy
100
100
  # 或使用短别名
101
101
  npx vdt deploy
102
+
103
+ # 如需指定自定义 dotenv 文件
104
+ npx vdt deploy --env-path .env.production
105
+ # 等价:设置环境变量再运行
106
+ VERCEL_DEPLOY_TOOL_ENV_PATH=.env.production npx vdt deploy
107
+ # 多文件场景(依赖 dotenvx)
108
+ dotenvx run -f .env.test -f .env.test-2 -- vdt deploy
102
109
  ```
103
110
 
104
111
  ### 方式二:使用 API
@@ -256,6 +263,13 @@ export default defineConfig({
256
263
  - 部署到 Vercel
257
264
  - 设置自定义域名别名
258
265
 
266
+ ### 环境变量优先级
267
+
268
+ - `--env-path` / `VERCEL_DEPLOY_TOOL_ENV_PATH` 指定的 dotenv(如 `.env.production`)
269
+ - 现有 `process.env`
270
+ - c12 自动加载的 `.env*`
271
+ - 配置默认值
272
+
259
273
  ## 📋 .gitignore 配置
260
274
 
261
275
  添加以下内容到 `.gitignore`:
package/dist/cli.js CHANGED
@@ -9,12 +9,13 @@ import { Command as Command3 } from "commander";
9
9
  // src/commands/deploy.ts
10
10
  import { Command } from "commander";
11
11
  import { consola as consola10 } from "consola";
12
+ import { config as dotenvxConfig2 } from "@dotenvx/dotenvx";
12
13
 
13
14
  // src/config/loader.ts
14
15
  import { loadConfig as c12LoadConfig } from "c12";
15
- import { resolve } from "pathe";
16
16
  import { consola } from "consola";
17
17
  import { isUndefined } from "lodash-es";
18
+ import { config as dotenvxConfig } from "@dotenvx/dotenvx";
18
19
  import { printFormat } from "@ruan-cat/utils";
19
20
  var CONFIG_NAME = "vercel-deploy-tool";
20
21
  var DEFAULT_CONFIG = {
@@ -25,32 +26,40 @@ var DEFAULT_CONFIG = {
25
26
  deployTargets: []
26
27
  };
27
28
  async function loadConfig() {
28
- const { config: config2 } = await c12LoadConfig({
29
- cwd: resolve("."),
29
+ consola.start("\u5F00\u59CB\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6 vercel-deploy-tool.config.* ...");
30
+ const envPath = process.env.VERCEL_DEPLOY_TOOL_ENV_PATH;
31
+ if (envPath) {
32
+ dotenvxConfig({ path: envPath });
33
+ consola.info(`\u5DF2\u4ECE VERCEL_DEPLOY_TOOL_ENV_PATH \u52A0\u8F7D dotenv: ${envPath}`);
34
+ }
35
+ let config;
36
+ const loaded = await c12LoadConfig({
37
+ cwd: process.cwd(),
30
38
  name: CONFIG_NAME,
31
39
  dotenv: true,
32
40
  defaults: DEFAULT_CONFIG
33
41
  });
42
+ config = loaded.config;
34
43
  const vercelOrgId = process.env.VERCEL_ORG_ID;
35
44
  const vercelProjectId = process.env.VERCEL_PROJECT_ID;
36
45
  const vercelToken = process.env.VERCEL_TOKEN;
37
46
  if (!isUndefined(vercelOrgId)) {
38
- config2.vercelOrgId = vercelOrgId;
47
+ config.vercelOrgId = vercelOrgId;
39
48
  }
40
49
  if (!isUndefined(vercelProjectId)) {
41
- config2.vercelProjectId = vercelProjectId;
50
+ config.vercelProjectId = vercelProjectId;
42
51
  }
43
52
  if (!isUndefined(vercelToken)) {
44
- config2.vercelToken = vercelToken;
53
+ config.vercelToken = vercelToken;
45
54
  }
46
55
  consola.success("\u914D\u7F6E\u52A0\u8F7D\u5B8C\u6210");
47
- consola.box(printFormat(config2));
48
- return config2;
56
+ consola.box(printFormat(config));
57
+ return config;
49
58
  }
50
- var config = await loadConfig();
51
59
 
52
60
  // src/core/tasks/index.ts
53
- import fs from "fs";
61
+ import fs4 from "fs";
62
+ import { resolve as resolve5 } from "path";
54
63
  import { consola as consola9 } from "consola";
55
64
 
56
65
  // src/core/executor.ts
@@ -105,19 +114,21 @@ var VERCEL_NULL_CONFIG_PATH = "./vercel.null.def.json";
105
114
  var VERCEL_OUTPUT_STATIC = ".vercel/output/static";
106
115
 
107
116
  // src/core/tasks/link.ts
117
+ import fs from "fs";
118
+ import { resolve } from "path";
108
119
  import { spawnSync } from "child_process";
109
120
  import { concat } from "lodash-es";
110
121
  import { consola as consola2 } from "consola";
111
122
 
112
123
  // src/core/vercel.ts
113
- function getVercelProjectNameArg(config2) {
114
- return ["--name", config2.vercelProjectName];
124
+ function getVercelProjectNameArg(config) {
125
+ return ["--project", config.vercelProjectName];
115
126
  }
116
- function getVercelScopeArg(config2) {
117
- return ["--scope", config2.vercelOrgId];
127
+ function getVercelScopeArg(config) {
128
+ return ["--scope", config.vercelOrgId];
118
129
  }
119
- function getVercelTokenArg(config2) {
120
- return ["--token", config2.vercelToken];
130
+ function getVercelTokenArg(config) {
131
+ return ["--token", config.vercelToken];
121
132
  }
122
133
  function getVercelLocalConfigArg() {
123
134
  return ["--local-config", VERCEL_NULL_CONFIG_PATH];
@@ -125,24 +136,37 @@ function getVercelLocalConfigArg() {
125
136
  function getTargetCWDArg(target) {
126
137
  return ["--cwd", target.targetCWD];
127
138
  }
139
+ function createVercelSpawnOptions(stdoutMode = "inherit") {
140
+ const base = {
141
+ encoding: "utf8",
142
+ shell: process.platform === "win32"
143
+ };
144
+ if (stdoutMode === "pipe") {
145
+ return { ...base, stdio: ["inherit", "pipe", "inherit"] };
146
+ }
147
+ return { ...base, stdio: "inherit" };
148
+ }
128
149
 
129
150
  // src/core/tasks/link.ts
130
- function createLinkTask(config2, target) {
151
+ function createLinkTask(config, target) {
131
152
  return {
132
153
  name: `Link: ${target.targetCWD}`,
133
154
  fn: async () => {
155
+ const targetPath = resolve(target.targetCWD);
156
+ if (!fs.existsSync(targetPath)) {
157
+ const err = new Error(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6784\u5EFA: ${target.targetCWD}`);
158
+ consola2.error(err.message);
159
+ throw err;
160
+ }
134
161
  const args = concat(
135
162
  ["link"],
136
163
  ["--yes"],
137
164
  getTargetCWDArg(target),
138
- getVercelProjectNameArg(config2),
139
- getVercelTokenArg(config2)
165
+ getVercelProjectNameArg(config),
166
+ getVercelTokenArg(config)
140
167
  );
141
168
  consola2.start(`\u5F00\u59CB link \u4EFB\u52A1: ${target.targetCWD}`);
142
- const result = spawnSync("vercel", args, {
143
- encoding: "utf-8",
144
- stdio: "inherit"
145
- });
169
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
146
170
  if (result.error) {
147
171
  consola2.error(`link \u4EFB\u52A1\u5931\u8D25: ${target.targetCWD}`);
148
172
  throw result.error;
@@ -154,26 +178,31 @@ function createLinkTask(config2, target) {
154
178
  }
155
179
 
156
180
  // src/core/tasks/build.ts
181
+ import fs2 from "fs";
182
+ import { resolve as resolve2 } from "path";
157
183
  import { spawnSync as spawnSync2 } from "child_process";
158
184
  import { concat as concat2 } from "lodash-es";
159
185
  import { consola as consola3 } from "consola";
160
- function createBuildTask(config2, target) {
186
+ function createBuildTask(config, target) {
161
187
  return {
162
188
  name: `Build: ${target.targetCWD}`,
163
189
  fn: async () => {
190
+ const targetPath = resolve2(target.targetCWD);
191
+ if (!fs2.existsSync(targetPath)) {
192
+ const err = new Error(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6784\u5EFA: ${target.targetCWD}`);
193
+ consola3.error(err.message);
194
+ throw err;
195
+ }
164
196
  const args = concat2(
165
197
  ["build"],
166
198
  ["--yes"],
167
199
  ["--prod"],
168
200
  getTargetCWDArg(target),
169
201
  getVercelLocalConfigArg(),
170
- getVercelTokenArg(config2)
202
+ getVercelTokenArg(config)
171
203
  );
172
204
  consola3.start(`\u5F00\u59CB build \u4EFB\u52A1: ${target.targetCWD}`);
173
- const result = spawnSync2("vercel", args, {
174
- encoding: "utf-8",
175
- stdio: "inherit"
176
- });
205
+ const result = spawnSync2("vercel", args, createVercelSpawnOptions());
177
206
  if (result.error) {
178
207
  consola3.error(`build \u4EFB\u52A1\u5931\u8D25: ${target.targetCWD}`);
179
208
  throw result.error;
@@ -188,8 +217,8 @@ function createBuildTask(config2, target) {
188
217
  import { spawnSync as spawnSync3 } from "child_process";
189
218
  import { consola as consola4 } from "consola";
190
219
  import { isUndefined as isUndefined2, isEmpty } from "lodash-es";
191
- function createAfterBuildTasks(config2) {
192
- const afterBuildTasks = config2.afterBuildTasks;
220
+ function createAfterBuildTasks(config) {
221
+ const afterBuildTasks = config.afterBuildTasks;
193
222
  if (isUndefined2(afterBuildTasks) || isEmpty(afterBuildTasks)) {
194
223
  return [
195
224
  {
@@ -244,14 +273,14 @@ function createUserCommandTasks(target) {
244
273
  }
245
274
 
246
275
  // src/core/tasks/copy-dist.ts
247
- import { resolve as resolve2 } from "path";
276
+ import { resolve as resolve3 } from "path";
248
277
  import { rmSync, mkdirSync, cpSync } from "fs";
249
278
  import { consola as consola6 } from "consola";
250
279
  function createCopyDistTasks(target) {
251
280
  const targetCWD = target.targetCWD;
252
281
  const outputDirectory = target.outputDirectory;
253
282
  function joinPath(dir) {
254
- return resolve2(process.cwd(), targetCWD, dir);
283
+ return resolve3(process.cwd(), targetCWD, dir);
255
284
  }
256
285
  const pathVercelOutputStatic = joinPath(VERCEL_OUTPUT_STATIC);
257
286
  const pathOutputDirectory = joinPath(outputDirectory);
@@ -286,25 +315,31 @@ function createCopyDistTasks(target) {
286
315
  }
287
316
 
288
317
  // src/core/tasks/deploy.ts
318
+ import fs3 from "fs";
319
+ import { resolve as resolve4 } from "path";
289
320
  import { spawnSync as spawnSync5 } from "child_process";
290
321
  import { concat as concat3 } from "lodash-es";
291
322
  import { consola as consola7 } from "consola";
292
- function createDeployTask(config2, target) {
323
+ function createDeployTask(config, target) {
293
324
  return {
294
325
  name: `Deploy: ${target.targetCWD}`,
295
326
  fn: async () => {
327
+ const targetPath = resolve4(target.targetCWD);
328
+ if (!fs3.existsSync(targetPath)) {
329
+ const err = new Error(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6784\u5EFA: ${target.targetCWD}`);
330
+ consola7.error(err.message);
331
+ throw err;
332
+ }
296
333
  const args = concat3(
297
334
  ["deploy"],
298
335
  ["--yes"],
299
336
  ["--prebuilt"],
300
337
  ["--prod"],
301
338
  getTargetCWDArg(target),
302
- getVercelTokenArg(config2)
339
+ getVercelTokenArg(config)
303
340
  );
304
341
  consola7.start(`\u5F00\u59CB\u90E8\u7F72\u4EFB\u52A1: ${target.targetCWD}`);
305
- const result = spawnSync5("vercel", args, {
306
- encoding: "utf-8"
307
- });
342
+ const result = spawnSync5("vercel", args, createVercelSpawnOptions("pipe"));
308
343
  if (result.error) {
309
344
  consola7.error(`\u90E8\u7F72\u5931\u8D25\u4E86: ${target.targetCWD}`);
310
345
  consola7.error(result.error);
@@ -322,16 +357,13 @@ function createDeployTask(config2, target) {
322
357
  import { spawnSync as spawnSync6 } from "child_process";
323
358
  import { concat as concat4 } from "lodash-es";
324
359
  import { consola as consola8 } from "consola";
325
- function createAliasTask(config2, vercelUrl, userUrl) {
360
+ function createAliasTask(config, vercelUrl, userUrl) {
326
361
  return {
327
362
  name: `Alias: ${userUrl}`,
328
363
  fn: async () => {
329
- const args = concat4(["alias", "set", vercelUrl, userUrl], getVercelTokenArg(config2), getVercelScopeArg(config2));
364
+ const args = concat4(["alias", "set", vercelUrl, userUrl], getVercelTokenArg(config), getVercelScopeArg(config));
330
365
  consola8.start(`\u5F00\u59CB\u522B\u540D\u4EFB\u52A1: ${userUrl}`);
331
- const result = spawnSync6("vercel", args, {
332
- encoding: "utf-8",
333
- stdio: "inherit"
334
- });
366
+ const result = spawnSync6("vercel", args, createVercelSpawnOptions());
335
367
  if (result.error) {
336
368
  consola8.error(`\u522B\u540D\u4EFB\u52A1\u5931\u8D25: ${userUrl}`);
337
369
  throw result.error;
@@ -345,19 +377,31 @@ function createAliasTask(config2, vercelUrl, userUrl) {
345
377
 
346
378
  // src/core/tasks/index.ts
347
379
  async function generateVercelNullConfig() {
348
- fs.writeFileSync(VERCEL_NULL_CONFIG_PATH, JSON.stringify(VERCEL_NULL_CONFIG, null, 2));
380
+ fs4.writeFileSync(VERCEL_NULL_CONFIG_PATH, JSON.stringify(VERCEL_NULL_CONFIG, null, 2));
349
381
  consola9.success(`\u751F\u6210 Vercel \u7A7A\u914D\u7F6E\u6587\u4EF6: ${VERCEL_NULL_CONFIG_PATH}`);
350
382
  }
351
- async function executeDeploymentWorkflow(config2) {
383
+ async function executeDeploymentWorkflow(config) {
352
384
  await generateVercelNullConfig();
353
- const { deployTargets } = config2;
385
+ const { deployTargets } = config;
386
+ const availableTargets = deployTargets.filter((target) => {
387
+ const targetPath = resolve5(target.targetCWD);
388
+ if (!fs4.existsSync(targetPath)) {
389
+ consola9.warn(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${target.targetCWD}`);
390
+ return false;
391
+ }
392
+ return true;
393
+ });
394
+ if (availableTargets.length === 0) {
395
+ consola9.error("\u6CA1\u6709\u53EF\u7528\u7684\u90E8\u7F72\u76EE\u6807\uFF0C\u8BF7\u5148\u6784\u5EFA\u4EA7\u7269");
396
+ return;
397
+ }
354
398
  await task("Vercel \u90E8\u7F72\u5DE5\u4F5C\u6D41", async ({ task: task2 }) => {
355
399
  await task2("1. Link \u9879\u76EE", async () => {
356
- const linkTasks = deployTargets.map((target) => createLinkTask(config2, target));
400
+ const linkTasks = availableTargets.map((target) => createLinkTask(config, target));
357
401
  await task2.group((task3) => linkTasks.map((t) => task3(t.name, t.fn)));
358
402
  });
359
403
  await task2("2. \u6784\u5EFA\u9879\u76EE", async () => {
360
- const buildTasks = deployTargets.filter(isNeedVercelBuild).map((target) => createBuildTask(config2, target));
404
+ const buildTasks = availableTargets.filter(isNeedVercelBuild).map((target) => createBuildTask(config, target));
361
405
  if (buildTasks.length === 0) {
362
406
  consola9.warn("\u6CA1\u6709\u9700\u8981\u6267\u884C build \u7684\u76EE\u6807");
363
407
  return;
@@ -365,11 +409,11 @@ async function executeDeploymentWorkflow(config2) {
365
409
  await task2.group((task3) => buildTasks.map((t) => task3(t.name, t.fn)));
366
410
  });
367
411
  await task2("3. \u6267\u884C AfterBuild \u4EFB\u52A1", async () => {
368
- const afterBuildTasks = createAfterBuildTasks(config2);
412
+ const afterBuildTasks = createAfterBuildTasks(config);
369
413
  await executeSequential("AfterBuild", afterBuildTasks);
370
414
  });
371
415
  await task2("4. \u6267\u884C\u7528\u6237\u547D\u4EE4\u4E0E\u6587\u4EF6\u590D\u5236", async () => {
372
- const targetTasks = deployTargets.map((target) => ({
416
+ const targetTasks = availableTargets.map((target) => ({
373
417
  name: `\u5904\u7406\u76EE\u6807: ${target.targetCWD}`,
374
418
  fn: async () => {
375
419
  if (!isDeployTargetWithUserCommands(target)) {
@@ -389,14 +433,14 @@ async function executeDeploymentWorkflow(config2) {
389
433
  await task2.group((task3) => targetTasks.map((t) => task3(t.name, t.fn)));
390
434
  });
391
435
  await task2("5. \u90E8\u7F72\u4E0E\u8BBE\u7F6E\u522B\u540D", async () => {
392
- const deployAliasTasks = deployTargets.map((target) => ({
436
+ const deployAliasTasks = availableTargets.map((target) => ({
393
437
  name: `\u90E8\u7F72\u4E0E\u522B\u540D: ${target.targetCWD}`,
394
438
  fn: async () => {
395
- const deployTask = createDeployTask(config2, target);
439
+ const deployTask = createDeployTask(config, target);
396
440
  const deployResult = await task2(deployTask.name, deployTask.fn);
397
441
  const vercelUrl = deployResult.result;
398
442
  if (target.url && target.url.length > 0) {
399
- const aliasTasks = target.url.map((userUrl) => createAliasTask(config2, vercelUrl, userUrl));
443
+ const aliasTasks = target.url.map((userUrl) => createAliasTask(config, vercelUrl, userUrl));
400
444
  await task2.group((task3) => aliasTasks.map((t) => task3(t.name, t.fn)));
401
445
  } else {
402
446
  consola9.warn(`\u76EE\u6807 ${target.targetCWD} \u6CA1\u6709\u914D\u7F6E\u522B\u540D`);
@@ -412,12 +456,17 @@ async function executeDeploymentWorkflow(config2) {
412
456
  // src/commands/deploy.ts
413
457
  function createDeployCommand() {
414
458
  const command = new Command("deploy");
415
- command.description("\u90E8\u7F72\u9879\u76EE\u5230 Vercel").action(async () => {
459
+ command.description("\u90E8\u7F72\u9879\u76EE\u5230 Vercel").option("--env-path <path>", "\u6307\u5B9A dotenv \u6587\u4EF6\u8DEF\u5F84\uFF0C\u7528\u4E8E\u8986\u76D6\u9ED8\u8BA4\u73AF\u5883\u53D8\u91CF").action(async (options) => {
416
460
  try {
461
+ if (options?.envPath) {
462
+ process.env.VERCEL_DEPLOY_TOOL_ENV_PATH = options.envPath;
463
+ dotenvxConfig2({ path: options.envPath });
464
+ consola10.info(`\u5DF2\u4ECE --env-path \u52A0\u8F7D\u73AF\u5883\u53D8\u91CF: ${options.envPath}`);
465
+ }
417
466
  consola10.start("\u5F00\u59CB\u52A0\u8F7D\u914D\u7F6E...");
418
- const config2 = await loadConfig();
467
+ const config = await loadConfig();
419
468
  consola10.start("\u5F00\u59CB\u6267\u884C\u90E8\u7F72\u5DE5\u4F5C\u6D41...");
420
- await executeDeploymentWorkflow(config2);
469
+ await executeDeploymentWorkflow(config);
421
470
  consola10.success("\u90E8\u7F72\u5B8C\u6210\uFF01");
422
471
  } catch (error) {
423
472
  consola10.error("\u90E8\u7F72\u5931\u8D25:");
@@ -520,4 +569,10 @@ program.on("--help", () => {
520
569
  console.log(" VERCEL_PROJECT_ID - Vercel \u9879\u76EE ID");
521
570
  console.log("");
522
571
  });
523
- program.parse();
572
+ async function main() {
573
+ await program.parseAsync();
574
+ }
575
+ main().catch((error) => {
576
+ console.error(error);
577
+ process.exit(1);
578
+ });
package/dist/index.d.ts CHANGED
@@ -112,7 +112,12 @@ declare function defineConfig(config: VercelDeployConfig): VercelDeployConfig;
112
112
  /**
113
113
  * 异步加载配置(工厂函数模式)
114
114
  * @description
115
- * 从约定俗成的配置处,获得用户配置文件
115
+ * 从约定俗成的配置处,获得用户配置文件。不会在模块导入时自动执行,避免 top-level await 引起的执行时警告。
116
+ *
117
+ * 环境变量优先级:
118
+ * 1) 命令行传入的 env-path(通过 VERCEL_DEPLOY_TOOL_ENV_PATH 或 deploy 命令参数注入)
119
+ * 2) Node 进程已有的 process.env
120
+ * 3) c12 默认加载的 .env* 文件
116
121
  *
117
122
  * @example
118
123
  * ```ts
@@ -121,29 +126,17 @@ declare function defineConfig(config: VercelDeployConfig): VercelDeployConfig;
121
126
  */
122
127
  declare function loadConfig(): Promise<VercelDeployConfig>;
123
128
  /**
124
- * 默认导出:已初始化的配置实例(top-level await)
125
- * @description
126
- * 这是混合模式的一部分,提供默认的配置实例
127
- *
128
- * @example
129
- * ```ts
130
- * import { config } from "@ruan-cat/vercel-deploy-tool/config/loader";
131
- * console.log(config.vercelProjectName);
132
- * ```
133
- */
134
- declare const config: VercelDeployConfig;
135
- /**
136
- * 导出配置获取函数
129
+ * 导出配置获取函数(带缓存)
137
130
  * @description
138
- * 获取已加载的配置实例
131
+ * 首次调用会触发加载,后续复用结果。避免 top-level await。
139
132
  *
140
133
  * @example
141
134
  * ```ts
142
135
  * import { getConfig } from "@ruan-cat/vercel-deploy-tool";
143
- * const config = getConfig();
136
+ * const config = await getConfig();
144
137
  * ```
145
138
  */
146
- declare function getConfig(): VercelDeployConfig;
139
+ declare function getConfig(): Promise<VercelDeployConfig>;
147
140
 
148
141
  /**
149
142
  * 执行 Vercel 部署工作流
@@ -251,4 +244,4 @@ declare function createDeployCommand(): Command;
251
244
  */
252
245
  declare function createInitCommand(): Command;
253
246
 
254
- export { type DeployTarget, type DeployTargetBase, type DeployTargetType, type DeployTargetWithUserCommands, VERCEL_NULL_CONFIG, VERCEL_NULL_CONFIG_PATH, VERCEL_OUTPUT_STATIC, type VercelDeployConfig, config, createDeployCommand, createInitCommand, defineConfig, executeDeploymentWorkflow, getConfig, getIsCopyDist, isDeployTargetBase, isDeployTargetWithUserCommands, isNeedVercelBuild, loadConfig };
247
+ export { type DeployTarget, type DeployTargetBase, type DeployTargetType, type DeployTargetWithUserCommands, VERCEL_NULL_CONFIG, VERCEL_NULL_CONFIG_PATH, VERCEL_OUTPUT_STATIC, type VercelDeployConfig, createDeployCommand, createInitCommand, defineConfig, executeDeploymentWorkflow, getConfig, getIsCopyDist, isDeployTargetBase, isDeployTargetWithUserCommands, isNeedVercelBuild, loadConfig };
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  // src/config/define-config.ts
2
- function defineConfig(config2) {
3
- return config2;
2
+ function defineConfig(config) {
3
+ return config;
4
4
  }
5
5
 
6
6
  // src/config/loader.ts
7
7
  import { loadConfig as c12LoadConfig } from "c12";
8
- import { resolve } from "pathe";
9
8
  import { consola } from "consola";
10
9
  import { isUndefined } from "lodash-es";
10
+ import { config as dotenvxConfig } from "@dotenvx/dotenvx";
11
11
  import { printFormat } from "@ruan-cat/utils";
12
12
  var CONFIG_NAME = "vercel-deploy-tool";
13
13
  var DEFAULT_CONFIG = {
@@ -18,35 +18,47 @@ var DEFAULT_CONFIG = {
18
18
  deployTargets: []
19
19
  };
20
20
  async function loadConfig() {
21
- const { config: config2 } = await c12LoadConfig({
22
- cwd: resolve("."),
21
+ consola.start("\u5F00\u59CB\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6 vercel-deploy-tool.config.* ...");
22
+ const envPath = process.env.VERCEL_DEPLOY_TOOL_ENV_PATH;
23
+ if (envPath) {
24
+ dotenvxConfig({ path: envPath });
25
+ consola.info(`\u5DF2\u4ECE VERCEL_DEPLOY_TOOL_ENV_PATH \u52A0\u8F7D dotenv: ${envPath}`);
26
+ }
27
+ let config;
28
+ const loaded = await c12LoadConfig({
29
+ cwd: process.cwd(),
23
30
  name: CONFIG_NAME,
24
31
  dotenv: true,
25
32
  defaults: DEFAULT_CONFIG
26
33
  });
34
+ config = loaded.config;
27
35
  const vercelOrgId = process.env.VERCEL_ORG_ID;
28
36
  const vercelProjectId = process.env.VERCEL_PROJECT_ID;
29
37
  const vercelToken = process.env.VERCEL_TOKEN;
30
38
  if (!isUndefined(vercelOrgId)) {
31
- config2.vercelOrgId = vercelOrgId;
39
+ config.vercelOrgId = vercelOrgId;
32
40
  }
33
41
  if (!isUndefined(vercelProjectId)) {
34
- config2.vercelProjectId = vercelProjectId;
42
+ config.vercelProjectId = vercelProjectId;
35
43
  }
36
44
  if (!isUndefined(vercelToken)) {
37
- config2.vercelToken = vercelToken;
45
+ config.vercelToken = vercelToken;
38
46
  }
39
47
  consola.success("\u914D\u7F6E\u52A0\u8F7D\u5B8C\u6210");
40
- consola.box(printFormat(config2));
41
- return config2;
42
- }
43
- var config = await loadConfig();
44
- function getConfig() {
48
+ consola.box(printFormat(config));
45
49
  return config;
46
50
  }
51
+ var cachedConfigPromise = null;
52
+ async function getConfig() {
53
+ if (!cachedConfigPromise) {
54
+ cachedConfigPromise = loadConfig();
55
+ }
56
+ return cachedConfigPromise;
57
+ }
47
58
 
48
59
  // src/core/tasks/index.ts
49
- import fs from "fs";
60
+ import fs4 from "fs";
61
+ import { resolve as resolve5 } from "path";
50
62
  import { consola as consola9 } from "consola";
51
63
 
52
64
  // src/core/executor.ts
@@ -104,19 +116,21 @@ var VERCEL_NULL_CONFIG_PATH = "./vercel.null.def.json";
104
116
  var VERCEL_OUTPUT_STATIC = ".vercel/output/static";
105
117
 
106
118
  // src/core/tasks/link.ts
119
+ import fs from "fs";
120
+ import { resolve } from "path";
107
121
  import { spawnSync } from "child_process";
108
122
  import { concat } from "lodash-es";
109
123
  import { consola as consola2 } from "consola";
110
124
 
111
125
  // src/core/vercel.ts
112
- function getVercelProjectNameArg(config2) {
113
- return ["--name", config2.vercelProjectName];
126
+ function getVercelProjectNameArg(config) {
127
+ return ["--project", config.vercelProjectName];
114
128
  }
115
- function getVercelScopeArg(config2) {
116
- return ["--scope", config2.vercelOrgId];
129
+ function getVercelScopeArg(config) {
130
+ return ["--scope", config.vercelOrgId];
117
131
  }
118
- function getVercelTokenArg(config2) {
119
- return ["--token", config2.vercelToken];
132
+ function getVercelTokenArg(config) {
133
+ return ["--token", config.vercelToken];
120
134
  }
121
135
  function getVercelLocalConfigArg() {
122
136
  return ["--local-config", VERCEL_NULL_CONFIG_PATH];
@@ -124,24 +138,37 @@ function getVercelLocalConfigArg() {
124
138
  function getTargetCWDArg(target) {
125
139
  return ["--cwd", target.targetCWD];
126
140
  }
141
+ function createVercelSpawnOptions(stdoutMode = "inherit") {
142
+ const base = {
143
+ encoding: "utf8",
144
+ shell: process.platform === "win32"
145
+ };
146
+ if (stdoutMode === "pipe") {
147
+ return { ...base, stdio: ["inherit", "pipe", "inherit"] };
148
+ }
149
+ return { ...base, stdio: "inherit" };
150
+ }
127
151
 
128
152
  // src/core/tasks/link.ts
129
- function createLinkTask(config2, target) {
153
+ function createLinkTask(config, target) {
130
154
  return {
131
155
  name: `Link: ${target.targetCWD}`,
132
156
  fn: async () => {
157
+ const targetPath = resolve(target.targetCWD);
158
+ if (!fs.existsSync(targetPath)) {
159
+ const err = new Error(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6784\u5EFA: ${target.targetCWD}`);
160
+ consola2.error(err.message);
161
+ throw err;
162
+ }
133
163
  const args = concat(
134
164
  ["link"],
135
165
  ["--yes"],
136
166
  getTargetCWDArg(target),
137
- getVercelProjectNameArg(config2),
138
- getVercelTokenArg(config2)
167
+ getVercelProjectNameArg(config),
168
+ getVercelTokenArg(config)
139
169
  );
140
170
  consola2.start(`\u5F00\u59CB link \u4EFB\u52A1: ${target.targetCWD}`);
141
- const result = spawnSync("vercel", args, {
142
- encoding: "utf-8",
143
- stdio: "inherit"
144
- });
171
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
145
172
  if (result.error) {
146
173
  consola2.error(`link \u4EFB\u52A1\u5931\u8D25: ${target.targetCWD}`);
147
174
  throw result.error;
@@ -153,26 +180,31 @@ function createLinkTask(config2, target) {
153
180
  }
154
181
 
155
182
  // src/core/tasks/build.ts
183
+ import fs2 from "fs";
184
+ import { resolve as resolve2 } from "path";
156
185
  import { spawnSync as spawnSync2 } from "child_process";
157
186
  import { concat as concat2 } from "lodash-es";
158
187
  import { consola as consola3 } from "consola";
159
- function createBuildTask(config2, target) {
188
+ function createBuildTask(config, target) {
160
189
  return {
161
190
  name: `Build: ${target.targetCWD}`,
162
191
  fn: async () => {
192
+ const targetPath = resolve2(target.targetCWD);
193
+ if (!fs2.existsSync(targetPath)) {
194
+ const err = new Error(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6784\u5EFA: ${target.targetCWD}`);
195
+ consola3.error(err.message);
196
+ throw err;
197
+ }
163
198
  const args = concat2(
164
199
  ["build"],
165
200
  ["--yes"],
166
201
  ["--prod"],
167
202
  getTargetCWDArg(target),
168
203
  getVercelLocalConfigArg(),
169
- getVercelTokenArg(config2)
204
+ getVercelTokenArg(config)
170
205
  );
171
206
  consola3.start(`\u5F00\u59CB build \u4EFB\u52A1: ${target.targetCWD}`);
172
- const result = spawnSync2("vercel", args, {
173
- encoding: "utf-8",
174
- stdio: "inherit"
175
- });
207
+ const result = spawnSync2("vercel", args, createVercelSpawnOptions());
176
208
  if (result.error) {
177
209
  consola3.error(`build \u4EFB\u52A1\u5931\u8D25: ${target.targetCWD}`);
178
210
  throw result.error;
@@ -187,8 +219,8 @@ function createBuildTask(config2, target) {
187
219
  import { spawnSync as spawnSync3 } from "child_process";
188
220
  import { consola as consola4 } from "consola";
189
221
  import { isUndefined as isUndefined2, isEmpty } from "lodash-es";
190
- function createAfterBuildTasks(config2) {
191
- const afterBuildTasks = config2.afterBuildTasks;
222
+ function createAfterBuildTasks(config) {
223
+ const afterBuildTasks = config.afterBuildTasks;
192
224
  if (isUndefined2(afterBuildTasks) || isEmpty(afterBuildTasks)) {
193
225
  return [
194
226
  {
@@ -243,14 +275,14 @@ function createUserCommandTasks(target) {
243
275
  }
244
276
 
245
277
  // src/core/tasks/copy-dist.ts
246
- import { resolve as resolve2 } from "path";
278
+ import { resolve as resolve3 } from "path";
247
279
  import { rmSync, mkdirSync, cpSync } from "fs";
248
280
  import { consola as consola6 } from "consola";
249
281
  function createCopyDistTasks(target) {
250
282
  const targetCWD = target.targetCWD;
251
283
  const outputDirectory = target.outputDirectory;
252
284
  function joinPath(dir) {
253
- return resolve2(process.cwd(), targetCWD, dir);
285
+ return resolve3(process.cwd(), targetCWD, dir);
254
286
  }
255
287
  const pathVercelOutputStatic = joinPath(VERCEL_OUTPUT_STATIC);
256
288
  const pathOutputDirectory = joinPath(outputDirectory);
@@ -285,25 +317,31 @@ function createCopyDistTasks(target) {
285
317
  }
286
318
 
287
319
  // src/core/tasks/deploy.ts
320
+ import fs3 from "fs";
321
+ import { resolve as resolve4 } from "path";
288
322
  import { spawnSync as spawnSync5 } from "child_process";
289
323
  import { concat as concat3 } from "lodash-es";
290
324
  import { consola as consola7 } from "consola";
291
- function createDeployTask(config2, target) {
325
+ function createDeployTask(config, target) {
292
326
  return {
293
327
  name: `Deploy: ${target.targetCWD}`,
294
328
  fn: async () => {
329
+ const targetPath = resolve4(target.targetCWD);
330
+ if (!fs3.existsSync(targetPath)) {
331
+ const err = new Error(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u6784\u5EFA: ${target.targetCWD}`);
332
+ consola7.error(err.message);
333
+ throw err;
334
+ }
295
335
  const args = concat3(
296
336
  ["deploy"],
297
337
  ["--yes"],
298
338
  ["--prebuilt"],
299
339
  ["--prod"],
300
340
  getTargetCWDArg(target),
301
- getVercelTokenArg(config2)
341
+ getVercelTokenArg(config)
302
342
  );
303
343
  consola7.start(`\u5F00\u59CB\u90E8\u7F72\u4EFB\u52A1: ${target.targetCWD}`);
304
- const result = spawnSync5("vercel", args, {
305
- encoding: "utf-8"
306
- });
344
+ const result = spawnSync5("vercel", args, createVercelSpawnOptions("pipe"));
307
345
  if (result.error) {
308
346
  consola7.error(`\u90E8\u7F72\u5931\u8D25\u4E86: ${target.targetCWD}`);
309
347
  consola7.error(result.error);
@@ -321,16 +359,13 @@ function createDeployTask(config2, target) {
321
359
  import { spawnSync as spawnSync6 } from "child_process";
322
360
  import { concat as concat4 } from "lodash-es";
323
361
  import { consola as consola8 } from "consola";
324
- function createAliasTask(config2, vercelUrl, userUrl) {
362
+ function createAliasTask(config, vercelUrl, userUrl) {
325
363
  return {
326
364
  name: `Alias: ${userUrl}`,
327
365
  fn: async () => {
328
- const args = concat4(["alias", "set", vercelUrl, userUrl], getVercelTokenArg(config2), getVercelScopeArg(config2));
366
+ const args = concat4(["alias", "set", vercelUrl, userUrl], getVercelTokenArg(config), getVercelScopeArg(config));
329
367
  consola8.start(`\u5F00\u59CB\u522B\u540D\u4EFB\u52A1: ${userUrl}`);
330
- const result = spawnSync6("vercel", args, {
331
- encoding: "utf-8",
332
- stdio: "inherit"
333
- });
368
+ const result = spawnSync6("vercel", args, createVercelSpawnOptions());
334
369
  if (result.error) {
335
370
  consola8.error(`\u522B\u540D\u4EFB\u52A1\u5931\u8D25: ${userUrl}`);
336
371
  throw result.error;
@@ -344,19 +379,31 @@ function createAliasTask(config2, vercelUrl, userUrl) {
344
379
 
345
380
  // src/core/tasks/index.ts
346
381
  async function generateVercelNullConfig() {
347
- fs.writeFileSync(VERCEL_NULL_CONFIG_PATH, JSON.stringify(VERCEL_NULL_CONFIG, null, 2));
382
+ fs4.writeFileSync(VERCEL_NULL_CONFIG_PATH, JSON.stringify(VERCEL_NULL_CONFIG, null, 2));
348
383
  consola9.success(`\u751F\u6210 Vercel \u7A7A\u914D\u7F6E\u6587\u4EF6: ${VERCEL_NULL_CONFIG_PATH}`);
349
384
  }
350
- async function executeDeploymentWorkflow(config2) {
385
+ async function executeDeploymentWorkflow(config) {
351
386
  await generateVercelNullConfig();
352
- const { deployTargets } = config2;
387
+ const { deployTargets } = config;
388
+ const availableTargets = deployTargets.filter((target) => {
389
+ const targetPath = resolve5(target.targetCWD);
390
+ if (!fs4.existsSync(targetPath)) {
391
+ consola9.warn(`\u76EE\u6807\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7: ${target.targetCWD}`);
392
+ return false;
393
+ }
394
+ return true;
395
+ });
396
+ if (availableTargets.length === 0) {
397
+ consola9.error("\u6CA1\u6709\u53EF\u7528\u7684\u90E8\u7F72\u76EE\u6807\uFF0C\u8BF7\u5148\u6784\u5EFA\u4EA7\u7269");
398
+ return;
399
+ }
353
400
  await task("Vercel \u90E8\u7F72\u5DE5\u4F5C\u6D41", async ({ task: task2 }) => {
354
401
  await task2("1. Link \u9879\u76EE", async () => {
355
- const linkTasks = deployTargets.map((target) => createLinkTask(config2, target));
402
+ const linkTasks = availableTargets.map((target) => createLinkTask(config, target));
356
403
  await task2.group((task3) => linkTasks.map((t) => task3(t.name, t.fn)));
357
404
  });
358
405
  await task2("2. \u6784\u5EFA\u9879\u76EE", async () => {
359
- const buildTasks = deployTargets.filter(isNeedVercelBuild).map((target) => createBuildTask(config2, target));
406
+ const buildTasks = availableTargets.filter(isNeedVercelBuild).map((target) => createBuildTask(config, target));
360
407
  if (buildTasks.length === 0) {
361
408
  consola9.warn("\u6CA1\u6709\u9700\u8981\u6267\u884C build \u7684\u76EE\u6807");
362
409
  return;
@@ -364,11 +411,11 @@ async function executeDeploymentWorkflow(config2) {
364
411
  await task2.group((task3) => buildTasks.map((t) => task3(t.name, t.fn)));
365
412
  });
366
413
  await task2("3. \u6267\u884C AfterBuild \u4EFB\u52A1", async () => {
367
- const afterBuildTasks = createAfterBuildTasks(config2);
414
+ const afterBuildTasks = createAfterBuildTasks(config);
368
415
  await executeSequential("AfterBuild", afterBuildTasks);
369
416
  });
370
417
  await task2("4. \u6267\u884C\u7528\u6237\u547D\u4EE4\u4E0E\u6587\u4EF6\u590D\u5236", async () => {
371
- const targetTasks = deployTargets.map((target) => ({
418
+ const targetTasks = availableTargets.map((target) => ({
372
419
  name: `\u5904\u7406\u76EE\u6807: ${target.targetCWD}`,
373
420
  fn: async () => {
374
421
  if (!isDeployTargetWithUserCommands(target)) {
@@ -388,14 +435,14 @@ async function executeDeploymentWorkflow(config2) {
388
435
  await task2.group((task3) => targetTasks.map((t) => task3(t.name, t.fn)));
389
436
  });
390
437
  await task2("5. \u90E8\u7F72\u4E0E\u8BBE\u7F6E\u522B\u540D", async () => {
391
- const deployAliasTasks = deployTargets.map((target) => ({
438
+ const deployAliasTasks = availableTargets.map((target) => ({
392
439
  name: `\u90E8\u7F72\u4E0E\u522B\u540D: ${target.targetCWD}`,
393
440
  fn: async () => {
394
- const deployTask = createDeployTask(config2, target);
441
+ const deployTask = createDeployTask(config, target);
395
442
  const deployResult = await task2(deployTask.name, deployTask.fn);
396
443
  const vercelUrl = deployResult.result;
397
444
  if (target.url && target.url.length > 0) {
398
- const aliasTasks = target.url.map((userUrl) => createAliasTask(config2, vercelUrl, userUrl));
445
+ const aliasTasks = target.url.map((userUrl) => createAliasTask(config, vercelUrl, userUrl));
399
446
  await task2.group((task3) => aliasTasks.map((t) => task3(t.name, t.fn)));
400
447
  } else {
401
448
  consola9.warn(`\u76EE\u6807 ${target.targetCWD} \u6CA1\u6709\u914D\u7F6E\u522B\u540D`);
@@ -411,14 +458,20 @@ async function executeDeploymentWorkflow(config2) {
411
458
  // src/commands/deploy.ts
412
459
  import { Command } from "commander";
413
460
  import { consola as consola10 } from "consola";
461
+ import { config as dotenvxConfig2 } from "@dotenvx/dotenvx";
414
462
  function createDeployCommand() {
415
463
  const command = new Command("deploy");
416
- command.description("\u90E8\u7F72\u9879\u76EE\u5230 Vercel").action(async () => {
464
+ command.description("\u90E8\u7F72\u9879\u76EE\u5230 Vercel").option("--env-path <path>", "\u6307\u5B9A dotenv \u6587\u4EF6\u8DEF\u5F84\uFF0C\u7528\u4E8E\u8986\u76D6\u9ED8\u8BA4\u73AF\u5883\u53D8\u91CF").action(async (options) => {
417
465
  try {
466
+ if (options?.envPath) {
467
+ process.env.VERCEL_DEPLOY_TOOL_ENV_PATH = options.envPath;
468
+ dotenvxConfig2({ path: options.envPath });
469
+ consola10.info(`\u5DF2\u4ECE --env-path \u52A0\u8F7D\u73AF\u5883\u53D8\u91CF: ${options.envPath}`);
470
+ }
418
471
  consola10.start("\u5F00\u59CB\u52A0\u8F7D\u914D\u7F6E...");
419
- const config2 = await loadConfig();
472
+ const config = await loadConfig();
420
473
  consola10.start("\u5F00\u59CB\u6267\u884C\u90E8\u7F72\u5DE5\u4F5C\u6D41...");
421
- await executeDeploymentWorkflow(config2);
474
+ await executeDeploymentWorkflow(config);
422
475
  consola10.success("\u90E8\u7F72\u5B8C\u6210\uFF01");
423
476
  } catch (error) {
424
477
  consola10.error("\u90E8\u7F72\u5931\u8D25:");
@@ -492,7 +545,6 @@ export {
492
545
  VERCEL_NULL_CONFIG,
493
546
  VERCEL_NULL_CONFIG_PATH,
494
547
  VERCEL_OUTPUT_STATIC,
495
- config,
496
548
  createDeployCommand,
497
549
  createInitCommand,
498
550
  defineConfig,
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@ruan-cat/vercel-deploy-tool",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "阮喵喵自用的vercel部署工具,用于实现复杂项目的部署。",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
8
  "homepage": "https://vercel-deploy-tool.ruancat6312.top",
9
9
  "bugs": {
10
10
  "url": "https://github.com/ruan-cat/monorepo/issues"
package/src/cli.ts CHANGED
@@ -50,5 +50,12 @@ program.on("--help", () => {
50
50
  console.log("");
51
51
  });
52
52
 
53
- // 解析命令行参数
54
- program.parse();
53
+ async function main() {
54
+ // 解析命令行参数(异步,确保子命令的 async action 被执行并正确传递异常)
55
+ await program.parseAsync();
56
+ }
57
+
58
+ main().catch((error) => {
59
+ console.error(error);
60
+ process.exit(1);
61
+ });
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { consola } from "consola";
3
+ import { config as dotenvxConfig } from "@dotenvx/dotenvx";
3
4
  import { loadConfig } from "../config/loader";
4
5
  import { executeDeploymentWorkflow } from "../core/tasks";
5
6
 
@@ -17,23 +18,33 @@ import { executeDeploymentWorkflow } from "../core/tasks";
17
18
  export function createDeployCommand(): Command {
18
19
  const command = new Command("deploy");
19
20
 
20
- command.description("部署项目到 Vercel").action(async () => {
21
- try {
22
- consola.start("开始加载配置...");
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
+ }
23
32
 
24
- const config = await loadConfig();
33
+ consola.start("开始加载配置...");
25
34
 
26
- consola.start("开始执行部署工作流...");
35
+ const config = await loadConfig();
27
36
 
28
- await executeDeploymentWorkflow(config);
37
+ consola.start("开始执行部署工作流...");
29
38
 
30
- consola.success("部署完成!");
31
- } catch (error) {
32
- consola.error("部署失败:");
33
- consola.error(error);
34
- process.exit(1);
35
- }
36
- });
39
+ await executeDeploymentWorkflow(config);
40
+
41
+ consola.success("部署完成!");
42
+ } catch (error) {
43
+ consola.error("部署失败:");
44
+ consola.error(error);
45
+ process.exit(1);
46
+ }
47
+ });
37
48
 
38
49
  return command;
39
50
  }
@@ -1,7 +1,7 @@
1
1
  import { loadConfig as c12LoadConfig } from "c12";
2
- import { resolve } from "pathe";
3
2
  import { consola } from "consola";
4
3
  import { isUndefined } from "lodash-es";
4
+ import { config as dotenvxConfig } from "@dotenvx/dotenvx";
5
5
  import { printFormat } from "@ruan-cat/utils";
6
6
  import type { VercelDeployConfig } from "./schema";
7
7
 
@@ -20,7 +20,12 @@ const DEFAULT_CONFIG: VercelDeployConfig = {
20
20
  /**
21
21
  * 异步加载配置(工厂函数模式)
22
22
  * @description
23
- * 从约定俗成的配置处,获得用户配置文件
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* 文件
24
29
  *
25
30
  * @example
26
31
  * ```ts
@@ -28,13 +33,26 @@ const DEFAULT_CONFIG: VercelDeployConfig = {
28
33
  * ```
29
34
  */
30
35
  export async function loadConfig(): Promise<VercelDeployConfig> {
31
- const { config } = await c12LoadConfig<VercelDeployConfig>({
32
- cwd: resolve("."),
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(),
33
49
  name: CONFIG_NAME,
34
50
  dotenv: true,
35
51
  defaults: DEFAULT_CONFIG,
36
52
  });
37
53
 
54
+ config = loaded.config;
55
+
38
56
  // 环境变量覆盖
39
57
  const vercelOrgId = process.env.VERCEL_ORG_ID;
40
58
  const vercelProjectId = process.env.VERCEL_PROJECT_ID;
@@ -56,30 +74,22 @@ export async function loadConfig(): Promise<VercelDeployConfig> {
56
74
  return config;
57
75
  }
58
76
 
59
- /**
60
- * 默认导出:已初始化的配置实例(top-level await)
61
- * @description
62
- * 这是混合模式的一部分,提供默认的配置实例
63
- *
64
- * @example
65
- * ```ts
66
- * import { config } from "@ruan-cat/vercel-deploy-tool/config/loader";
67
- * console.log(config.vercelProjectName);
68
- * ```
69
- */
70
- export const config = await loadConfig();
77
+ let cachedConfigPromise: Promise<VercelDeployConfig> | null = null;
71
78
 
72
79
  /**
73
- * 导出配置获取函数
80
+ * 导出配置获取函数(带缓存)
74
81
  * @description
75
- * 获取已加载的配置实例
82
+ * 首次调用会触发加载,后续复用结果。避免 top-level await。
76
83
  *
77
84
  * @example
78
85
  * ```ts
79
86
  * import { getConfig } from "@ruan-cat/vercel-deploy-tool";
80
- * const config = getConfig();
87
+ * const config = await getConfig();
81
88
  * ```
82
89
  */
83
- export function getConfig(): VercelDeployConfig {
84
- return config;
90
+ export async function getConfig(): Promise<VercelDeployConfig> {
91
+ if (!cachedConfigPromise) {
92
+ cachedConfigPromise = loadConfig();
93
+ }
94
+ return cachedConfigPromise;
85
95
  }
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import { concat } from "lodash-es";
3
3
  import { consola } from "consola";
4
4
  import type { VercelDeployConfig } from "../../config/schema";
5
- import { getVercelTokenArg, getVercelScopeArg } from "../vercel";
5
+ import { createVercelSpawnOptions, getVercelTokenArg, getVercelScopeArg } from "../vercel";
6
6
 
7
7
  /**
8
8
  * 创建 Alias 任务
@@ -21,10 +21,7 @@ export function createAliasTask(config: VercelDeployConfig, vercelUrl: string, u
21
21
 
22
22
  consola.start(`开始别名任务: ${userUrl}`);
23
23
 
24
- const result = spawnSync("vercel", args, {
25
- encoding: "utf-8",
26
- stdio: "inherit",
27
- });
24
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
28
25
 
29
26
  if (result.error) {
30
27
  consola.error(`别名任务失败: ${userUrl}`);
@@ -1,8 +1,10 @@
1
+ import fs from "node:fs";
2
+ import { resolve } from "node:path";
1
3
  import { spawnSync } from "node:child_process";
2
4
  import { concat } from "lodash-es";
3
5
  import { consola } from "consola";
4
6
  import type { VercelDeployConfig, DeployTarget } from "../../config/schema";
5
- import { getVercelTokenArg, getVercelLocalConfigArg, getTargetCWDArg } from "../vercel";
7
+ import { createVercelSpawnOptions, getVercelTokenArg, getVercelLocalConfigArg, getTargetCWDArg } from "../vercel";
6
8
 
7
9
  /**
8
10
  * 创建 Build 任务
@@ -15,6 +17,14 @@ export function createBuildTask(config: VercelDeployConfig, target: DeployTarget
15
17
  return {
16
18
  name: `Build: ${target.targetCWD}`,
17
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
+
18
28
  const args = concat(
19
29
  ["build"],
20
30
  ["--yes"],
@@ -26,10 +36,7 @@ export function createBuildTask(config: VercelDeployConfig, target: DeployTarget
26
36
 
27
37
  consola.start(`开始 build 任务: ${target.targetCWD}`);
28
38
 
29
- const result = spawnSync("vercel", args, {
30
- encoding: "utf-8",
31
- stdio: "inherit",
32
- });
39
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
33
40
 
34
41
  if (result.error) {
35
42
  consola.error(`build 任务失败: ${target.targetCWD}`);
@@ -1,8 +1,10 @@
1
+ import fs from "node:fs";
2
+ import { resolve } from "node:path";
1
3
  import { spawnSync } from "node:child_process";
2
4
  import { concat } from "lodash-es";
3
5
  import { consola } from "consola";
4
6
  import type { VercelDeployConfig, DeployTarget } from "../../config/schema";
5
- import { getVercelTokenArg, getTargetCWDArg } from "../vercel";
7
+ import { createVercelSpawnOptions, getVercelTokenArg, getTargetCWDArg } from "../vercel";
6
8
 
7
9
  /**
8
10
  * 创建 Deploy 任务
@@ -15,6 +17,14 @@ export function createDeployTask(config: VercelDeployConfig, target: DeployTarge
15
17
  return {
16
18
  name: `Deploy: ${target.targetCWD}`,
17
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
+
18
28
  const args = concat(
19
29
  ["deploy"],
20
30
  ["--yes"],
@@ -26,9 +36,7 @@ export function createDeployTask(config: VercelDeployConfig, target: DeployTarge
26
36
 
27
37
  consola.start(`开始部署任务: ${target.targetCWD}`);
28
38
 
29
- const result = spawnSync("vercel", args, {
30
- encoding: "utf-8",
31
- });
39
+ const result = spawnSync("vercel", args, createVercelSpawnOptions("pipe"));
32
40
 
33
41
  if (result.error) {
34
42
  consola.error(`部署失败了: ${target.targetCWD}`);
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import { resolve } from "node:path";
2
3
  import { consola } from "consola";
3
4
  import { task, executeSequential } from "../executor";
4
5
  import type { VercelDeployConfig } from "../../config/schema";
@@ -40,17 +41,34 @@ export async function executeDeploymentWorkflow(config: VercelDeployConfig) {
40
41
 
41
42
  const { deployTargets } = config;
42
43
 
44
+ // 过滤不存在的目标目录,避免后续 Vercel CLI 抛出 ENOENT
45
+ const availableTargets = deployTargets.filter((target) => {
46
+ const targetPath = resolve(target.targetCWD);
47
+
48
+ if (!fs.existsSync(targetPath)) {
49
+ consola.warn(`目标目录不存在,已跳过: ${target.targetCWD}`);
50
+ return false;
51
+ }
52
+
53
+ return true;
54
+ });
55
+
56
+ if (availableTargets.length === 0) {
57
+ consola.error("没有可用的部署目标,请先构建产物");
58
+ return;
59
+ }
60
+
43
61
  await task("Vercel 部署工作流", async ({ task }) => {
44
62
  // 1. Link 阶段(并行)
45
63
  await task("1. Link 项目", async () => {
46
- const linkTasks = deployTargets.map((target) => createLinkTask(config, target));
64
+ const linkTasks = availableTargets.map((target) => createLinkTask(config, target));
47
65
 
48
66
  await task.group((task) => linkTasks.map((t) => task(t.name, t.fn)));
49
67
  });
50
68
 
51
69
  // 2. Build 阶段(并行)
52
70
  await task("2. 构建项目", async () => {
53
- const buildTasks = deployTargets.filter(isNeedVercelBuild).map((target) => createBuildTask(config, target));
71
+ const buildTasks = availableTargets.filter(isNeedVercelBuild).map((target) => createBuildTask(config, target));
54
72
 
55
73
  if (buildTasks.length === 0) {
56
74
  consola.warn("没有需要执行 build 的目标");
@@ -69,7 +87,7 @@ export async function executeDeploymentWorkflow(config: VercelDeployConfig) {
69
87
 
70
88
  // 4. UserCommands + CopyDist 阶段(并行目标,串行步骤)
71
89
  await task("4. 执行用户命令与文件复制", async () => {
72
- const targetTasks = deployTargets.map((target) => ({
90
+ const targetTasks = availableTargets.map((target) => ({
73
91
  name: `处理目标: ${target.targetCWD}`,
74
92
  fn: async () => {
75
93
  // 如果不是 userCommands 类型,跳过
@@ -98,7 +116,7 @@ export async function executeDeploymentWorkflow(config: VercelDeployConfig) {
98
116
 
99
117
  // 5. Deploy + Alias 阶段(并行目标,串行步骤)
100
118
  await task("5. 部署与设置别名", async () => {
101
- const deployAliasTasks = deployTargets.map((target) => ({
119
+ const deployAliasTasks = availableTargets.map((target) => ({
102
120
  name: `部署与别名: ${target.targetCWD}`,
103
121
  fn: async () => {
104
122
  // 5.1 部署
@@ -1,8 +1,10 @@
1
+ import fs from "node:fs";
2
+ import { resolve } from "node:path";
1
3
  import { spawnSync } from "node:child_process";
2
4
  import { concat } from "lodash-es";
3
5
  import { consola } from "consola";
4
6
  import type { VercelDeployConfig, DeployTarget } from "../../config/schema";
5
- import { getVercelProjectNameArg, getVercelTokenArg, getTargetCWDArg } from "../vercel";
7
+ import { createVercelSpawnOptions, getVercelProjectNameArg, getVercelTokenArg, getTargetCWDArg } from "../vercel";
6
8
 
7
9
  /**
8
10
  * 创建 Link 任务
@@ -15,6 +17,14 @@ export function createLinkTask(config: VercelDeployConfig, target: DeployTarget)
15
17
  return {
16
18
  name: `Link: ${target.targetCWD}`,
17
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
+
18
28
  const args = concat(
19
29
  ["link"],
20
30
  ["--yes"],
@@ -25,10 +35,7 @@ export function createLinkTask(config: VercelDeployConfig, target: DeployTarget)
25
35
 
26
36
  consola.start(`开始 link 任务: ${target.targetCWD}`);
27
37
 
28
- const result = spawnSync("vercel", args, {
29
- encoding: "utf-8",
30
- stdio: "inherit",
31
- });
38
+ const result = spawnSync("vercel", args, createVercelSpawnOptions());
32
39
 
33
40
  if (result.error) {
34
41
  consola.error(`link 任务失败: ${target.targetCWD}`);
@@ -1,11 +1,12 @@
1
1
  import type { VercelDeployConfig, DeployTarget } from "../config/schema";
2
2
  import { VERCEL_NULL_CONFIG_PATH } from "../utils/vercel-null-config";
3
+ import type { SpawnSyncOptions } from "node:child_process";
3
4
 
4
5
  /**
5
6
  * 获取 Vercel 项目名称参数
6
7
  */
7
8
  export function getVercelProjectNameArg(config: VercelDeployConfig): string[] {
8
- return ["--name", config.vercelProjectName];
9
+ return ["--project", config.vercelProjectName];
9
10
  }
10
11
 
11
12
  /**
@@ -35,3 +36,20 @@ export function getVercelLocalConfigArg(): string[] {
35
36
  export function getTargetCWDArg(target: DeployTarget): string[] {
36
37
  return ["--cwd", target.targetCWD];
37
38
  }
39
+
40
+ /**
41
+ * 统一的 Vercel CLI spawn 配置
42
+ * @param stdoutMode stdout 行为,默认继承终端;需要读取 stdout 时传入 "pipe"
43
+ */
44
+ export function createVercelSpawnOptions(stdoutMode: "inherit" | "pipe" = "inherit"): SpawnSyncOptions {
45
+ const base: SpawnSyncOptions = {
46
+ encoding: "utf8",
47
+ shell: process.platform === "win32",
48
+ };
49
+
50
+ if (stdoutMode === "pipe") {
51
+ return { ...base, stdio: ["inherit", "pipe", "inherit"] };
52
+ }
53
+
54
+ return { ...base, stdio: "inherit" };
55
+ }
package/src/index.ts CHANGED
@@ -39,7 +39,7 @@ export { defineConfig } from "./config/define-config";
39
39
  * const config = await loadConfig();
40
40
  * ```
41
41
  */
42
- export { loadConfig, getConfig, config } from "./config/loader";
42
+ export { loadConfig, getConfig } from "./config/loader";
43
43
 
44
44
  // ==================== 类型定义 ====================
45
45