vite-plugin-deploy-ftp 3.3.0 → 3.4.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
@@ -1,17 +1,6 @@
1
1
  # vite-plugin-deploy-ftp
2
2
 
3
- dist 目录上传到 FTP 服务器,支持单个或多个 FTP 服务器配置
4
-
5
- ## 介绍
6
-
7
- `vite-plugin-deploy-ftp` 是一个 Vite 插件,用于将打包后的文件上传到 FTP 服务器。插件支持:
8
-
9
- - 单个 FTP 服务器配置
10
- - 多个 FTP 服务器配置(可多选上传目标)
11
- - 自动备份远程文件
12
- - 连接重试机制
13
- - 选择性文件备份
14
- - 当前版本仅支持 ESM(`import`),不再提供 CommonJS(`require`)入口
3
+ Vite 打包后的目录上传到 FTP,适合不想手动打开 FTP 工具、重复拖文件发布的项目。
15
4
 
16
5
  ## 安装
17
6
 
@@ -19,72 +8,137 @@
19
8
  pnpm add vite-plugin-deploy-ftp -D
20
9
  ```
21
10
 
22
- ## 使用
11
+ ## 快速开始
23
12
 
24
- ### 单个 FTP 配置
13
+ 推荐用环境变量控制是否上传,默认本地普通打包不上传,只有明确开启时才发布。
14
+
15
+ ```bash
16
+ # .env
17
+ FTP_HOST=ftp.example.com
18
+ FTP_PORT=21
19
+ FTP_USER=username
20
+ FTP_PASSWORD=password
21
+ FTP_PATH=/public_html
22
+ FTP_ALIAS=https://example.com
23
+ DEPLOY_FTP=0
24
+ ```
25
25
 
26
26
  ```ts
27
27
  // vite.config.ts
28
+ import { defineConfig, loadEnv } from 'vite'
28
29
  import vitePluginDeployFtp from 'vite-plugin-deploy-ftp'
29
30
 
30
- export default {
31
- plugins: [
32
- vitePluginDeployFtp({
33
- open: true,
34
- host: 'ftp.example.com',
35
- port: 21,
36
- user: 'username',
37
- password: 'password',
38
- uploadPath: '/public_html',
39
- alias: 'https://example.com',
40
- singleBack: false,
41
- singleBackFiles: ['index.html'],
42
- maxRetries: 3,
43
- retryDelay: 1000,
44
- }),
45
- ],
46
- }
31
+ export default defineConfig(({ mode }) => {
32
+ const env = loadEnv(mode, process.cwd(), '')
33
+ const shouldDeploy = env.DEPLOY_FTP === '1'
34
+
35
+ return {
36
+ plugins: [
37
+ vitePluginDeployFtp({
38
+ open: shouldDeploy,
39
+ autoUpload: true,
40
+ failOnError: true,
41
+ host: env.FTP_HOST,
42
+ port: +(env.FTP_PORT || 21),
43
+ user: env.FTP_USER,
44
+ password: env.FTP_PASSWORD,
45
+ uploadPath: env.FTP_PATH?.split(',').map((path) => path.trim()) || '',
46
+ alias: env.FTP_ALIAS,
47
+ singleBack: true,
48
+ singleBackFiles: ['index.html'],
49
+ }),
50
+ ],
51
+ }
52
+ })
53
+ ```
54
+
55
+ 上传时再打开开关:
56
+
57
+ ```bash
58
+ # macOS / Linux
59
+ DEPLOY_FTP=1 pnpm build
60
+
61
+ # Windows PowerShell
62
+ $env:DEPLOY_FTP='1'; pnpm build
63
+ ```
64
+
65
+ `FTP_PATH` 可以写一个目录:
66
+
67
+ ```bash
68
+ FTP_PATH=/public_html
69
+ ```
70
+
71
+ 也可以写多个目录:
72
+
73
+ ```bash
74
+ FTP_PATH=/public_html,/backup_html
47
75
  ```
48
76
 
49
- ### 多个 FTP 配置
77
+ ## 常用配置说明
78
+
79
+ | 参数 | 说明 |
80
+ | ----------------- | -------------------------------------------------------------- |
81
+ | `open` | 是否启用上传。推荐用环境变量控制,避免普通打包时误上传。 |
82
+ | `autoUpload` | 是否跳过“是否上传”的确认。自动发布时建议设为 `true`。 |
83
+ | `failOnError` | 上传失败时是否让命令失败。发布流程里建议设为 `true`。 |
84
+ | `uploadPath` | 上传目录。支持字符串,也支持字符串数组,数组会上传到多个目录。 |
85
+ | `alias` | 访问域名。填写后,上传完成会输出可访问链接。 |
86
+ | `singleBack` | 是否只备份指定文件。通常备份 `index.html` 就够,速度更快。 |
87
+ | `singleBackFiles` | 单文件备份列表,支持子目录文件,例如 `assets/app.js`。 |
88
+ | `ftps` | 多个 FTP 服务器配置。需要发布到多个服务器时使用。 |
89
+ | `defaultFtp` | 多 FTP 时默认选中的服务器名称,可减少手动选择。 |
90
+ | `concurrency` | 同时上传的数量。服务器不稳定时保持默认值更稳。 |
91
+
92
+ ## 重要行为说明
93
+
94
+ - 插件只在 Vite 构建结束后上传。
95
+ - `open: false` 时不会上传,也不会检查 FTP 配置。
96
+ - `uploadPath` 传数组时,会把同一份文件依次上传到每个目录。
97
+ - 多 FTP 和多目录可以一起使用,会按“服务器 × 目录”的组合逐个上传。
98
+ - 上传前如果远端目录已有文件,会根据配置询问或执行备份。
99
+ - `singleBack: true` 时,只备份 `singleBackFiles` 里的文件。
100
+ - `autoUpload: false` 时,上传前会询问是否继续。
101
+ - 上传失败且 `failOnError: true` 时,构建命令会失败,方便发布系统拦截。
102
+ - 当前版本仅支持 ESM,也就是 `import`,不支持 `require`。
103
+
104
+ ## 风险提示
105
+
106
+ - 不建议把 FTP 用户名、密码直接写进 `vite.config.ts`,推荐放到环境变量里。
107
+ - 生产发布建议使用 `open` 环境变量开关,避免普通打包误传线上目录。
108
+ - `uploadPath` 写成数组时,会上传到多个目录,请确认每个目录都是预期目标。
109
+ - 完整备份会下载远端目录并重新上传压缩包,远端文件多时会比较慢。
110
+ - 如果 FTP 服务器不稳定,不建议把 `concurrency` 调太高。
111
+
112
+ ## 示例
113
+
114
+ ### 多个 FTP 服务器
50
115
 
51
116
  ```ts
52
- // vite.config.ts
53
117
  import vitePluginDeployFtp from 'vite-plugin-deploy-ftp'
54
118
 
55
119
  export default {
56
120
  plugins: [
57
121
  vitePluginDeployFtp({
58
- open: true,
122
+ open: process.env.DEPLOY_FTP === '1',
123
+ autoUpload: true,
59
124
  uploadPath: '/public_html',
60
- singleBack: false,
61
- singleBackFiles: ['index.html'],
62
- maxRetries: 3,
63
- retryDelay: 1000,
125
+ defaultFtp: 'production',
64
126
  ftps: [
65
127
  {
66
- name: '生产环境',
67
- host: 'ftp.production.com',
68
- port: 21,
69
- user: 'prod_user',
70
- password: 'prod_password',
71
- alias: 'https://production.com',
72
- },
73
- {
74
- name: '测试环境',
75
- host: 'ftp.test.com',
128
+ name: 'production',
129
+ host: process.env.FTP_PROD_HOST,
76
130
  port: 21,
77
- user: 'test_user',
78
- password: 'test_password',
79
- alias: 'https://test.com',
131
+ user: process.env.FTP_PROD_USER,
132
+ password: process.env.FTP_PROD_PASSWORD,
133
+ alias: 'https://example.com',
80
134
  },
81
135
  {
82
- name: '开发环境',
83
- host: 'ftp.dev.com',
136
+ name: 'test',
137
+ host: process.env.FTP_TEST_HOST,
84
138
  port: 21,
85
- user: 'dev_user',
86
- password: 'dev_password',
87
- alias: 'https://dev.com',
139
+ user: process.env.FTP_TEST_USER,
140
+ password: process.env.FTP_TEST_PASSWORD,
141
+ alias: 'https://test.example.com',
88
142
  },
89
143
  ],
90
144
  }),
@@ -92,23 +146,50 @@ export default {
92
146
  }
93
147
  ```
94
148
 
95
- ## 配置参数
149
+ ### 多个上传目录
150
+
151
+ ```ts
152
+ import vitePluginDeployFtp from 'vite-plugin-deploy-ftp'
153
+
154
+ export default {
155
+ plugins: [
156
+ vitePluginDeployFtp({
157
+ open: process.env.DEPLOY_FTP === '1',
158
+ autoUpload: true,
159
+ host: process.env.FTP_HOST,
160
+ user: process.env.FTP_USER,
161
+ password: process.env.FTP_PASSWORD,
162
+ uploadPath: ['/public_html', '/backup_html'],
163
+ alias: 'https://example.com',
164
+ }),
165
+ ],
166
+ }
167
+ ```
168
+
169
+ ## 完整配置表
96
170
 
97
171
  ### 通用参数
98
172
 
99
- | 参数 | 类型 | 默认值 | 说明 |
100
- | ----------------- | ---------- | ---------------- | -------------------------------- |
101
- | `open` | `boolean` | `true` | 是否启用插件 |
102
- | `uploadPath` | `string` | - | FTP 服务器上的上传路径 |
103
- | `singleBack` | `boolean` | `false` | 是否使用单文件备份模式 |
104
- | `singleBackFiles` | `string[]` | `['index.html']` | 单文件备份模式下要备份的文件列表 |
105
- | `maxRetries` | `number` | `3` | 连接失败时的最大重试次数 |
106
- | `retryDelay` | `number` | `1000` | 重试延迟时间(毫秒) |
173
+ | 参数 | 类型 | 默认值 | 说明 |
174
+ | ----------------- | -------------------- | ---------------- | ------------------------------------------------ |
175
+ | `open` | `boolean` | `true` | 是否启用插件 |
176
+ | `uploadPath` | `string \| string[]` | - | FTP 服务器上的上传路径,传数组时会上传到多个目录 |
177
+ | `singleBack` | `boolean` | `false` | 是否使用单文件备份模式 |
178
+ | `singleBackFiles` | `string[]` | `['index.html']` | 单文件备份模式下要备份的文件列表 |
179
+ | `debug` | `boolean` | `false` | 是否输出调试耗时 |
180
+ | `maxRetries` | `number` | `3` | 连接或上传失败时的最大重试次数 |
181
+ | `retryDelay` | `number` | `1000` | 重试延迟时间,单位毫秒 |
182
+ | `showBackFile` | `boolean` | `false` | 是否显示备份文件列表 |
183
+ | `autoUpload` | `boolean` | `false` | 是否跳过上传确认 |
184
+ | `fancy` | `boolean` | `true` | 是否使用更丰富的终端输出 |
185
+ | `failOnError` | `boolean` | `true` | 上传失败时是否中断构建命令 |
186
+ | `concurrency` | `number` | `1` | 同时上传的任务数量 |
107
187
 
108
188
  ### 单个 FTP 配置参数
109
189
 
110
190
  | 参数 | 类型 | 默认值 | 说明 |
111
191
  | ---------- | -------- | ------ | -------------------------- |
192
+ | `name` | `string` | - | FTP 配置名称 |
112
193
  | `host` | `string` | - | FTP 服务器地址 |
113
194
  | `port` | `number` | `21` | FTP 服务器端口 |
114
195
  | `user` | `string` | - | FTP 用户名 |
@@ -117,11 +198,12 @@ export default {
117
198
 
118
199
  ### 多个 FTP 配置参数
119
200
 
120
- | 参数 | 类型 | 说明 |
121
- | ------ | ------------- | ------------------ |
122
- | `ftps` | `FtpConfig[]` | FTP 服务器配置数组 |
201
+ | 参数 | 类型 | 说明 |
202
+ | ------------ | ------------- | ----------------------- |
203
+ | `ftps` | `FtpConfig[]` | FTP 服务器配置数组 |
204
+ | `defaultFtp` | `string` | 默认使用的 FTP 配置名称 |
123
205
 
124
- #### FtpConfig 对象
206
+ ### FtpConfig 对象
125
207
 
126
208
  | 参数 | 类型 | 默认值 | 说明 |
127
209
  | ---------- | -------- | ------ | ---------------------------------- |
@@ -131,57 +213,3 @@ export default {
131
213
  | `user` | `string` | - | FTP 用户名 |
132
214
  | `password` | `string` | - | FTP 密码 |
133
215
  | `alias` | `string` | `''` | 网站别名,用于生成完整 URL |
134
-
135
- ## 功能特性
136
-
137
- ### 多服务器选择
138
-
139
- 当使用多个 FTP 配置时,插件会显示一个多选界面,让您选择要上传到哪些服务器:
140
-
141
- ```
142
- ? 选择要上传的FTP服务器(可多选)
143
- ❯ ◯ 生产环境
144
- ◯ 测试环境
145
- ◯ 开发环境
146
- ```
147
-
148
- ### 备份功能
149
-
150
- 插件提供两种备份模式:
151
-
152
- 1. **完整备份**: 将远程目录下的所有文件打包备份
153
- 2. **选择性备份**: 只备份指定的文件(通过 `singleBackFiles` 配置)
154
-
155
- ### 连接重试
156
-
157
- 当 FTP 连接失败时,插件会自动重试,您可以通过 `maxRetries` 和 `retryDelay` 参数控制重试行为。
158
-
159
- ## 环境变量示例
160
-
161
- 建议将敏感信息(如用户名和密码)放在环境变量中:
162
-
163
- ```bash
164
- # .env
165
- VITE_FTP_HOST=ftp.example.com
166
- VITE_FTP_PORT=21
167
- VITE_FTP_USER=username
168
- VITE_FTP_PASSWORD=password
169
- VITE_FTP_PATH=/public_html
170
- VITE_FTP_ALIAS=https://example.com
171
- ```
172
-
173
- ```ts
174
- // vite.config.ts
175
- export default {
176
- plugins: [
177
- vitePluginDeployFtp({
178
- host: process.env.VITE_FTP_HOST,
179
- port: +(process.env.VITE_FTP_PORT || 21),
180
- user: process.env.VITE_FTP_USER,
181
- password: process.env.VITE_FTP_PASSWORD,
182
- uploadPath: process.env.VITE_FTP_PATH,
183
- alias: process.env.VITE_FTP_ALIAS,
184
- }),
185
- ],
186
- }
187
- ```
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Plugin } from 'vite';
2
2
 
3
3
  interface BaseOption {
4
- uploadPath: string;
4
+ uploadPath: string | string[];
5
5
  singleBackFiles?: string[];
6
6
  singleBack?: boolean;
7
7
  debug?: boolean;
package/dist/index.js CHANGED
@@ -135,7 +135,8 @@ var renderPanel = (title, rows, tone = "info", footer) => {
135
135
  const paddedLabel = padVisual(row.label, labelWidth);
136
136
  const prefix = `${paddedLabel} `;
137
137
  const availableValueWidth = Math.max(8, innerWidth - stringWidth(prefix));
138
- contentLines.push(`${chalk3.gray(prefix)}${fitVisual(row.value, availableValueWidth)}`);
138
+ const value = row.preserveValue ? row.value : fitVisual(row.value, availableValueWidth);
139
+ contentLines.push(`${chalk3.gray(prefix)}${value}`);
139
140
  }
140
141
  }
141
142
  if (footer) {
@@ -144,7 +145,10 @@ var renderPanel = (title, rows, tone = "info", footer) => {
144
145
  }
145
146
  const top = color(`\u256D${"\u2500".repeat(innerWidth + 2)}\u256E`);
146
147
  const bottom = color(`\u2570${"\u2500".repeat(innerWidth + 2)}\u256F`);
147
- const body = contentLines.map((line) => `${color("\u2502")} ${fitVisual(line, innerWidth)} ${color("\u2502")}`).join("\n");
148
+ const body = contentLines.map((line) => {
149
+ const fittedLine = stringWidth(line) > innerWidth ? line : fitVisual(line, innerWidth);
150
+ return `${color("\u2502")} ${fittedLine} ${color("\u2502")}`;
151
+ }).join("\n");
148
152
  return `${top}
149
153
  ${body}
150
154
  ${bottom}`;
@@ -272,7 +276,8 @@ var renderBackupPanel = (summary) => {
272
276
  { label: "\u7ED3\u679C:", value: chalk5.green(`${summary.items.length} \u4E2A\u5907\u4EFD\u6587\u4EF6`) },
273
277
  ...previewItems.map((item, index) => ({
274
278
  label: `\u6587\u4EF6 ${index + 1}:`,
275
- value: chalk5.cyan(truncateTerminalText(item, 22))
279
+ value: chalk5.cyan(item),
280
+ preserveValue: true
276
281
  }))
277
282
  ];
278
283
  if (summary.items.length > previewItems.length) {
@@ -311,7 +316,12 @@ function vitePluginDeployFtp(option) {
311
316
  const isMultiFtp = "ftps" in safeOption;
312
317
  const ftpConfigs = isMultiFtp ? safeOption.ftps || [] : [{ ...safeOption, name: safeOption.name || safeOption.alias || safeOption.host }];
313
318
  const defaultFtp = isMultiFtp ? safeOption.defaultFtp : void 0;
314
- const normalizedUploadPath = normalizeFtpUploadPath(uploadPath);
319
+ const uploadPaths = Array.isArray(uploadPath) ? uploadPath : [uploadPath];
320
+ const normalizedUploadPaths = Array.from(
321
+ new Set(
322
+ uploadPaths.map((targetPath) => typeof targetPath === "string" ? normalizeFtpUploadPath(targetPath) : "/")
323
+ )
324
+ );
315
325
  let outDir = normalizePath2(path2.resolve("dist"));
316
326
  let upload = false;
317
327
  let buildFailed = false;
@@ -323,7 +333,14 @@ function vitePluginDeployFtp(option) {
323
333
  };
324
334
  const validateOptions = () => {
325
335
  const errors = [];
326
- if (!uploadPath) errors.push("uploadPath is required");
336
+ if (uploadPaths.length === 0) {
337
+ errors.push("uploadPath is required");
338
+ }
339
+ uploadPaths.forEach((targetPath, index) => {
340
+ if (typeof targetPath !== "string" || targetPath.trim() === "") {
341
+ errors.push(`uploadPath${uploadPaths.length > 1 ? `[${index}]` : ""} is required`);
342
+ }
343
+ });
327
344
  if (!Number.isInteger(maxRetries) || maxRetries < 1) errors.push("maxRetries must be >= 1");
328
345
  if (!Number.isFinite(retryDelay) || retryDelay < 0) errors.push("retryDelay must be >= 0");
329
346
  if (!Number.isInteger(concurrency) || concurrency < 1) errors.push("concurrency must be >= 1");
@@ -737,7 +754,7 @@ function vitePluginDeployFtp(option) {
737
754
  );
738
755
  return { results, debugEntries };
739
756
  };
740
- const deploySingleTarget = async (ftpConfig) => {
757
+ const deploySingleTarget = async (ftpConfig, normalizedUploadPath) => {
741
758
  const { host, port = 21, user, password, alias = "", name } = ftpConfig;
742
759
  const normalizedAlias = alias ? normalizeUrlLikeBase(alias) : "";
743
760
  if (!host || !user || !password) {
@@ -757,10 +774,11 @@ function vitePluginDeployFtp(option) {
757
774
  });
758
775
  const totalFiles = allFiles.length;
759
776
  const displayName = name || host;
777
+ const resultName = normalizedUploadPaths.length > 1 ? `${displayName} ${normalizedUploadPath}` : displayName;
760
778
  const startTime = Date.now();
761
779
  if (allFiles.length === 0) {
762
780
  console.log(`${getLogSymbol("warning")} \u6CA1\u6709\u627E\u5230\u9700\u8981\u4E0A\u4F20\u7684\u6587\u4EF6`);
763
- return { name: displayName, totalFiles: 0, failedCount: 0 };
781
+ return { name: resultName, totalFiles: 0, failedCount: 0 };
764
782
  }
765
783
  clearScreen();
766
784
  console.log(
@@ -903,7 +921,7 @@ function vitePluginDeployFtp(option) {
903
921
  }
904
922
  ];
905
923
  if (accessUrl) {
906
- resultRows.push({ label: "\u8BBF\u95EE:", value: chalk5.cyan(truncateTerminalText(accessUrl, 20)) });
924
+ resultRows.push({ label: "\u8BBF\u95EE:", value: chalk5.cyan(accessUrl), preserveValue: true });
907
925
  }
908
926
  if (failedCount > 0) {
909
927
  const failedItems = results.filter((result) => !result.success).slice(0, 2);
@@ -936,7 +954,7 @@ function vitePluginDeployFtp(option) {
936
954
  });
937
955
  console.log(renderDebugPanel(debugEntries));
938
956
  }
939
- return { name: displayName, totalFiles: results.length, failedCount };
957
+ return { name: resultName, totalFiles: results.length, failedCount };
940
958
  } catch (error) {
941
959
  if (preflightSpinner) preflightSpinner.stop();
942
960
  console.log(`
@@ -950,7 +968,7 @@ ${getLogSymbol("danger")} \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF
950
968
  console.log(renderDebugPanel(debugEntries));
951
969
  }
952
970
  return {
953
- name: displayName,
971
+ name: resultName,
954
972
  totalFiles,
955
973
  failedCount: totalFiles > 0 ? totalFiles : 1,
956
974
  error: error instanceof Error ? error : new Error(String(error))
@@ -1021,8 +1039,10 @@ ${getLogSymbol("danger")} \u4E0A\u4F20\u8FC7\u7A0B\u4E2D\u53D1\u751F\u9519\u8BEF
1021
1039
  }
1022
1040
  const deployResults = [];
1023
1041
  for (const ftpConfig of selectedConfigs) {
1024
- const targetResult = await deploySingleTarget(ftpConfig);
1025
- deployResults.push(targetResult);
1042
+ for (const normalizedUploadPath of normalizedUploadPaths) {
1043
+ const targetResult = await deploySingleTarget(ftpConfig, normalizedUploadPath);
1044
+ deployResults.push(targetResult);
1045
+ }
1026
1046
  }
1027
1047
  return deployResults;
1028
1048
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-deploy-ftp",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",