@zhipu/zp-cli 0.0.3 → 0.0.4

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
@@ -24,11 +24,13 @@ zpc check
24
24
  `zpc deploy` 若发现无 `~/.zpc` 部署记录、但 api/web 部署目录非空或已有本项目 nginx 配置,会交互询问:
25
25
 
26
26
  1. 清空目标文件夹并覆盖(全量,旧目录备份为带时间戳的 `.bak-*`)
27
- 2. 使用增量部署(api 保留 `public`,跳过 web)
27
+ 2. 使用增量部署(api 保留 `excludeOnIncremental` 指定项,默认 `public`;web 全量覆盖)
28
28
  3. 取消部署(默认;非交互环境同样默认取消)
29
29
 
30
30
  可选环境变量:`DEPLOY_DIR`、`DEPLOY_FORCE`;`ZPC_HOME` 覆盖 `~/.zpc`(部署状态记录);测试可用 `ZPC_API_ROOT` / `ZPC_WEB_ROOT` / `ZPC_NGINX_DEFAULT_D` / `ZPC_DEPLOY_SKIP_RUNTIME`。
31
31
 
32
+ `api.excludeOnIncremental`:增量部署时排除、不覆盖的目录或文件名列表,默认 `["public"]`;仅对 api 生效,指定项在增量部署后保留服务器上的既有内容。
33
+
32
34
  ## 部署约定
33
35
 
34
36
  `zpc.config` 中的 `projectName` 即下文 `<system-tag>`。
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as findConfigFile, c as loadConfig, l as templateFileName, n as SCHEMA_FILE_NAME, o as isConfigFormat, r as configFileName, s as listConfigFiles, t as CONFIG_FORMATS } from "./config-BZc9pg7J.mjs";
1
+ import { a as findConfigFile, c as loadConfig, l as templateFileName, n as SCHEMA_FILE_NAME, o as isConfigFormat, r as configFileName, s as listConfigFiles, t as CONFIG_FORMATS } from "./config-DzcldbBT.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import fs, { mkdtempSync, writeFileSync } from "node:fs";
4
4
  import path from "node:path";
@@ -8,7 +8,7 @@ import os, { tmpdir } from "node:os";
8
8
  import chalk from "chalk";
9
9
  import zlib from "node:zlib";
10
10
  //#region package.json
11
- var version = "0.0.3";
11
+ var version = "0.0.4";
12
12
  var description = "内部单命令部署工具";
13
13
  //#endregion
14
14
  //#region src/utils/deploy-paths.ts
@@ -351,8 +351,8 @@ function deployDir(src, dest, log) {
351
351
  }
352
352
  }
353
353
  const API_INCREMENTAL_FILES = ["package.json", "bootstrap.js"];
354
- /** api 增量部署:只替换 dist 与根部 package.json / bootstrap.js,不碰 public。 */
355
- function deployApiIncremental(src, dest, log) {
354
+ /** api 增量部署:只替换 dist 与根部 package.json / bootstrap.js,不覆盖 excludeOnIncremental 指定项。 */
355
+ function deployApiIncremental(src, dest, exclude, log) {
356
356
  const distSrc = path.join(src, "dist");
357
357
  if (!fs.existsSync(distSrc)) throw new Error(`[deploy] ${src} 缺少 dist,无法增量部署`);
358
358
  fs.mkdirSync(dest, { recursive: true });
@@ -378,8 +378,15 @@ function deployApiIncremental(src, dest, log) {
378
378
  if (!fs.existsSync(from)) throw new Error(`[deploy] ${src} 缺少 ${file},无法增量部署`);
379
379
  fs.cpSync(from, path.join(dest, file));
380
380
  }
381
- fs.mkdirSync(path.join(dest, "public"), { recursive: true });
382
- log(`增量部署 ${src} -> ${dest}(更新 dist、${API_INCREMENTAL_FILES.join("、")},保留 public)`);
381
+ for (const entry of exclude) {
382
+ const destEntry = path.join(dest, entry);
383
+ if (fs.existsSync(destEntry)) continue;
384
+ const srcEntry = path.join(src, entry);
385
+ if (fs.existsSync(srcEntry) && !fs.statSync(srcEntry).isDirectory()) continue;
386
+ fs.mkdirSync(destEntry, { recursive: true });
387
+ }
388
+ const skipText = exclude.length ? `,跳过 ${exclude.join("、")}` : "";
389
+ log(`增量部署 ${src} -> ${dest}(更新 dist、${API_INCREMENTAL_FILES.join("、")}${skipText})`);
383
390
  }
384
391
  async function installAndReloadApi(name, dest, item, log) {
385
392
  if (!fs.existsSync(path.join(dest, "package.json"))) throw new Error(`[deploy] ${dest} 缺少 package.json,无法 pnpm i`);
@@ -696,7 +703,7 @@ async function promptDeployResolution() {
696
703
  value: "full"
697
704
  },
698
705
  {
699
- name: "使用增量部署(api 保留 public,跳过 web)",
706
+ name: "使用增量部署(api 保留排除项,web 全量覆盖)",
700
707
  value: "incremental"
701
708
  },
702
709
  {
@@ -844,12 +851,13 @@ async function deployKind(stagingDir, items, kind, destRoot, projectName, increm
844
851
  if (!fs.existsSync(src)) throw new Error(`[deploy] 配置中有 ${kind}/${item.name},但压缩包中没有对应目录`);
845
852
  const dest = path.join(destRoot, projectName, item.name);
846
853
  const pm2Name = `${projectName}-${item.name}`;
847
- if (kind === "api" && incremental && fs.existsSync(dest)) deployApiIncremental(src, dest, log$2.info);
854
+ const apiItem = kind === "api" ? item : null;
855
+ if (apiItem && incremental && fs.existsSync(dest)) deployApiIncremental(src, dest, apiItem.excludeOnIncremental ?? [], log$2.info);
848
856
  else {
849
- if (kind === "api" && incremental && !fs.existsSync(dest)) log$2.warn(`${dest} 不存在,增量改为全量部署`);
857
+ if (apiItem && incremental && !fs.existsSync(dest)) log$2.warn(`${dest} 不存在,增量改为全量部署`);
850
858
  deployDir(src, dest, log$2.info);
851
859
  }
852
- if (kind === "api" && !shouldSkipDeployRuntime()) await installAndReloadApi(pm2Name, dest, item, log$2.info);
860
+ if (apiItem && !shouldSkipDeployRuntime()) await installAndReloadApi(pm2Name, dest, apiItem, log$2.info);
853
861
  }
854
862
  }
855
863
  async function runDeploy(dir, options, prompt = promptDeployResolution) {
@@ -898,12 +906,11 @@ async function runDeploy(dir, options, prompt = promptDeployResolution) {
898
906
  } else incremental = zipIsIncremental;
899
907
  }
900
908
  try {
901
- log$2.info(`模式: ${incremental ? "增量(api 保留 public,跳过 web)" : "全量"}`);
909
+ log$2.info(`模式: ${incremental ? "增量(api 保留排除项,web 全量覆盖)" : "全量"}`);
902
910
  if (!incremental && !hadRecord) log$2.info("首次部署该项目,完成后将记录到 ~/.zpc/deploy-state.json");
903
911
  if (force) log$2.warn("已启用 --force(允许抢占冲突的 nginx proxy)");
904
912
  await deployKind(stagingDir, config.api, "api", getApiRoot(), config.projectName, incremental);
905
- if (!incremental) await deployKind(stagingDir, config.web, "web", getWebRoot(), config.projectName, false);
906
- else if (config.web?.length) log$2.warn("增量模式跳过 web 部署");
913
+ await deployKind(stagingDir, config.web, "web", getWebRoot(), config.projectName, false);
907
914
  } finally {
908
915
  fs.rmSync(stagingDir, {
909
916
  recursive: true,
@@ -921,7 +928,7 @@ async function runDeploy(dir, options, prompt = promptDeployResolution) {
921
928
  else if (zipIsIncremental) log$2.info("已记录部署状态,下次 deploy 将默认增量");
922
929
  }
923
930
  const apiCount = config.api?.length ?? 0;
924
- const webCount = incremental ? 0 : config.web?.length ?? 0;
931
+ const webCount = config.web?.length ?? 0;
925
932
  const prefix = `${config.projectName}/<name>`;
926
933
  log$2.success(`完成: api ${apiCount} 个 -> ${getApiRoot()}/${prefix},web ${webCount} 个 -> ${getWebRoot()}/${prefix}`);
927
934
  }
@@ -1016,10 +1023,9 @@ function resolveIncrementalMode(projectRoot, config, options = {}) {
1016
1023
  //#endregion
1017
1024
  //#region src/commands/pack.ts
1018
1025
  const log = createLogger("pack");
1019
- function resolveProjects(root, config, incremental) {
1026
+ function resolveProjects(root, config) {
1020
1027
  const projects = [];
1021
- const kinds = incremental ? ["api"] : ["api", "web"];
1022
- for (const kind of kinds) {
1028
+ for (const kind of ["api", "web"]) {
1023
1029
  const items = config[kind];
1024
1030
  if (items == null) continue;
1025
1031
  for (const item of items) {
@@ -1035,7 +1041,7 @@ function resolveProjects(root, config, incremental) {
1035
1041
  });
1036
1042
  }
1037
1043
  }
1038
- if (projects.length === 0) throw new Error(incremental ? "[pack] 增量打包需要配置中至少有一个 api 项目" : "[pack] 配置中没有 api / web 项目");
1044
+ if (projects.length === 0) throw new Error("[pack] 配置中没有 api / web 项目");
1039
1045
  return projects;
1040
1046
  }
1041
1047
  function resolveBuildEnv(project, config) {
@@ -1101,7 +1107,8 @@ function stageApi(project, dest, incremental) {
1101
1107
  copyRequiredFile(path.join(project.absPath, "package.json"), dest, "package.json");
1102
1108
  copyRequiredFile(path.join(project.absPath, "bootstrap.js"), dest, "bootstrap.js");
1103
1109
  if (incremental) {
1104
- log.warn(`api/${project.name} 增量:跳过 public`);
1110
+ const excluded = project.excludeOnIncremental ?? [];
1111
+ log.warn(excluded.length ? `api/${project.name} 增量:跳过 ${excluded.join("、")}` : `api/${project.name} 增量:不排除任何目录/文件`);
1105
1112
  return;
1106
1113
  }
1107
1114
  const publicDir = path.join(project.absPath, "public");
@@ -1136,13 +1143,13 @@ async function runPack(dir, options) {
1136
1143
  log.info(`项目目录: ${projectRoot}`);
1137
1144
  log.info(`配置: ${file}`);
1138
1145
  log.info(`项目: ${config.projectName}`);
1139
- log.info(`模式: ${incremental ? "增量(仅 api,不含 public)" : "全量"}`);
1146
+ log.info(`模式: ${incremental ? "增量(api 不含排除项,web 全量)" : "全量"}`);
1140
1147
  if (packMode.existingDeployDirs.length > 0) {
1141
1148
  const listed = packMode.existingDeployDirs.map((dir) => path.normalize(dir)).join(", ");
1142
- log.warn(`无 ~/.zpc 部署记录,但检测到部署目录非空:${listed}。将按增量处理(api 保留 public,跳过 web);若确需全量请清空部署目录并删除 deploy-state 中对应记录`);
1149
+ log.warn(`无 ~/.zpc 部署记录,但检测到部署目录非空:${listed}。将按增量处理(api 保留排除项,web 全量覆盖);若确需全量请清空部署目录并删除 deploy-state 中对应记录`);
1143
1150
  }
1144
1151
  if (options.concurrency !== void 0) log.info(`并行上限: ${options.concurrency}`);
1145
- const projects = resolveProjects(projectRoot, config, incremental);
1152
+ const projects = resolveProjects(projectRoot, config);
1146
1153
  await buildAll(projects, config, options);
1147
1154
  stageDist(stagingDir, projectRoot, projects, incremental);
1148
1155
  try {
@@ -13,6 +13,8 @@ const CONFIG_FORMATS = [
13
13
  "cjs",
14
14
  "json"
15
15
  ];
16
+ /** 增量部署默认排除、不覆盖的目录/文件名。 */
17
+ const DEFAULT_EXCLUDE_ON_INCREMENTAL = ["public"];
16
18
  function defineConfig(config) {
17
19
  return config;
18
20
  }
@@ -66,9 +68,9 @@ function validateConfig(config, source) {
66
68
  function optionalAppList(value, kind, source) {
67
69
  if (value == null) return [];
68
70
  if (!Array.isArray(value)) throw new Error(`[config] ${source} 中 ${kind} 必须是数组`);
69
- return value.map((item, index) => parseAppConfig(item, `${source} ${kind}[${index}]`));
71
+ return value.map((item, index) => parseAppConfig(item, kind, `${source} ${kind}[${index}]`));
70
72
  }
71
- function parseAppConfig(item, label) {
73
+ function parseAppConfig(item, kind, label) {
72
74
  if (!isPlainObject(item)) throw new Error(`[config] ${label} 必须是对象`);
73
75
  if (typeof item.path !== "string" || item.path.trim() === "") throw new Error(`[config] ${label} 缺少 path`);
74
76
  if (!isSafeName(item.name)) throw new Error(`[config] ${label} 缺少合法 name`);
@@ -81,8 +83,15 @@ function parseAppConfig(item, label) {
81
83
  parsed.proxy = item.proxy;
82
84
  }
83
85
  if (item.buildEnv != null) parsed.buildEnv = parseEnvMap(item.buildEnv, `${label} buildEnv`);
84
- if (item.env != null) parsed.env = parseEnvMap(item.env, `${label} env`);
85
- return parsed;
86
+ if (kind === "web") return parsed;
87
+ const api = { ...parsed };
88
+ if (item.env != null) api.env = parseEnvMap(item.env, `${label} env`);
89
+ api.excludeOnIncremental = item.excludeOnIncremental != null ? parseExcludeOnIncremental(item.excludeOnIncremental, `${label} excludeOnIncremental`) : [...DEFAULT_EXCLUDE_ON_INCREMENTAL];
90
+ return api;
91
+ }
92
+ function parseExcludeOnIncremental(value, label) {
93
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || !isSafeName(entry))) throw new Error(`[config] ${label} 必须是字符串数组(目录/文件名,不含路径分隔符)`);
94
+ return value;
86
95
  }
87
96
  function parseEnvMap(value, label) {
88
97
  if (!isPlainObject(value)) throw new Error(`[config] ${label} 必须是对象`);
package/dist/index.d.mts CHANGED
@@ -2,6 +2,7 @@
2
2
  declare const CONFIG_FORMATS: readonly ["ts", "mts", "cts", "js", "mjs", "cjs", "json"];
3
3
  type ConfigFormat = (typeof CONFIG_FORMATS)[number];
4
4
  type ZpcEnvMap = Record<string, string | number | boolean>;
5
+ /** api / web 应用配置公共字段 */
5
6
  interface ZpcAppConfig {
6
7
  /** 相对项目根的源码目录 */
7
8
  path: string;
@@ -11,14 +12,21 @@ interface ZpcAppConfig {
11
12
  proxy?: string;
12
13
  /** 仅构建期注入 */
13
14
  buildEnv?: ZpcEnvMap;
15
+ }
16
+ /** api 应用配置(额外含运行时 env 与增量部署排除项) */
17
+ interface ZpcApiConfig extends ZpcAppConfig {
14
18
  /** 仅运行时注入(pm2) */
15
19
  env?: ZpcEnvMap;
20
+ /** 增量部署时排除、不覆盖的目录/文件名;默认 ["public"] */
21
+ excludeOnIncremental?: string[];
16
22
  }
23
+ /** web 应用配置(静态站点,仅构建期 buildEnv,无运行时 env) */
24
+ interface ZpcWebConfig extends ZpcAppConfig {}
17
25
  interface ZpcConfig {
18
26
  projectName: string;
19
- api?: ZpcAppConfig[];
20
- web?: ZpcAppConfig[];
27
+ api?: ZpcApiConfig[];
28
+ web?: ZpcWebConfig[];
21
29
  }
22
30
  declare function defineConfig(config: ZpcConfig): ZpcConfig;
23
31
  //#endregion
24
- export { type ConfigFormat, type ZpcAppConfig, type ZpcConfig, type ZpcEnvMap, defineConfig };
32
+ export { type ConfigFormat, type ZpcApiConfig, type ZpcAppConfig, type ZpcConfig, type ZpcEnvMap, type ZpcWebConfig, defineConfig };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { i as defineConfig } from "./config-BZc9pg7J.mjs";
1
+ import { i as defineConfig } from "./config-DzcldbBT.mjs";
2
2
  export { defineConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhipu/zp-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "内部单命令部署工具",
5
5
  "bin": {
6
6
  "zpc": "./dist/cli.mjs"
@@ -54,6 +54,15 @@
54
54
  "env": {
55
55
  "$ref": "#/definitions/envMap",
56
56
  "description": "仅运行时注入(pm2)"
57
+ },
58
+ "excludeOnIncremental": {
59
+ "type": "array",
60
+ "items": {
61
+ "type": "string",
62
+ "minLength": 1
63
+ },
64
+ "default": ["public"],
65
+ "description": "增量部署时排除、不覆盖的目录/文件名;仅 api 生效,默认 [\"public\"]"
57
66
  }
58
67
  }
59
68
  },