@xfe-repo/web-app 2.0.0-beta.3 → 2.0.0-beta.5

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
@@ -122,3 +122,37 @@ SSR `start` 使用与构建一致的隐藏工作目录,使官方 serve 正确
122
122
  构建会先加载 `react-app-polyfill/stable`,并在应用原有 browserslist 的基础上,补充 Edge 18、Chrome 64、Firefox 60、Safari/iOS 12 及以上的 JS 编译目标,业务代码和第三方依赖使用同一目标。
123
123
 
124
124
  这是 JS 兼容基线;具体组件样式和浏览器 Web API 仍需在目标浏览器验证。
125
+
126
+
127
+ ## 可选的项目部署模板
128
+
129
+ `xfe-web config` 仅合并并写入 values、复制公共模板,不解析或重建 Drone 步骤。`managed: true` 时写到项目 `deploy/`,不修改共享 npm 包模板;未启用时保持原有行为。
130
+
131
+ ```json
132
+ {
133
+ "deploy": {
134
+ "managed": true,
135
+ "droneConfigPath": "deploy/.drone.yml",
136
+ "targetK8s": "infra",
137
+ "targetNamespace": "deployment-prod",
138
+ "hostsProd": ["app.example.com"],
139
+ "values": {
140
+ "nodeVersion": "24.22.1",
141
+ "buildCommand": "pnpm check && pnpm build && pnpm prune --prod",
142
+ "environments": {"test": false, "stage": false, "prod": true, "stable": false},
143
+ "replicas": 1,
144
+ "strategy": {"type": "Recreate"},
145
+ "persistence": {"enabled": true, "size": "20Gi", "mountPath": "/data"},
146
+ "podSecurityContext": {"fsGroup": 1000},
147
+ "env": [{"name": "DATA_DIR", "value": "/data"}],
148
+ "envFrom": [{"secretRef": {"name": "application-env"}}]
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ `values` 中的对象递归合并,列表整体替换。默认 Node 22.22.0、`pnpm build` 和所有发布环境开启,保持既有应用行为。公共 Drone 保留固定步骤,每个发布步骤在运行时检查 `environments` 布尔开关,关闭时直接结束,不创建命名空间或发布资源。
155
+
156
+ 构建命令与 Docker 的 Node 参数由 Drone 直接读取生成的 values;实例、更新策略、PVC、环境变量、安全上下文、探针和路由由 Helm 读取。只有运行前就必须确定的 Node 镜像、集群 Secret,以及原有构建目录参数使用简单占位替换,不再有配置脚本中的流水线遍历或筛选。
157
+
158
+ PVC 默认关闭,支持 `existingClaim`、`storageClass`。`virtualService.timeout` 可配置长连接超时,`corsPolicy: null` 关闭默认跨域策略。应用负责自己的存储与认证业务变量,密钥通过环境来源注入,不写入 values 或构建命令。修改配置后重新生成并提交 Drone 入口。
package/deploy/.drone.yml CHANGED
@@ -18,7 +18,7 @@ resources:
18
18
  steps:
19
19
  - name: install
20
20
  pull: if-not-exists
21
- image: node:22.22.0-bookworm
21
+ image: node:<<NODE_VERSION>>-bookworm
22
22
  volumes:
23
23
  - name: node-modules
24
24
  path: /drone/src/node_modules
@@ -30,7 +30,7 @@ steps:
30
30
 
31
31
  - name: build
32
32
  pull: if-not-exists
33
- image: node:22.22.0-bookworm
33
+ image: node:<<NODE_VERSION>>-bookworm
34
34
  resources:
35
35
  limits:
36
36
  memory: 8192MiB
@@ -40,13 +40,31 @@ steps:
40
40
  commands:
41
41
  - corepack enable
42
42
  - corepack prepare pnpm@10.30.3 --activate
43
- - cp -r node_modules/@xfe-repo/web-app/deploy deploy
43
+ - |
44
+ if [ "$(node -p "Boolean(require('./xfe.json').deploy?.managed)")" = true ]; then
45
+ pnpm exec xfe-web config
46
+ else
47
+ cp -r node_modules/@xfe-repo/web-app/deploy deploy
48
+ fi
44
49
  - export NODE_OPTIONS="--max-old-space-size=8192"
45
- - export XFE_RELEASE=$DRONE_REPO_NAME@${DRONE_TAG:CI_COMMIT_SHA}
50
+ - export XFE_RELEASE="$DRONE_REPO_NAME@$DRONE_COMMIT_SHA"
51
+ - if [ -n "$DRONE_TAG" ]; then export XFE_RELEASE="$DRONE_REPO_NAME@$DRONE_TAG"; fi
46
52
  - export REACT_APP_RELEASE=$XFE_RELEASE
47
53
  - printf '%s' "$XFE_RELEASE" > deploy/static/release
48
54
  - cp deploy/static/release deploy/static/check-update
49
- - pnpm build
55
+ - |
56
+ node <<'NODE'
57
+ const fs = require('node:fs');
58
+ const {createRequire} = require('node:module');
59
+ const yaml = createRequire(require.resolve('@xfe-repo/web-app/package.json'))('yaml');
60
+ const values = yaml.parse(fs.readFileSync('deploy/helm/values.yaml', 'utf8'));
61
+ const dockerfile = fs.readFileSync('deploy/Dockerfile', 'utf8');
62
+ fs.writeFileSync('deploy/Dockerfile', dockerfile.replace(/^ARG XFE_NODE_VERSION=.*$/m, 'ARG XFE_NODE_VERSION=' + values.nodeVersion));
63
+ const {spawnSync} = require('node:child_process');
64
+ const result = spawnSync(values.buildCommand, {shell: true, stdio: 'inherit'});
65
+ if (result.error) throw result.error;
66
+ process.exit(result.status ?? 1);
67
+ NODE
50
68
  - cp -a node_modules deploy/modules
51
69
 
52
70
  - name: image-build
@@ -70,26 +88,31 @@ steps:
70
88
  - name: pre-deploy
71
89
  image: xhj-prod-registry-vpc.cn-hangzhou.cr.aliyuncs.com/xhj-image-common/deploy-helm:latest
72
90
  environment:
73
- SAAS_K8S_CONFIG:
74
- from_secret: saas-k8s-config
75
- INFRA_K8S_CONFIG:
76
- from_secret: infra-k8s-config
91
+ K8S_CONFIG:
92
+ from_secret: target-k8s-config
77
93
  commands:
78
- # 判断values.yaml中的targetK8s字段,选择目标集群
79
- - export TARGET_K8S=$(cat deploy/helm/values.yaml | grep targetK8s | awk '{print $2}' | sed 's/\"//g')
80
- # 将$INFRA_K8S_CONFIG或$SAAS_K8S_CONFIG写入kube_config
81
- - echo "$TARGET_K8S" | grep -q "infra" && echo "$INFRA_K8S_CONFIG" > kube_config || echo "$SAAS_K8S_CONFIG" > kube_config
94
+ # 集群凭据通过生成入口选定,运行流程不再注入其他集群的凭据。
95
+ - umask 077
96
+ - printf '%s' "$K8S_CONFIG" > kube_config
82
97
  # 将环境类型写入values.yaml
83
98
  - appName="`echo ${DRONE_REPO//\//-}`"
84
99
  - sed -i "s/<<APP_NAME>>/$appName/g" deploy/helm/values.yaml
85
100
  - sed -i "s/<<CI_COMMIT_SHA>>/$CI_COMMIT_SHA/g" deploy/helm/values.yaml
86
101
  - sed -i "s/<<TAG_CONTENT>>/$DRONE_TAG/g" deploy/helm/values.yaml
87
- # 展示values.yaml 与 TARGET_K8S
88
- - cat deploy/helm/values.yaml && echo $TARGET_K8S
102
+ # 展示非敏感部署配置
103
+ - cat deploy/helm/values.yaml
89
104
 
90
105
  - name: deploy-to-test
91
106
  image: xhj-prod-registry-vpc.cn-hangzhou.cr.aliyuncs.com/xhj-image-common/deploy-helm:latest
107
+ environment:
108
+ DEPLOY_ENV: test
92
109
  commands:
110
+ - &environmentEnabled |
111
+ test -f deploy/helm/values.yaml || exit 1
112
+ if ! sed -n '/^environments:/,/^[^ ]/p' deploy/helm/values.yaml | grep -q "^ $DEPLOY_ENV: true$"; then
113
+ echo "跳过未启用的发布环境:$DEPLOY_ENV"
114
+ exit 0
115
+ fi
93
116
  - export KUBECONFIG=$PWD/kube_config
94
117
  - cp deploy/helm/values.yaml deploy/helm/values.test.yaml
95
118
  - sed -i "s/<<ENV_TYPE>>/test/g" deploy/helm/values.test.yaml
@@ -106,7 +129,10 @@ steps:
106
129
 
107
130
  - name: deploy-to-stage
108
131
  image: xhj-prod-registry-vpc.cn-hangzhou.cr.aliyuncs.com/xhj-image-common/deploy-helm:latest
132
+ environment:
133
+ DEPLOY_ENV: stage
109
134
  commands:
135
+ - *environmentEnabled
110
136
  - cp deploy/helm/values.yaml deploy/helm/values.stage.yaml
111
137
  - sed -i "s/<<ENV_TYPE>>/stage/g" deploy/helm/values.stage.yaml
112
138
  - sed -i "s/<<APP_SUFFIX>>//g" deploy/helm/values.stage.yaml
@@ -120,13 +146,24 @@ steps:
120
146
 
121
147
  - name: deploy-to-prod
122
148
  image: xhj-prod-registry-vpc.cn-hangzhou.cr.aliyuncs.com/xhj-image-common/deploy-helm:latest
149
+ environment:
150
+ DEPLOY_ENV: prod
123
151
  commands:
152
+ - *environmentEnabled
124
153
  - cp deploy/helm/values.yaml deploy/helm/values.prod.yaml
125
154
  - sed -i "s/<<ENV_TYPE>>/prod/g" deploy/helm/values.prod.yaml
126
155
  - sed -i "s/<<APP_SUFFIX>>//g" deploy/helm/values.prod.yaml
127
156
  - sed -i "s/<<HOST_PREFIX>>//g" deploy/helm/values.prod.yaml
128
157
  - helm template deploy/helm --values deploy/helm/values.prod.yaml
129
- - helm template deploy/helm --values deploy/helm/values.prod.yaml | kubectl --kubeconfig=kube_config apply -f -
158
+ - |
159
+ if grep -q '^managed: true$' deploy/helm/values.yaml; then
160
+ namespace=$(awk '$1 == "namespace:" { gsub(/[\047\042]/, "", $2); print $2 }' deploy/helm/values.yaml)
161
+ if [ -z "$namespace" ]; then namespace=deployment-prod; fi
162
+ chart_name=$(printf '%s' "$DRONE_REPO" | tr '/' '-')
163
+ helm upgrade --install "$chart_name" deploy/helm --values deploy/helm/values.prod.yaml --namespace "$namespace" --create-namespace --kubeconfig=kube_config --atomic --wait --timeout 5m
164
+ else
165
+ helm template deploy/helm --values deploy/helm/values.prod.yaml | kubectl --kubeconfig=kube_config apply -f -
166
+ fi
130
167
  when:
131
168
  ref:
132
169
  include:
@@ -135,7 +172,10 @@ steps:
135
172
  - name: deploy-to-stable
136
173
  image: xhj-prod-registry-vpc.cn-hangzhou.cr.aliyuncs.com/xhj-image-common/deploy-helm:latest
137
174
  failure: ignore
175
+ environment:
176
+ DEPLOY_ENV: stable
138
177
  commands:
178
+ - *environmentEnabled
139
179
  - cp deploy/helm/values.yaml deploy/helm/values.test.yaml
140
180
  - sed -i "s/<<ENV_TYPE>>/test/g" deploy/helm/values.test.yaml
141
181
  - sed -i "s/<<APP_SUFFIX>>/-stable/g" deploy/helm/values.test.yaml
@@ -150,11 +190,13 @@ steps:
150
190
  - name: push-helm-chart
151
191
  image: xhj-prod-registry-vpc.cn-hangzhou.cr.aliyuncs.com/xhj-image-common/deploy-helm:latest
152
192
  environment:
193
+ DEPLOY_ENV: prod
153
194
  USERNAME:
154
195
  from_secret: username
155
196
  PASSWORD:
156
197
  from_secret: password
157
198
  commands:
199
+ - *environmentEnabled
158
200
  - chartName="${DRONE_REPO//\//-}"
159
201
  - chartVersion="0.0.0-$DRONE_TAG"
160
202
  - cp deploy/helm/values.prod.yaml deploy/helm/values.yaml
@@ -183,15 +225,9 @@ image_pull_secrets:
183
225
 
184
226
  ---
185
227
  kind: secret
186
- name: saas-k8s-config
187
- get:
188
- path: saas-k8s-config
189
- name: config
190
- ---
191
- kind: secret
192
- name: infra-k8s-config
228
+ name: target-k8s-config
193
229
  get:
194
- path: infra-k8s-config
230
+ path: <<TARGET_K8S>>-k8s-config
195
231
  name: config
196
232
  ---
197
233
  kind: secret
package/deploy/Dockerfile CHANGED
@@ -1,4 +1,5 @@
1
- FROM node:22.22.0-bookworm-slim
1
+ ARG XFE_NODE_VERSION=22.22.0
2
+ FROM node:${XFE_NODE_VERSION}-bookworm-slim
2
3
 
3
4
  WORKDIR /app
4
5
 
@@ -0,0 +1,10 @@
1
+ # 构建产物和 deploy/modules 是运行镜像输入,不能排除。
2
+ node_modules/*
3
+ .env*
4
+ .git
5
+ .idea
6
+ .data
7
+ .impeccable
8
+ design
9
+ coverage
10
+ *.tsbuildinfo
@@ -1,4 +1,4 @@
1
- apiVersion: v1
1
+ apiVersion: v2
2
2
  name: deployment
3
3
  description: A Helm chart for xfe
4
4
 
@@ -7,11 +7,15 @@ metadata:
7
7
  kubernetes.io/change-cause: {{ or $.Values.tagContent $.Values.commitSHA }}
8
8
  spec:
9
9
  strategy:
10
+ {{- if .Values.strategy }}
11
+ {{- toYaml .Values.strategy | nindent 4 }}
12
+ {{- else }}
10
13
  type: RollingUpdate
11
14
  rollingUpdate:
12
15
  maxSurge: 2
13
16
  maxUnavailable: 50%
14
- replicas: {{ if eq $.Values.envType "prod" }} 2 {{ else }} 1 {{ end }}
17
+ {{- end }}
18
+ replicas: {{ if hasKey .Values "replicas" }}{{ .Values.replicas }}{{ else if eq $.Values.envType "prod" }} 2 {{ else }} 1 {{ end }}
15
19
  selector:
16
20
  matchLabels:
17
21
  app: {{ $.Values.app }}
@@ -20,6 +24,13 @@ spec:
20
24
  labels:
21
25
  app: {{ $.Values.app }}
22
26
  spec:
27
+ {{- with .Values.podSecurityContext }}
28
+ securityContext:
29
+ {{- toYaml . | nindent 8 }}
30
+ {{- end }}
31
+ {{- with .Values.terminationGracePeriodSeconds }}
32
+ terminationGracePeriodSeconds: {{ . }}
33
+ {{- end }}
23
34
  imagePullSecrets:
24
35
  - name: {{ $.Values.imagePullSecret }}
25
36
  containers:
@@ -27,9 +38,17 @@ spec:
27
38
  imagePullPolicy: Always
28
39
  image: "{{ $.Values.image }}"
29
40
  resources:
41
+ {{- if .Values.resources }}
42
+ {{- toYaml .Values.resources | nindent 12 }}
43
+ {{- else }}
30
44
  requests:
31
45
  cpu: "0.01"
32
46
  memory: "100Mi"
47
+ {{- end }}
48
+ {{- with .Values.containerSecurityContext }}
49
+ securityContext:
50
+ {{- toYaml . | nindent 12 }}
51
+ {{- end }}
33
52
  env:
34
53
  - name: HOST
35
54
  value: "0.0.0.0"
@@ -39,9 +58,37 @@ spec:
39
58
  - name: HOST_PREFIX
40
59
  value: "{{ $.Values.hostPrefix }}"
41
60
  {{end}}
42
- {{- if eq $.Values.targetK8s "saas" }}
61
+ {{- with .Values.env }}
62
+ {{- toYaml . | nindent 12 }}
63
+ {{- end }}
64
+ {{- if hasKey .Values "envFrom" }}
65
+ envFrom:
66
+ {{- toYaml .Values.envFrom | nindent 12 }}
67
+ {{- else if eq $.Values.targetK8s "saas" }}
43
68
  envFrom:
44
69
  - configMapRef:
45
70
  name: "app-common-config"
46
71
  {{- end }}
47
- ---
72
+ {{- with .Values.startupProbe }}
73
+ startupProbe:
74
+ {{- toYaml . | nindent 12 }}
75
+ {{- end }}
76
+ {{- with .Values.readinessProbe }}
77
+ readinessProbe:
78
+ {{- toYaml . | nindent 12 }}
79
+ {{- end }}
80
+ {{- with .Values.livenessProbe }}
81
+ livenessProbe:
82
+ {{- toYaml . | nindent 12 }}
83
+ {{- end }}
84
+ {{- if .Values.persistence.enabled }}
85
+ volumeMounts:
86
+ - name: data
87
+ mountPath: {{ .Values.persistence.mountPath | quote }}
88
+ {{- end }}
89
+ {{- if .Values.persistence.enabled }}
90
+ volumes:
91
+ - name: data
92
+ persistentVolumeClaim:
93
+ claimName: {{ default (printf "%s-data" .Values.app) .Values.persistence.existingClaim }}
94
+ {{- end }}
@@ -0,0 +1,17 @@
1
+ {{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
2
+ apiVersion: v1
3
+ kind: PersistentVolumeClaim
4
+ metadata:
5
+ name: {{ .Values.app }}-data
6
+ namespace: {{ include "template.namespace" . }}
7
+ annotations:
8
+ helm.sh/resource-policy: keep
9
+ spec:
10
+ accessModes: [ReadWriteOnce]
11
+ {{- with .Values.persistence.storageClass }}
12
+ storageClassName: {{ . | quote }}
13
+ {{- end }}
14
+ resources:
15
+ requests:
16
+ storage: {{ .Values.persistence.size }}
17
+ {{- end }}
@@ -19,14 +19,16 @@ spec:
19
19
  - match:
20
20
  - uri:
21
21
  prefix: /
22
+ {{- with .Values.virtualService.timeout }}
23
+ timeout: {{ . }}
24
+ {{- end }}
22
25
  route:
23
26
  - destination:
24
27
  host: {{ .Values.app }}
25
28
  port:
26
29
  number: 80
30
+ {{- with .Values.virtualService.corsPolicy }}
27
31
  corsPolicy:
28
- allowOrigin:
29
- - "*"
30
- allowMethods:
31
- - "*"
32
+ {{- toYaml . | nindent 12 }}
33
+ {{- end }}
32
34
  {{- end }}
@@ -7,6 +7,9 @@ envType: '<<ENV_TYPE>>'
7
7
  hostPrefix: '<<HOST_PREFIX>>'
8
8
  namespace: ''
9
9
  virtualService:
10
+ corsPolicy:
11
+ allowOrigin: ['*']
12
+ allowMethods: ['*']
10
13
  enabled: true
11
14
  hosts:
12
15
  test:
@@ -18,3 +21,21 @@ virtualService:
18
21
  gateways:
19
22
  - 'common-gateway-eshetang'
20
23
  targetK8s: 'saas'
24
+
25
+ # 可选持久卷;省略时保持现有无状态 Web 应用的部署行为。
26
+ persistence:
27
+ enabled: false
28
+ mountPath: /data
29
+ size: 20Gi
30
+ existingClaim: ''
31
+ storageClass: ''
32
+
33
+ # 构建与发布配置由 Drone 在运行时读取,默认保持原有发布范围。
34
+ managed: false
35
+ nodeVersion: '22.22.0'
36
+ buildCommand: pnpm build
37
+ environments:
38
+ test: true
39
+ stage: true
40
+ prod: true
41
+ stable: true
package/dist/cli.js CHANGED
@@ -30,6 +30,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
 
32
32
  // src/config.ts
33
+ function mergeDeployValues(base, overrides) {
34
+ return Object.fromEntries(Object.entries({ ...base, ...overrides }).map(
35
+ /** 仅合并普通对象,列表保持声明的顺序。 */
36
+ ([key, value]) => {
37
+ const previous = base[key];
38
+ const mergeable = value && previous && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous);
39
+ return [key, mergeable ? mergeDeployValues(previous, value) : value];
40
+ }
41
+ ));
42
+ }
33
43
  var import_node_fs, import_node_path, import_node_url, import_yaml, import_meta, DEFAULT_BUILD_DIRECTORY, BUILD_DIRECTORY_ARGUMENT, protectedDirectories, loadXfeConfig, getBuildDirectory, getServerEntry, configDeploy, getJwtEnv;
34
44
  var init_config = __esm({
35
45
  "src/config.ts"() {
@@ -98,39 +108,40 @@ var init_config = __esm({
98
108
  };
99
109
  configDeploy = () => {
100
110
  const xfeConfig = loadXfeConfig();
101
- if (!xfeConfig?.deploy) return;
111
+ const deployment = xfeConfig.deploy;
112
+ if (!deployment) return;
102
113
  const configDirectory = typeof __dirname === "string" ? __dirname : import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
103
114
  const projectRoot = process.cwd();
115
+ const source = import_node_path.default.resolve(configDirectory, "../deploy");
116
+ const target = deployment.managed ? getBuildDirectory(projectRoot, { buildDirectory: "deploy" }) : source;
104
117
  const buildDirectory = import_node_path.default.relative(projectRoot, getBuildDirectory(projectRoot, xfeConfig.webapp)).replaceAll(import_node_path.default.sep, "/");
105
- const droneConfigFilePath = import_node_path.default.resolve(configDirectory, "../deploy/.drone.yml");
106
- const droneTarget = import_node_path.default.resolve(projectRoot, xfeConfig.deploy.droneConfigPath || "./.drone.yml");
107
- const defaultArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`);
108
- const configuredArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`);
109
- const droneConfig = import_node_fs.default.readFileSync(droneConfigFilePath, "utf8").replace(defaultArgument, configuredArgument);
118
+ const droneTarget = import_node_path.default.resolve(projectRoot, deployment.droneConfigPath || "./.drone.yml");
119
+ const defaults = import_yaml.default.parse(import_node_fs.default.readFileSync(import_node_path.default.join(source, "helm/values.yaml"), "utf8"));
120
+ const { virtualServiceEnabled = true, hostsProd = [], hostsStage = [], hostsTest = [], gateways = ["common-gateway-eshetang"] } = deployment;
121
+ defaults.virtualService = { ...defaults.virtualService, enabled: virtualServiceEnabled, hosts: { prod: hostsProd, stage: hostsStage, test: hostsTest }, gateways };
122
+ defaults.targetK8s = deployment.targetK8s || "saas";
123
+ defaults.namespace = deployment.targetNamespace || "";
124
+ const values = mergeDeployValues(defaults, deployment.values || {});
125
+ values.managed = Boolean(deployment.managed);
126
+ values.nodeVersion = deployment.values?.nodeVersion || "22.22.0";
127
+ if (!/^\d+\.\d+\.\d+$/.test(values.nodeVersion)) throw new Error("deploy.values.nodeVersion \u5FC5\u987B\u662F\u5B8C\u6574 Node \u7248\u672C\u53F7");
128
+ if (!["saas", "infra"].includes(values.targetK8s)) throw new Error("deploy.targetK8s \u53EA\u80FD\u4E3A saas \u6216 infra");
129
+ if (!values.environments || typeof values.environments !== "object" || Array.isArray(values.environments) || Object.values(values.environments).some(
130
+ /** 发布开关必须是布尔值,避免误执行未启用环境。 */
131
+ (enabled) => typeof enabled !== "boolean"
132
+ )) throw new Error("deploy.values.environments \u5FC5\u987B\u662F\u5E03\u5C14\u5F00\u5173\u5BF9\u8C61");
133
+ if (deployment.managed) values.virtualService.hosts = Object.fromEntries(Object.entries(values.virtualService.hosts).filter(
134
+ /** 不生成没有配置域名的环境条目。 */
135
+ ([, hosts]) => Array.isArray(hosts) && hosts.length
136
+ ));
137
+ const drone = import_node_fs.default.readFileSync(import_node_path.default.join(source, ".drone.yml"), "utf8").replaceAll("<<NODE_VERSION>>", values.nodeVersion).replaceAll("<<TARGET_K8S>>", values.targetK8s).replace(JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`), JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`));
138
+ if (deployment.managed) {
139
+ if (target === source) throw new Error("managed \u90E8\u7F72\u8F93\u51FA\u4E0D\u80FD\u8986\u76D6\u6846\u67B6\u6A21\u677F\u76EE\u5F55");
140
+ import_node_fs.default.cpSync(source, target, { recursive: true });
141
+ }
110
142
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(droneTarget), { recursive: true });
111
- import_node_fs.default.writeFileSync(droneTarget, droneConfig);
112
- const valuesYamlFilePath = import_node_path.default.resolve(configDirectory, "../deploy/helm/values.yaml");
113
- const valuesYamlFile = import_node_fs.default.readFileSync(valuesYamlFilePath, "utf8");
114
- const valuesYaml = import_yaml.default.parse(valuesYamlFile);
115
- if (!valuesYaml.virtualService) valuesYaml.virtualService = {};
116
- if (!valuesYaml.virtualService.hosts) valuesYaml.virtualService.hosts = { test: [], stage: [], prod: [] };
117
- const {
118
- virtualServiceEnabled = true,
119
- hostsProd = [],
120
- hostsStage = [],
121
- hostsTest = [],
122
- gateways = ["common-gateway-eshetang"]
123
- } = xfeConfig.deploy;
124
- const { targetK8s = "saas", targetNamespace = "" } = xfeConfig.deploy;
125
- valuesYaml.virtualService.enabled = virtualServiceEnabled;
126
- valuesYaml.virtualService.gateways = gateways;
127
- valuesYaml.virtualService.hosts.prod = hostsProd;
128
- valuesYaml.virtualService.hosts.stage = hostsStage;
129
- valuesYaml.virtualService.hosts.test = hostsTest;
130
- valuesYaml.targetK8s = targetK8s;
131
- valuesYaml.namespace = targetNamespace;
132
- const valuesYamlStr = import_yaml.default.stringify(valuesYaml, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" });
133
- import_node_fs.default.writeFileSync(valuesYamlFilePath, valuesYamlStr, "utf8");
143
+ import_node_fs.default.writeFileSync(import_node_path.default.join(target, "helm/values.yaml"), import_yaml.default.stringify(values, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" }));
144
+ import_node_fs.default.writeFileSync(droneTarget, drone);
134
145
  };
135
146
  getJwtEnv = async () => {
136
147
  const xfeConfig = loadXfeConfig();
package/dist/cli.mjs CHANGED
@@ -19,6 +19,16 @@ import fs from "fs";
19
19
  import path from "path";
20
20
  import { fileURLToPath } from "url";
21
21
  import yaml from "yaml";
22
+ function mergeDeployValues(base, overrides) {
23
+ return Object.fromEntries(Object.entries({ ...base, ...overrides }).map(
24
+ /** 仅合并普通对象,列表保持声明的顺序。 */
25
+ ([key, value]) => {
26
+ const previous = base[key];
27
+ const mergeable = value && previous && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous);
28
+ return [key, mergeable ? mergeDeployValues(previous, value) : value];
29
+ }
30
+ ));
31
+ }
22
32
  var DEFAULT_BUILD_DIRECTORY, BUILD_DIRECTORY_ARGUMENT, protectedDirectories, loadXfeConfig, getBuildDirectory, getServerEntry, configDeploy, getJwtEnv;
23
33
  var init_config = __esm({
24
34
  "src/config.ts"() {
@@ -82,39 +92,40 @@ var init_config = __esm({
82
92
  };
83
93
  configDeploy = () => {
84
94
  const xfeConfig = loadXfeConfig();
85
- if (!xfeConfig?.deploy) return;
95
+ const deployment = xfeConfig.deploy;
96
+ if (!deployment) return;
86
97
  const configDirectory = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
87
98
  const projectRoot = process.cwd();
99
+ const source = path.resolve(configDirectory, "../deploy");
100
+ const target = deployment.managed ? getBuildDirectory(projectRoot, { buildDirectory: "deploy" }) : source;
88
101
  const buildDirectory = path.relative(projectRoot, getBuildDirectory(projectRoot, xfeConfig.webapp)).replaceAll(path.sep, "/");
89
- const droneConfigFilePath = path.resolve(configDirectory, "../deploy/.drone.yml");
90
- const droneTarget = path.resolve(projectRoot, xfeConfig.deploy.droneConfigPath || "./.drone.yml");
91
- const defaultArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`);
92
- const configuredArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`);
93
- const droneConfig = fs.readFileSync(droneConfigFilePath, "utf8").replace(defaultArgument, configuredArgument);
102
+ const droneTarget = path.resolve(projectRoot, deployment.droneConfigPath || "./.drone.yml");
103
+ const defaults = yaml.parse(fs.readFileSync(path.join(source, "helm/values.yaml"), "utf8"));
104
+ const { virtualServiceEnabled = true, hostsProd = [], hostsStage = [], hostsTest = [], gateways = ["common-gateway-eshetang"] } = deployment;
105
+ defaults.virtualService = { ...defaults.virtualService, enabled: virtualServiceEnabled, hosts: { prod: hostsProd, stage: hostsStage, test: hostsTest }, gateways };
106
+ defaults.targetK8s = deployment.targetK8s || "saas";
107
+ defaults.namespace = deployment.targetNamespace || "";
108
+ const values = mergeDeployValues(defaults, deployment.values || {});
109
+ values.managed = Boolean(deployment.managed);
110
+ values.nodeVersion = deployment.values?.nodeVersion || "22.22.0";
111
+ if (!/^\d+\.\d+\.\d+$/.test(values.nodeVersion)) throw new Error("deploy.values.nodeVersion \u5FC5\u987B\u662F\u5B8C\u6574 Node \u7248\u672C\u53F7");
112
+ if (!["saas", "infra"].includes(values.targetK8s)) throw new Error("deploy.targetK8s \u53EA\u80FD\u4E3A saas \u6216 infra");
113
+ if (!values.environments || typeof values.environments !== "object" || Array.isArray(values.environments) || Object.values(values.environments).some(
114
+ /** 发布开关必须是布尔值,避免误执行未启用环境。 */
115
+ (enabled) => typeof enabled !== "boolean"
116
+ )) throw new Error("deploy.values.environments \u5FC5\u987B\u662F\u5E03\u5C14\u5F00\u5173\u5BF9\u8C61");
117
+ if (deployment.managed) values.virtualService.hosts = Object.fromEntries(Object.entries(values.virtualService.hosts).filter(
118
+ /** 不生成没有配置域名的环境条目。 */
119
+ ([, hosts]) => Array.isArray(hosts) && hosts.length
120
+ ));
121
+ const drone = fs.readFileSync(path.join(source, ".drone.yml"), "utf8").replaceAll("<<NODE_VERSION>>", values.nodeVersion).replaceAll("<<TARGET_K8S>>", values.targetK8s).replace(JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`), JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`));
122
+ if (deployment.managed) {
123
+ if (target === source) throw new Error("managed \u90E8\u7F72\u8F93\u51FA\u4E0D\u80FD\u8986\u76D6\u6846\u67B6\u6A21\u677F\u76EE\u5F55");
124
+ fs.cpSync(source, target, { recursive: true });
125
+ }
94
126
  fs.mkdirSync(path.dirname(droneTarget), { recursive: true });
95
- fs.writeFileSync(droneTarget, droneConfig);
96
- const valuesYamlFilePath = path.resolve(configDirectory, "../deploy/helm/values.yaml");
97
- const valuesYamlFile = fs.readFileSync(valuesYamlFilePath, "utf8");
98
- const valuesYaml = yaml.parse(valuesYamlFile);
99
- if (!valuesYaml.virtualService) valuesYaml.virtualService = {};
100
- if (!valuesYaml.virtualService.hosts) valuesYaml.virtualService.hosts = { test: [], stage: [], prod: [] };
101
- const {
102
- virtualServiceEnabled = true,
103
- hostsProd = [],
104
- hostsStage = [],
105
- hostsTest = [],
106
- gateways = ["common-gateway-eshetang"]
107
- } = xfeConfig.deploy;
108
- const { targetK8s = "saas", targetNamespace = "" } = xfeConfig.deploy;
109
- valuesYaml.virtualService.enabled = virtualServiceEnabled;
110
- valuesYaml.virtualService.gateways = gateways;
111
- valuesYaml.virtualService.hosts.prod = hostsProd;
112
- valuesYaml.virtualService.hosts.stage = hostsStage;
113
- valuesYaml.virtualService.hosts.test = hostsTest;
114
- valuesYaml.targetK8s = targetK8s;
115
- valuesYaml.namespace = targetNamespace;
116
- const valuesYamlStr = yaml.stringify(valuesYaml, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" });
117
- fs.writeFileSync(valuesYamlFilePath, valuesYamlStr, "utf8");
127
+ fs.writeFileSync(path.join(target, "helm/values.yaml"), yaml.stringify(values, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" }));
128
+ fs.writeFileSync(droneTarget, drone);
118
129
  };
119
130
  getJwtEnv = async () => {
120
131
  const xfeConfig = loadXfeConfig();
package/dist/config.d.mts CHANGED
@@ -26,7 +26,7 @@ declare const loadXfeConfig: (root?: string) => XfeConfig;
26
26
  declare const getBuildDirectory: (projectRoot: string, webapp?: WebAppConfig) => string;
27
27
  /** 生产仅校验入口约定而不读取源码,构建阶段由编译器检查文件存在。 */
28
28
  declare const getServerEntry: (projectRoot: string, webapp?: WebAppConfig) => string | undefined;
29
- /** 将输出目录传给 Docker 构建,并沿用现有的集群、域名与 Helm 配置。 */
29
+ /** 合并部署 values 并生成公共模板,不改写流水线步骤或执行逻辑。 */
30
30
  declare const configDeploy: () => void;
31
31
  declare const getJwtEnv: () => Promise<Partial<JwtEnv>>;
32
32
 
package/dist/config.d.ts CHANGED
@@ -26,7 +26,7 @@ declare const loadXfeConfig: (root?: string) => XfeConfig;
26
26
  declare const getBuildDirectory: (projectRoot: string, webapp?: WebAppConfig) => string;
27
27
  /** 生产仅校验入口约定而不读取源码,构建阶段由编译器检查文件存在。 */
28
28
  declare const getServerEntry: (projectRoot: string, webapp?: WebAppConfig) => string | undefined;
29
- /** 将输出目录传给 Docker 构建,并沿用现有的集群、域名与 Helm 配置。 */
29
+ /** 合并部署 values 并生成公共模板,不改写流水线步骤或执行逻辑。 */
30
30
  declare const configDeploy: () => void;
31
31
  declare const getJwtEnv: () => Promise<Partial<JwtEnv>>;
32
32
 
package/dist/config.js CHANGED
@@ -101,40 +101,51 @@ var getServerEntry = (projectRoot, webapp = {}) => {
101
101
  };
102
102
  var configDeploy = () => {
103
103
  const xfeConfig = loadXfeConfig();
104
- if (!xfeConfig?.deploy) return;
104
+ const deployment = xfeConfig.deploy;
105
+ if (!deployment) return;
105
106
  const configDirectory = typeof __dirname === "string" ? __dirname : import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
106
107
  const projectRoot = process.cwd();
108
+ const source = import_node_path.default.resolve(configDirectory, "../deploy");
109
+ const target = deployment.managed ? getBuildDirectory(projectRoot, { buildDirectory: "deploy" }) : source;
107
110
  const buildDirectory = import_node_path.default.relative(projectRoot, getBuildDirectory(projectRoot, xfeConfig.webapp)).replaceAll(import_node_path.default.sep, "/");
108
- const droneConfigFilePath = import_node_path.default.resolve(configDirectory, "../deploy/.drone.yml");
109
- const droneTarget = import_node_path.default.resolve(projectRoot, xfeConfig.deploy.droneConfigPath || "./.drone.yml");
110
- const defaultArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`);
111
- const configuredArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`);
112
- const droneConfig = import_node_fs.default.readFileSync(droneConfigFilePath, "utf8").replace(defaultArgument, configuredArgument);
111
+ const droneTarget = import_node_path.default.resolve(projectRoot, deployment.droneConfigPath || "./.drone.yml");
112
+ const defaults = import_yaml.default.parse(import_node_fs.default.readFileSync(import_node_path.default.join(source, "helm/values.yaml"), "utf8"));
113
+ const { virtualServiceEnabled = true, hostsProd = [], hostsStage = [], hostsTest = [], gateways = ["common-gateway-eshetang"] } = deployment;
114
+ defaults.virtualService = { ...defaults.virtualService, enabled: virtualServiceEnabled, hosts: { prod: hostsProd, stage: hostsStage, test: hostsTest }, gateways };
115
+ defaults.targetK8s = deployment.targetK8s || "saas";
116
+ defaults.namespace = deployment.targetNamespace || "";
117
+ const values = mergeDeployValues(defaults, deployment.values || {});
118
+ values.managed = Boolean(deployment.managed);
119
+ values.nodeVersion = deployment.values?.nodeVersion || "22.22.0";
120
+ if (!/^\d+\.\d+\.\d+$/.test(values.nodeVersion)) throw new Error("deploy.values.nodeVersion \u5FC5\u987B\u662F\u5B8C\u6574 Node \u7248\u672C\u53F7");
121
+ if (!["saas", "infra"].includes(values.targetK8s)) throw new Error("deploy.targetK8s \u53EA\u80FD\u4E3A saas \u6216 infra");
122
+ if (!values.environments || typeof values.environments !== "object" || Array.isArray(values.environments) || Object.values(values.environments).some(
123
+ /** 发布开关必须是布尔值,避免误执行未启用环境。 */
124
+ (enabled) => typeof enabled !== "boolean"
125
+ )) throw new Error("deploy.values.environments \u5FC5\u987B\u662F\u5E03\u5C14\u5F00\u5173\u5BF9\u8C61");
126
+ if (deployment.managed) values.virtualService.hosts = Object.fromEntries(Object.entries(values.virtualService.hosts).filter(
127
+ /** 不生成没有配置域名的环境条目。 */
128
+ ([, hosts]) => Array.isArray(hosts) && hosts.length
129
+ ));
130
+ const drone = import_node_fs.default.readFileSync(import_node_path.default.join(source, ".drone.yml"), "utf8").replaceAll("<<NODE_VERSION>>", values.nodeVersion).replaceAll("<<TARGET_K8S>>", values.targetK8s).replace(JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`), JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`));
131
+ if (deployment.managed) {
132
+ if (target === source) throw new Error("managed \u90E8\u7F72\u8F93\u51FA\u4E0D\u80FD\u8986\u76D6\u6846\u67B6\u6A21\u677F\u76EE\u5F55");
133
+ import_node_fs.default.cpSync(source, target, { recursive: true });
134
+ }
113
135
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(droneTarget), { recursive: true });
114
- import_node_fs.default.writeFileSync(droneTarget, droneConfig);
115
- const valuesYamlFilePath = import_node_path.default.resolve(configDirectory, "../deploy/helm/values.yaml");
116
- const valuesYamlFile = import_node_fs.default.readFileSync(valuesYamlFilePath, "utf8");
117
- const valuesYaml = import_yaml.default.parse(valuesYamlFile);
118
- if (!valuesYaml.virtualService) valuesYaml.virtualService = {};
119
- if (!valuesYaml.virtualService.hosts) valuesYaml.virtualService.hosts = { test: [], stage: [], prod: [] };
120
- const {
121
- virtualServiceEnabled = true,
122
- hostsProd = [],
123
- hostsStage = [],
124
- hostsTest = [],
125
- gateways = ["common-gateway-eshetang"]
126
- } = xfeConfig.deploy;
127
- const { targetK8s = "saas", targetNamespace = "" } = xfeConfig.deploy;
128
- valuesYaml.virtualService.enabled = virtualServiceEnabled;
129
- valuesYaml.virtualService.gateways = gateways;
130
- valuesYaml.virtualService.hosts.prod = hostsProd;
131
- valuesYaml.virtualService.hosts.stage = hostsStage;
132
- valuesYaml.virtualService.hosts.test = hostsTest;
133
- valuesYaml.targetK8s = targetK8s;
134
- valuesYaml.namespace = targetNamespace;
135
- const valuesYamlStr = import_yaml.default.stringify(valuesYaml, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" });
136
- import_node_fs.default.writeFileSync(valuesYamlFilePath, valuesYamlStr, "utf8");
136
+ import_node_fs.default.writeFileSync(import_node_path.default.join(target, "helm/values.yaml"), import_yaml.default.stringify(values, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" }));
137
+ import_node_fs.default.writeFileSync(droneTarget, drone);
137
138
  };
139
+ function mergeDeployValues(base, overrides) {
140
+ return Object.fromEntries(Object.entries({ ...base, ...overrides }).map(
141
+ /** 仅合并普通对象,列表保持声明的顺序。 */
142
+ ([key, value]) => {
143
+ const previous = base[key];
144
+ const mergeable = value && previous && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous);
145
+ return [key, mergeable ? mergeDeployValues(previous, value) : value];
146
+ }
147
+ ));
148
+ }
138
149
  var getJwtEnv = async () => {
139
150
  const xfeConfig = loadXfeConfig();
140
151
  if (!xfeConfig?.jwt) return {};
package/dist/config.mjs CHANGED
@@ -62,40 +62,51 @@ var getServerEntry = (projectRoot, webapp = {}) => {
62
62
  };
63
63
  var configDeploy = () => {
64
64
  const xfeConfig = loadXfeConfig();
65
- if (!xfeConfig?.deploy) return;
65
+ const deployment = xfeConfig.deploy;
66
+ if (!deployment) return;
66
67
  const configDirectory = typeof __dirname === "string" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
67
68
  const projectRoot = process.cwd();
69
+ const source = path.resolve(configDirectory, "../deploy");
70
+ const target = deployment.managed ? getBuildDirectory(projectRoot, { buildDirectory: "deploy" }) : source;
68
71
  const buildDirectory = path.relative(projectRoot, getBuildDirectory(projectRoot, xfeConfig.webapp)).replaceAll(path.sep, "/");
69
- const droneConfigFilePath = path.resolve(configDirectory, "../deploy/.drone.yml");
70
- const droneTarget = path.resolve(projectRoot, xfeConfig.deploy.droneConfigPath || "./.drone.yml");
71
- const defaultArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`);
72
- const configuredArgument = JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`);
73
- const droneConfig = fs.readFileSync(droneConfigFilePath, "utf8").replace(defaultArgument, configuredArgument);
72
+ const droneTarget = path.resolve(projectRoot, deployment.droneConfigPath || "./.drone.yml");
73
+ const defaults = yaml.parse(fs.readFileSync(path.join(source, "helm/values.yaml"), "utf8"));
74
+ const { virtualServiceEnabled = true, hostsProd = [], hostsStage = [], hostsTest = [], gateways = ["common-gateway-eshetang"] } = deployment;
75
+ defaults.virtualService = { ...defaults.virtualService, enabled: virtualServiceEnabled, hosts: { prod: hostsProd, stage: hostsStage, test: hostsTest }, gateways };
76
+ defaults.targetK8s = deployment.targetK8s || "saas";
77
+ defaults.namespace = deployment.targetNamespace || "";
78
+ const values = mergeDeployValues(defaults, deployment.values || {});
79
+ values.managed = Boolean(deployment.managed);
80
+ values.nodeVersion = deployment.values?.nodeVersion || "22.22.0";
81
+ if (!/^\d+\.\d+\.\d+$/.test(values.nodeVersion)) throw new Error("deploy.values.nodeVersion \u5FC5\u987B\u662F\u5B8C\u6574 Node \u7248\u672C\u53F7");
82
+ if (!["saas", "infra"].includes(values.targetK8s)) throw new Error("deploy.targetK8s \u53EA\u80FD\u4E3A saas \u6216 infra");
83
+ if (!values.environments || typeof values.environments !== "object" || Array.isArray(values.environments) || Object.values(values.environments).some(
84
+ /** 发布开关必须是布尔值,避免误执行未启用环境。 */
85
+ (enabled) => typeof enabled !== "boolean"
86
+ )) throw new Error("deploy.values.environments \u5FC5\u987B\u662F\u5E03\u5C14\u5F00\u5173\u5BF9\u8C61");
87
+ if (deployment.managed) values.virtualService.hosts = Object.fromEntries(Object.entries(values.virtualService.hosts).filter(
88
+ /** 不生成没有配置域名的环境条目。 */
89
+ ([, hosts]) => Array.isArray(hosts) && hosts.length
90
+ ));
91
+ const drone = fs.readFileSync(path.join(source, ".drone.yml"), "utf8").replaceAll("<<NODE_VERSION>>", values.nodeVersion).replaceAll("<<TARGET_K8S>>", values.targetK8s).replace(JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${DEFAULT_BUILD_DIRECTORY}`), JSON.stringify(`${BUILD_DIRECTORY_ARGUMENT}=${buildDirectory}`));
92
+ if (deployment.managed) {
93
+ if (target === source) throw new Error("managed \u90E8\u7F72\u8F93\u51FA\u4E0D\u80FD\u8986\u76D6\u6846\u67B6\u6A21\u677F\u76EE\u5F55");
94
+ fs.cpSync(source, target, { recursive: true });
95
+ }
74
96
  fs.mkdirSync(path.dirname(droneTarget), { recursive: true });
75
- fs.writeFileSync(droneTarget, droneConfig);
76
- const valuesYamlFilePath = path.resolve(configDirectory, "../deploy/helm/values.yaml");
77
- const valuesYamlFile = fs.readFileSync(valuesYamlFilePath, "utf8");
78
- const valuesYaml = yaml.parse(valuesYamlFile);
79
- if (!valuesYaml.virtualService) valuesYaml.virtualService = {};
80
- if (!valuesYaml.virtualService.hosts) valuesYaml.virtualService.hosts = { test: [], stage: [], prod: [] };
81
- const {
82
- virtualServiceEnabled = true,
83
- hostsProd = [],
84
- hostsStage = [],
85
- hostsTest = [],
86
- gateways = ["common-gateway-eshetang"]
87
- } = xfeConfig.deploy;
88
- const { targetK8s = "saas", targetNamespace = "" } = xfeConfig.deploy;
89
- valuesYaml.virtualService.enabled = virtualServiceEnabled;
90
- valuesYaml.virtualService.gateways = gateways;
91
- valuesYaml.virtualService.hosts.prod = hostsProd;
92
- valuesYaml.virtualService.hosts.stage = hostsStage;
93
- valuesYaml.virtualService.hosts.test = hostsTest;
94
- valuesYaml.targetK8s = targetK8s;
95
- valuesYaml.namespace = targetNamespace;
96
- const valuesYamlStr = yaml.stringify(valuesYaml, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" });
97
- fs.writeFileSync(valuesYamlFilePath, valuesYamlStr, "utf8");
97
+ fs.writeFileSync(path.join(target, "helm/values.yaml"), yaml.stringify(values, { defaultStringType: "QUOTE_SINGLE", defaultKeyType: "PLAIN" }));
98
+ fs.writeFileSync(droneTarget, drone);
98
99
  };
100
+ function mergeDeployValues(base, overrides) {
101
+ return Object.fromEntries(Object.entries({ ...base, ...overrides }).map(
102
+ /** 仅合并普通对象,列表保持声明的顺序。 */
103
+ ([key, value]) => {
104
+ const previous = base[key];
105
+ const mergeable = value && previous && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous);
106
+ return [key, mergeable ? mergeDeployValues(previous, value) : value];
107
+ }
108
+ ));
109
+ }
99
110
  var getJwtEnv = async () => {
100
111
  const xfeConfig = loadXfeConfig();
101
112
  if (!xfeConfig?.jwt) return {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfe-repo/web-app",
3
- "version": "2.0.0-beta.3",
3
+ "version": "2.0.0-beta.5",
4
4
  "bin": {
5
5
  "xfe-web": "./bin/index.js"
6
6
  },
@@ -30,8 +30,8 @@
30
30
  "yaml": "^2.4.2",
31
31
  "tsx": "4.23.15",
32
32
  "@xfe-repo/eslint-config": "2.0.0-beta.1",
33
- "@xfe-repo/web-router": "2.0.0-beta.1",
34
- "@xfe-repo/typescript-config": "2.0.0-beta.1"
33
+ "@xfe-repo/typescript-config": "2.0.0-beta.1",
34
+ "@xfe-repo/web-router": "2.0.0-beta.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/express": "5.0.6"