@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 +34 -0
- package/deploy/.drone.yml +60 -24
- package/deploy/Dockerfile +2 -1
- package/deploy/Dockerfile.dockerignore +10 -0
- package/deploy/helm/Chart.yaml +1 -1
- package/deploy/helm/templates/deployment.yaml +50 -3
- package/deploy/helm/templates/pvc.yaml +17 -0
- package/deploy/helm/templates/virtualService.yaml +6 -4
- package/deploy/helm/values.yaml +21 -0
- package/dist/cli.js +40 -29
- package/dist/cli.mjs +40 -29
- package/dist/config.d.mts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +40 -29
- package/dist/config.mjs +40 -29
- package/package.json +3 -3
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
74
|
-
from_secret:
|
|
75
|
-
INFRA_K8S_CONFIG:
|
|
76
|
-
from_secret: infra-k8s-config
|
|
91
|
+
K8S_CONFIG:
|
|
92
|
+
from_secret: target-k8s-config
|
|
77
93
|
commands:
|
|
78
|
-
#
|
|
79
|
-
-
|
|
80
|
-
|
|
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
|
-
#
|
|
88
|
-
- cat deploy/helm/values.yaml
|
|
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
|
-
-
|
|
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:
|
|
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:
|
|
230
|
+
path: <<TARGET_K8S>>-k8s-config
|
|
195
231
|
name: config
|
|
196
232
|
---
|
|
197
233
|
kind: secret
|
package/deploy/Dockerfile
CHANGED
package/deploy/helm/Chart.yaml
CHANGED
|
@@ -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
|
-
|
|
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
|
-
{{-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
allowMethods:
|
|
31
|
-
- "*"
|
|
32
|
+
{{- toYaml . | nindent 12 }}
|
|
33
|
+
{{- end }}
|
|
32
34
|
{{- end }}
|
package/deploy/helm/values.yaml
CHANGED
|
@@ -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
|
-
|
|
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
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
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(
|
|
112
|
-
|
|
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
|
-
|
|
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
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
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(
|
|
96
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
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(
|
|
115
|
-
|
|
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
|
-
|
|
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
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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(
|
|
76
|
-
|
|
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
|
+
"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/
|
|
34
|
-
"@xfe-repo/
|
|
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"
|