@docupace/pipeline 1.0.121 → 1.0.122-connect.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tenable-scan.sh +224 -0
- package/bin/xray-bitbucket-insights.sh +287 -0
- package/bin/xray-html-report.sh +278 -0
- package/bin/xray.sh +204 -221
- package/bitbucket-pipelines.yml +291 -0
- package/package.json +1 -1
- package/src/changeset/cli.js +59 -33
- package/src/changeset/convert.js +7 -0
- package/src/changeset/validate.js +10 -0
- package/src/changeset/version.js +11 -7
- package/src/next/deploy.js +20 -2
- package/src/next/util.js +10 -0
package/bitbucket-pipelines.yml
CHANGED
|
@@ -7,6 +7,291 @@ clone:
|
|
|
7
7
|
definitions:
|
|
8
8
|
caches:
|
|
9
9
|
pnpm: ~/.pnpm-store
|
|
10
|
+
|
|
11
|
+
# Pipelines under `definitions.pipelines` are the ones other repos can pull in
|
|
12
|
+
# with `import: pipeline-shared-scripts:master:<name>`. Names under
|
|
13
|
+
# `pipelines.custom` below are local to this repo and are NOT importable.
|
|
14
|
+
pipelines:
|
|
15
|
+
xray-scan: &xray-scan-pipeline
|
|
16
|
+
- step:
|
|
17
|
+
name: JFrog Xray Scan (dependencies + Docker image)
|
|
18
|
+
runs-on:
|
|
19
|
+
- self.hosted
|
|
20
|
+
- linux.shell
|
|
21
|
+
- onprem
|
|
22
|
+
script:
|
|
23
|
+
- curl -fL https://install-cli.jfrog.io | sh
|
|
24
|
+
- jf --version
|
|
25
|
+
- |
|
|
26
|
+
# JFROG_XRAY_URL is stored without a scheme in some workspaces; the
|
|
27
|
+
# jf CLI needs an absolute URL or every call fails with
|
|
28
|
+
# "unsupported protocol scheme".
|
|
29
|
+
case "${JFROG_XRAY_URL}" in
|
|
30
|
+
http://*|https://*) XRAY_URL="${JFROG_XRAY_URL}" ;;
|
|
31
|
+
"") echo "JFROG_XRAY_URL is not set — cannot scan."; exit 1 ;;
|
|
32
|
+
*) XRAY_URL="https://${JFROG_XRAY_URL}" ;;
|
|
33
|
+
esac
|
|
34
|
+
export XRAY_URL
|
|
35
|
+
echo "Using Xray URL: ${XRAY_URL}"
|
|
36
|
+
# Self-hosted runners keep ~/.jfrog between builds, so a server left
|
|
37
|
+
# behind by an earlier failed run makes `config add` fail. Drop it first.
|
|
38
|
+
- jf config remove scan1 --quiet || true
|
|
39
|
+
- jf config add scan1 --url="${XRAY_URL}" --user=$JFROG_XRAY_USER --access-token=$JFROG_XRAY_PASSWORD --interactive=false
|
|
40
|
+
- jf config use scan1
|
|
41
|
+
- jf c show
|
|
42
|
+
- mkdir -p xray-report
|
|
43
|
+
- |
|
|
44
|
+
# A repo can hold more than one app (e.g. a frontend and a backend
|
|
45
|
+
# with separate lockfiles and Dockerfiles), and one context would
|
|
46
|
+
# only ever scan one of them. SCAN_TARGETS lists them all:
|
|
47
|
+
#
|
|
48
|
+
# label:auditDir:buildContext:dockerfile[;label:...]
|
|
49
|
+
#
|
|
50
|
+
# An empty auditDir skips the dependency scan for that target; an
|
|
51
|
+
# empty buildContext skips the image scan. dockerfile defaults to
|
|
52
|
+
# <buildContext>/Dockerfile. Example for two apps in one repo:
|
|
53
|
+
#
|
|
54
|
+
# SCAN_TARGETS="frontend:atlas-mui:atlas-mui:atlas-mui/Dockerfile;backend:intel-server:.:Dockerfile"
|
|
55
|
+
#
|
|
56
|
+
# With SCAN_TARGETS unset we fall back to the older single-target
|
|
57
|
+
# XRAY_BUILD_CONTEXT / XRAY_DOCKERFILE_PATH variables.
|
|
58
|
+
if [ -z "${SCAN_TARGETS:-}" ]; then
|
|
59
|
+
CTX="${XRAY_BUILD_CONTEXT:-.}"
|
|
60
|
+
SCAN_TARGETS="app:${CTX}:${CTX}:${XRAY_DOCKERFILE_PATH:-${CTX}/Dockerfile}"
|
|
61
|
+
echo "SCAN_TARGETS not set — scanning a single target: ${SCAN_TARGETS}"
|
|
62
|
+
fi
|
|
63
|
+
printf '%s' "${SCAN_TARGETS}" > /tmp/scan-targets
|
|
64
|
+
- |
|
|
65
|
+
# These runners inherit DOCKER_HOST=tcp://localhost:2375 (no daemon
|
|
66
|
+
# there) and DOCKER_CONTEXT is reserved by the docker CLI for
|
|
67
|
+
# selecting a named context — clear both so we use the local socket.
|
|
68
|
+
export DOCKER_HOST=""
|
|
69
|
+
unset DOCKER_CONTEXT
|
|
70
|
+
SHORT_SHA="$(printf '%s' "${BITBUCKET_COMMIT:-local}" | cut -c1-7)"
|
|
71
|
+
WORKDIR="$(pwd)"
|
|
72
|
+
# xray.txt contains the human-readable table output; kept as an
|
|
73
|
+
# artifact for quick log reading. bin/xray.sh reads simple-json.
|
|
74
|
+
: > xray.txt
|
|
75
|
+
# One "label:file" per line, consumed by the HTML report and the
|
|
76
|
+
# Code Insights publisher below.
|
|
77
|
+
: > /tmp/xray-report-args
|
|
78
|
+
# Split SCAN_TARGETS on ';' into positional params. Piping into a
|
|
79
|
+
# `while read` loop instead would let a command in the body (docker,
|
|
80
|
+
# jf) consume the rest of the list from stdin — which silently scans
|
|
81
|
+
# only the first target.
|
|
82
|
+
OLD_IFS="${IFS}"
|
|
83
|
+
IFS=';'
|
|
84
|
+
set -- $(cat /tmp/scan-targets)
|
|
85
|
+
IFS="${OLD_IFS}"
|
|
86
|
+
for target in "$@"; do
|
|
87
|
+
[ -z "${target}" ] && continue
|
|
88
|
+
label="$(printf '%s' "${target}" | cut -d: -f1)"
|
|
89
|
+
audit_dir="$(printf '%s' "${target}" | cut -d: -f2)"
|
|
90
|
+
build_ctx="$(printf '%s' "${target}" | cut -d: -f3)"
|
|
91
|
+
dockerfile="$(printf '%s' "${target}" | cut -d: -f4)"
|
|
92
|
+
[ -z "${label}" ] && label="app"
|
|
93
|
+
# Labels reach filenames and image tags, so keep them tame.
|
|
94
|
+
slug="$(printf '%s' "${label}" | tr -c 'A-Za-z0-9._-' '-')"
|
|
95
|
+
[ -n "${build_ctx}" ] && [ -z "${dockerfile}" ] && dockerfile="${build_ctx}/Dockerfile"
|
|
96
|
+
|
|
97
|
+
echo "=============================================================="
|
|
98
|
+
echo "Target '${label}': deps='${audit_dir:-none}' image='${dockerfile:-none}'"
|
|
99
|
+
echo "=============================================================="
|
|
100
|
+
|
|
101
|
+
if [ -z "${audit_dir}" ]; then
|
|
102
|
+
echo "No audit directory for '${label}' — skipping dependency scan."
|
|
103
|
+
elif [ ! -d "${audit_dir}" ]; then
|
|
104
|
+
echo "Audit directory '${audit_dir}' does not exist — skipping dependency scan for '${label}'."
|
|
105
|
+
else
|
|
106
|
+
# `jf audit` resolves the package manager from the working
|
|
107
|
+
# directory, so cd in rather than passing a path — and write to
|
|
108
|
+
# absolute paths so output lands back in xray-report/.
|
|
109
|
+
( cd "${audit_dir}" \
|
|
110
|
+
&& jf audit --format=table > "${WORKDIR}/xray-report/${slug}-audit.txt" 2>&1 \
|
|
111
|
+
; jf audit --format=simple-json > "${WORKDIR}/xray-report/${slug}-audit.json" 2>/dev/null ) || true
|
|
112
|
+
echo "--- ${label}: dependency scan ---"
|
|
113
|
+
cat "xray-report/${slug}-audit.txt" || true
|
|
114
|
+
{
|
|
115
|
+
echo "### ${label} — dependencies (${audit_dir})"
|
|
116
|
+
cat "xray-report/${slug}-audit.txt" 2>/dev/null
|
|
117
|
+
} >> xray.txt
|
|
118
|
+
echo "${label} deps:xray-report/${slug}-audit.json" >> /tmp/xray-report-args
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
if [ -z "${dockerfile}" ]; then
|
|
122
|
+
echo "No Dockerfile configured for '${label}' — skipping image scan."
|
|
123
|
+
elif [ ! -f "${dockerfile}" ]; then
|
|
124
|
+
echo "No Dockerfile at '${dockerfile}' — skipping image scan for '${label}'."
|
|
125
|
+
else
|
|
126
|
+
SCAN_IMAGE="${BITBUCKET_REPO_SLUG:-image}-${slug}:xray-${SHORT_SHA}"
|
|
127
|
+
echo "Building ${SCAN_IMAGE} from ${dockerfile} (context ${build_ctx})..."
|
|
128
|
+
# --load is buildx-only; the legacy builder loads locally anyway.
|
|
129
|
+
LOAD_FLAG=""
|
|
130
|
+
docker buildx version >/dev/null 2>&1 && LOAD_FLAG="--load"
|
|
131
|
+
if docker build "${build_ctx}" --file "${dockerfile}" --tag "${SCAN_IMAGE}" ${LOAD_FLAG}; then
|
|
132
|
+
jf docker scan "${SCAN_IMAGE}" --format=table > "xray-report/${slug}-docker.txt" 2>&1 || true
|
|
133
|
+
echo "--- ${label}: image scan ---"
|
|
134
|
+
cat "xray-report/${slug}-docker.txt" || true
|
|
135
|
+
jf docker scan "${SCAN_IMAGE}" --format=simple-json > "xray-report/${slug}-docker.json" 2>/dev/null || true
|
|
136
|
+
{
|
|
137
|
+
echo "### ${label} — image ${SCAN_IMAGE}"
|
|
138
|
+
cat "xray-report/${slug}-docker.txt" 2>/dev/null
|
|
139
|
+
} >> xray.txt
|
|
140
|
+
echo "${label} image:xray-report/${slug}-docker.json" >> /tmp/xray-report-args
|
|
141
|
+
docker rmi -f "${SCAN_IMAGE}" || true
|
|
142
|
+
else
|
|
143
|
+
echo "Docker build failed for '${label}' — no image scan for this target."
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
done
|
|
147
|
+
cp xray.txt xray-report/xray-audit.txt || true
|
|
148
|
+
cp /tmp/xray-report-args xray-report/scanned-targets.txt || true
|
|
149
|
+
- |
|
|
150
|
+
# The report/notify scripts live in bin/ of this shared repo, but an
|
|
151
|
+
# import only brings the YAML across — so consumers have to fetch
|
|
152
|
+
# them. The repo is private, so anonymous https fails; try each auth
|
|
153
|
+
# method the runners actually have, in order of preference.
|
|
154
|
+
# Local bin/ is used only when all three scripts are present (i.e.
|
|
155
|
+
# we're running inside the shared repo itself).
|
|
156
|
+
XRAY_BIN=""
|
|
157
|
+
if [ -f ./bin/xray.sh ] && [ -f ./bin/xray-html-report.sh ] && [ -f ./bin/xray-bitbucket-insights.sh ]; then
|
|
158
|
+
XRAY_BIN=./bin
|
|
159
|
+
echo "Using shared scripts from ./bin"
|
|
160
|
+
else
|
|
161
|
+
DEST=/tmp/pipeline-shared-scripts
|
|
162
|
+
rm -rf "${DEST}"
|
|
163
|
+
# Parallel arrays of label + URL; only the label is ever printed,
|
|
164
|
+
# so credentials never reach the build log.
|
|
165
|
+
LABELS=""; URLS=""
|
|
166
|
+
add_src() { LABELS="${LABELS}${LABELS:+|}$1"; URLS="${URLS}${URLS:+|}$2"; }
|
|
167
|
+
[ -n "${SCRIPTS_REPO:-}" ] && add_src "SCRIPTS_REPO" "${SCRIPTS_REPO}"
|
|
168
|
+
if [ -n "${BITBUCKET_USERNAME:-}" ] && [ -n "${BITBUCKET_APP_PASSWORD:-}" ]; then
|
|
169
|
+
add_src "BITBUCKET_USERNAME+APP_PASSWORD" "https://${BITBUCKET_USERNAME}:${BITBUCKET_APP_PASSWORD}@bitbucket.org/dp-cloud/pipeline-shared-scripts.git"
|
|
170
|
+
fi
|
|
171
|
+
[ -n "${BITBUCKET_STEP_OAUTH_TOKEN:-}" ] && add_src "step OAuth token" "https://x-token-auth:${BITBUCKET_STEP_OAUTH_TOKEN}@bitbucket.org/dp-cloud/pipeline-shared-scripts.git"
|
|
172
|
+
add_src "ssh" "git@bitbucket.org:dp-cloud/pipeline-shared-scripts.git"
|
|
173
|
+
i=1
|
|
174
|
+
while :; do
|
|
175
|
+
label="$(printf '%s' "${LABELS}" | cut -d'|' -f${i})"
|
|
176
|
+
url="$(printf '%s' "${URLS}" | cut -d'|' -f${i})"
|
|
177
|
+
[ -z "${label}" ] && break
|
|
178
|
+
echo "Fetching shared scripts via ${label}..."
|
|
179
|
+
# GIT_TERMINAL_PROMPT=0 so a private repo fails fast instead of
|
|
180
|
+
# blocking on a username prompt.
|
|
181
|
+
if GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes" \
|
|
182
|
+
git clone --depth 1 --quiet "${url}" "${DEST}" 2>/dev/null; then
|
|
183
|
+
XRAY_BIN="${DEST}/bin"
|
|
184
|
+
echo "Fetched shared scripts via ${label}."
|
|
185
|
+
break
|
|
186
|
+
fi
|
|
187
|
+
rm -rf "${DEST}"
|
|
188
|
+
i=$((i + 1))
|
|
189
|
+
done
|
|
190
|
+
if [ -z "${XRAY_BIN}" ]; then
|
|
191
|
+
echo "Could not fetch shared scripts (tried: $(printf '%s' "${LABELS}" | tr '|' ' ')). HTML report, Code Insights and Slack notify will be skipped; JSON/table artifacts are still produced."
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
194
|
+
[ -n "${XRAY_BIN}" ] && [ -f "${XRAY_BIN}/xray.sh" ] && cp "${XRAY_BIN}/xray.sh" ./xray-notify.sh
|
|
195
|
+
echo "${XRAY_BIN}" > /tmp/xray-bin-path
|
|
196
|
+
# Render the browsable HTML report (and PDF when XRAY_REPORT_PDF=true).
|
|
197
|
+
# Both consumers take one "label:file" argument per scan that ran,
|
|
198
|
+
# so every target shows up as its own section / report entry.
|
|
199
|
+
- |
|
|
200
|
+
XRAY_BIN="$(cat /tmp/xray-bin-path 2>/dev/null || echo '')"
|
|
201
|
+
if [ -n "${XRAY_BIN}" ] && [ -f "${XRAY_BIN}/xray-html-report.sh" ]; then
|
|
202
|
+
set -- xray-report/xray-report.html
|
|
203
|
+
while IFS= read -r a; do [ -n "${a}" ] && set -- "$@" "${a}"; done < /tmp/xray-report-args
|
|
204
|
+
bash "${XRAY_BIN}/xray-html-report.sh" "$@" || true
|
|
205
|
+
else
|
|
206
|
+
echo "xray-html-report.sh unavailable — skipping HTML report."
|
|
207
|
+
fi
|
|
208
|
+
# Publish to Bitbucket Code Insights: a report on the commit plus
|
|
209
|
+
# inline annotations in the PR diff. Never fails the build.
|
|
210
|
+
- |
|
|
211
|
+
XRAY_BIN="$(cat /tmp/xray-bin-path 2>/dev/null || echo '')"
|
|
212
|
+
if [ -n "${XRAY_BIN}" ] && [ -f "${XRAY_BIN}/xray-bitbucket-insights.sh" ]; then
|
|
213
|
+
set --
|
|
214
|
+
while IFS= read -r a; do [ -n "${a}" ] && set -- "$@" "${a}"; done < /tmp/xray-report-args
|
|
215
|
+
if [ "$#" -gt 0 ]; then
|
|
216
|
+
bash "${XRAY_BIN}/xray-bitbucket-insights.sh" "$@" || true
|
|
217
|
+
else
|
|
218
|
+
echo "No scan output to publish — skipping Code Insights."
|
|
219
|
+
fi
|
|
220
|
+
else
|
|
221
|
+
echo "xray-bitbucket-insights.sh unavailable — skipping Code Insights."
|
|
222
|
+
fi
|
|
223
|
+
- |
|
|
224
|
+
if [ -f ./xray-notify.sh ]; then
|
|
225
|
+
if [ -n "${XRAY_SLACK_TOKEN:-}" ] && [ -n "${XRAY_SLACK_CHANNEL:-}" ]; then
|
|
226
|
+
bash ./xray-notify.sh || true
|
|
227
|
+
else
|
|
228
|
+
echo "XRAY_SLACK_TOKEN / XRAY_SLACK_CHANNEL not set — skipping Slack notification."
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
- rm -f ./xray-notify.sh
|
|
232
|
+
# after-script always runs, so credentials don't survive a failed step.
|
|
233
|
+
after-script:
|
|
234
|
+
- jf config remove scan1 --quiet || true
|
|
235
|
+
artifacts:
|
|
236
|
+
- xray-report/**
|
|
237
|
+
|
|
238
|
+
- step: &tenable-scan-step
|
|
239
|
+
name: Tenable Cloud Security Scan (container image)
|
|
240
|
+
runs-on:
|
|
241
|
+
- self.hosted
|
|
242
|
+
- linux.shell
|
|
243
|
+
- onprem
|
|
244
|
+
script:
|
|
245
|
+
# bin/tenable-scan.sh lives in this shared repo; an import only brings
|
|
246
|
+
# the YAML across, so fetch it the same way the Xray step does.
|
|
247
|
+
- |
|
|
248
|
+
TENABLE_BIN=""
|
|
249
|
+
if [ -f ./bin/tenable-scan.sh ]; then
|
|
250
|
+
TENABLE_BIN=./bin
|
|
251
|
+
else
|
|
252
|
+
DEST=/tmp/pipeline-shared-scripts
|
|
253
|
+
rm -rf "${DEST}"
|
|
254
|
+
LABELS=""; URLS=""
|
|
255
|
+
add_src() { LABELS="${LABELS}${LABELS:+|}$1"; URLS="${URLS}${URLS:+|}$2"; }
|
|
256
|
+
[ -n "${SCRIPTS_REPO:-}" ] && add_src "SCRIPTS_REPO" "${SCRIPTS_REPO}"
|
|
257
|
+
if [ -n "${BITBUCKET_USERNAME:-}" ] && [ -n "${BITBUCKET_APP_PASSWORD:-}" ]; then
|
|
258
|
+
add_src "BITBUCKET_USERNAME+APP_PASSWORD" "https://${BITBUCKET_USERNAME}:${BITBUCKET_APP_PASSWORD}@bitbucket.org/dp-cloud/pipeline-shared-scripts.git"
|
|
259
|
+
fi
|
|
260
|
+
[ -n "${BITBUCKET_STEP_OAUTH_TOKEN:-}" ] && add_src "step OAuth token" "https://x-token-auth:${BITBUCKET_STEP_OAUTH_TOKEN}@bitbucket.org/dp-cloud/pipeline-shared-scripts.git"
|
|
261
|
+
add_src "ssh" "git@bitbucket.org:dp-cloud/pipeline-shared-scripts.git"
|
|
262
|
+
i=1
|
|
263
|
+
while :; do
|
|
264
|
+
label="$(printf '%s' "${LABELS}" | cut -d'|' -f${i})"
|
|
265
|
+
url="$(printf '%s' "${URLS}" | cut -d'|' -f${i})"
|
|
266
|
+
[ -z "${label}" ] && break
|
|
267
|
+
echo "Fetching shared scripts via ${label}..."
|
|
268
|
+
if GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes" \
|
|
269
|
+
git clone --depth 1 --quiet "${url}" "${DEST}" 2>/dev/null; then
|
|
270
|
+
TENABLE_BIN="${DEST}/bin"
|
|
271
|
+
echo "Fetched shared scripts via ${label}."
|
|
272
|
+
break
|
|
273
|
+
fi
|
|
274
|
+
rm -rf "${DEST}"
|
|
275
|
+
i=$((i + 1))
|
|
276
|
+
done
|
|
277
|
+
fi
|
|
278
|
+
if [ -z "${TENABLE_BIN}" ]; then
|
|
279
|
+
echo "Could not fetch shared scripts (tried: $(printf '%s' "${LABELS}" | tr '|' ' ')) — skipping Tenable scan."
|
|
280
|
+
exit 0
|
|
281
|
+
fi
|
|
282
|
+
if [ ! -f "${TENABLE_BIN}/tenable-scan.sh" ]; then
|
|
283
|
+
echo "tenable-scan.sh not present in ${TENABLE_BIN} — skipping Tenable scan."
|
|
284
|
+
exit 0
|
|
285
|
+
fi
|
|
286
|
+
bash "${TENABLE_BIN}/tenable-scan.sh"
|
|
287
|
+
artifacts:
|
|
288
|
+
- tenable-report/**
|
|
289
|
+
|
|
290
|
+
# Runs the Tenable image scan on its own, for repos that want it separately
|
|
291
|
+
# from the Xray scan: import: pipeline-shared-scripts:master:tenable-scan
|
|
292
|
+
tenable-scan:
|
|
293
|
+
- step: *tenable-scan-step
|
|
294
|
+
|
|
10
295
|
steps:
|
|
11
296
|
- step: &version-step
|
|
12
297
|
runs-on:
|
|
@@ -21,6 +306,12 @@ definitions:
|
|
|
21
306
|
- node src/changeset/cli.js version-commit-tag --install-latest
|
|
22
307
|
|
|
23
308
|
pipelines:
|
|
309
|
+
# Lets this repo run the shared scans against itself from the Bitbucket UI.
|
|
310
|
+
custom:
|
|
311
|
+
xray-scan: *xray-scan-pipeline
|
|
312
|
+
tenable-scan:
|
|
313
|
+
- step: *tenable-scan-step
|
|
314
|
+
|
|
24
315
|
pull-requests:
|
|
25
316
|
'**':
|
|
26
317
|
- step:
|
package/package.json
CHANGED
package/src/changeset/cli.js
CHANGED
|
@@ -133,7 +133,12 @@ program
|
|
|
133
133
|
.command('version')
|
|
134
134
|
.description('Run Changeset versioning')
|
|
135
135
|
.action(async () => {
|
|
136
|
-
|
|
136
|
+
try {
|
|
137
|
+
await versionCommand();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('❌ Changeset versioning failed:', error);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
137
142
|
});
|
|
138
143
|
|
|
139
144
|
// Command: commit-and-tag
|
|
@@ -154,10 +159,15 @@ program
|
|
|
154
159
|
)
|
|
155
160
|
.action(async (options) => {
|
|
156
161
|
console.log('📦 Running all steps...');
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
try {
|
|
163
|
+
const releases = await versionCommand(false, options.installLatest);
|
|
164
|
+
await updateJiraWithVersion(releases);
|
|
165
|
+
await updateWikiWithVersion(releases);
|
|
166
|
+
await commitTagCommand(releases);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('❌ Version-commit-tag failed:', error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
161
171
|
});
|
|
162
172
|
|
|
163
173
|
program
|
|
@@ -165,38 +175,44 @@ program
|
|
|
165
175
|
.description('Closes a version')
|
|
166
176
|
.action(async () => {
|
|
167
177
|
console.log('📦 Running all steps...');
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
178
|
+
try {
|
|
179
|
+
const releases = await versionCommand(true);
|
|
180
|
+
await commitTagCommand(releases);
|
|
181
|
+
|
|
182
|
+
const { stdout: currentBranch } =
|
|
183
|
+
await $`git rev-parse --abbrev-ref HEAD`;
|
|
184
|
+
|
|
185
|
+
if (isMonoRepo()) {
|
|
186
|
+
for (const release of getAppReleases(releases)) {
|
|
187
|
+
const appName = getAppNameFromPackageName(release.name);
|
|
188
|
+
const newVersion = toShortVersion(release.newVersion);
|
|
189
|
+
if (!newVersion) {
|
|
190
|
+
throw new Error('Error calculating branch name');
|
|
191
|
+
}
|
|
192
|
+
await $`git checkout -b release/${appName}/${newVersion}`;
|
|
193
|
+
await $`git push origin release/${appName}/${newVersion}`;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
const newVersion = toShortVersion(releases[0].newVersion);
|
|
177
197
|
if (!newVersion) {
|
|
178
198
|
throw new Error('Error calculating branch name');
|
|
179
199
|
}
|
|
180
|
-
await $`git checkout -b release/${
|
|
181
|
-
await $`git push origin release/${
|
|
200
|
+
await $`git checkout -b release/${newVersion}`;
|
|
201
|
+
await $`git push origin release/${newVersion}`;
|
|
182
202
|
}
|
|
183
|
-
} else {
|
|
184
|
-
const newVersion = toShortVersion(releases[0].newVersion);
|
|
185
|
-
if (!newVersion) {
|
|
186
|
-
throw new Error('Error calculating branch name');
|
|
187
|
-
}
|
|
188
|
-
await $`git checkout -b release/${newVersion}`;
|
|
189
|
-
await $`git push origin release/${newVersion}`;
|
|
190
|
-
}
|
|
191
203
|
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
// Return to original branch
|
|
205
|
+
await $`git checkout ${currentBranch.trim()}`;
|
|
194
206
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
207
|
+
// Open next alpha version
|
|
208
|
+
await $`pnpm changeset pre enter alpha`;
|
|
209
|
+
await $`git add .`;
|
|
210
|
+
await $`git commit -m "Open next alpha version"`;
|
|
211
|
+
await $`git push origin`;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error('❌ Close-version failed:', error);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
200
216
|
});
|
|
201
217
|
|
|
202
218
|
program
|
|
@@ -213,7 +229,12 @@ program
|
|
|
213
229
|
.command('convert-prepare <version> [changelogPath]')
|
|
214
230
|
.description('Convert release notes to Canny format - Part 1 (Prepare)')
|
|
215
231
|
.action(async (version, changelogPath) => {
|
|
216
|
-
|
|
232
|
+
try {
|
|
233
|
+
await convertPrepare(version, changelogPath);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('❌ Convert-prepare failed:', error);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
217
238
|
});
|
|
218
239
|
|
|
219
240
|
// Command: convert-complete
|
|
@@ -221,7 +242,12 @@ program
|
|
|
221
242
|
.command('convert-complete [changelogPath]')
|
|
222
243
|
.description('Convert release notes to Canny format - Part 2 (Complete)')
|
|
223
244
|
.action(async (changelogPath) => {
|
|
224
|
-
|
|
245
|
+
try {
|
|
246
|
+
await convertComplete(changelogPath);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('❌ Convert-complete failed:', error);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
225
251
|
});
|
|
226
252
|
|
|
227
253
|
// Parse arguments
|
package/src/changeset/convert.js
CHANGED
|
@@ -361,6 +361,13 @@ export async function convertPrepare(
|
|
|
361
361
|
console.log(` Processed: ${processedCount}`);
|
|
362
362
|
console.log(` Errors: ${errorCount}`);
|
|
363
363
|
console.log(`\n📄 Updated changelog at: ${resolvedChangelogPath}`);
|
|
364
|
+
|
|
365
|
+
if (errorCount > 0) {
|
|
366
|
+
console.error(
|
|
367
|
+
`❌ Convert-prepare failed to process ${errorCount} ticket(s).`,
|
|
368
|
+
);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
364
371
|
}
|
|
365
372
|
|
|
366
373
|
/**
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import { $ } from 'zx';
|
|
4
5
|
|
|
5
6
|
import { setGitInfo } from '../next/util.js';
|
|
6
7
|
import { versionStatus } from './version.js';
|
|
7
8
|
|
|
9
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
10
|
+
const PACKAGE_VERSION = JSON.parse(
|
|
11
|
+
fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'),
|
|
12
|
+
).version;
|
|
13
|
+
|
|
8
14
|
export async function validateChangesets() {
|
|
15
|
+
console.log(`validateChangesets: @docupace/pipeline version = ${PACKAGE_VERSION}`);
|
|
9
16
|
await setGitInfo();
|
|
10
17
|
const destBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
18
|
+
console.log(`validateChangesets: destBranch = ${destBranch}`);
|
|
19
|
+
console.log(`validateChangesets: fetching origin ${destBranch}:${destBranch}`);
|
|
11
20
|
await $`git fetch origin ${destBranch}:${destBranch}`;
|
|
21
|
+
console.log('validateChangesets: fetch succeeded');
|
|
12
22
|
let releases;
|
|
13
23
|
|
|
14
24
|
try {
|
package/src/changeset/version.js
CHANGED
|
@@ -89,19 +89,23 @@ export async function versionCommand(
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
let originalBaseBranch;
|
|
92
|
+
let releases;
|
|
92
93
|
try {
|
|
93
94
|
originalBaseBranch = updateBaseBranch();
|
|
94
|
-
|
|
95
|
-
revertBaseBranch(originalBaseBranch);
|
|
96
|
-
await $`pnpm changeset version`;
|
|
97
|
-
|
|
98
|
-
await removeChangesetOutput();
|
|
99
|
-
|
|
100
|
-
return releases;
|
|
95
|
+
releases = await versionStatus();
|
|
101
96
|
} catch {
|
|
102
97
|
revertBaseBranch(originalBaseBranch);
|
|
103
98
|
return [];
|
|
104
99
|
}
|
|
100
|
+
revertBaseBranch(originalBaseBranch);
|
|
101
|
+
|
|
102
|
+
// Intentionally outside the try/catch above: a failure here (e.g. release
|
|
103
|
+
// notes processing) must fail the build, not be swallowed as "no releases".
|
|
104
|
+
await $`pnpm changeset version`;
|
|
105
|
+
|
|
106
|
+
await removeChangesetOutput();
|
|
107
|
+
|
|
108
|
+
return releases;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
export async function updateJiraWithVersion(releases) {
|
package/src/next/deploy.js
CHANGED
|
@@ -66,7 +66,7 @@ async function updateAndCommitConnectVersion(
|
|
|
66
66
|
|
|
67
67
|
await $`cd ${connectDeployPath} && git add .`;
|
|
68
68
|
await $`cd ${connectDeployPath} && git commit -m "Update Connect version for ${toSite} in ${toEnv} to ${getComparedVersion}" || echo "No changes to commit"`;
|
|
69
|
-
await
|
|
69
|
+
await pushWithRetry(connectDeployPath);
|
|
70
70
|
|
|
71
71
|
console.log('🧹 Cleaning up...');
|
|
72
72
|
await $`rm -rf ${connectDeployPath}`;
|
|
@@ -114,6 +114,24 @@ async function compareGetConnectSiteVersions(fromEnv, fromSite, toEnv, toSite) {
|
|
|
114
114
|
throw error;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Push commits in repoPath, retrying once with a pull --rebase if the push
|
|
119
|
+
* is rejected because the remote branch moved (non-fast-forward race).
|
|
120
|
+
* @param {string} repoPath - The path to the local repository.
|
|
121
|
+
*/
|
|
122
|
+
async function pushWithRetry(repoPath) {
|
|
123
|
+
try {
|
|
124
|
+
await $`cd ${repoPath} && git push`;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn(
|
|
127
|
+
'⚠️ Push rejected, likely because the remote branch moved. Pulling and retrying once...',
|
|
128
|
+
);
|
|
129
|
+
console.warn(error.message);
|
|
130
|
+
await $`cd ${repoPath} && git pull --rebase`;
|
|
131
|
+
await $`cd ${repoPath} && git push`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
function getRepoLocalPathByRepoSlug(repoSlug) {
|
|
118
136
|
const repoName = `${repoSlug}`;
|
|
119
137
|
const repoPath = path.join(process.cwd(), repoName);
|
|
@@ -270,7 +288,7 @@ export async function pushDeployConfig(repoPath, commitMessage) {
|
|
|
270
288
|
|
|
271
289
|
await $`cd ${repoPath} && git add .`;
|
|
272
290
|
await $`cd ${repoPath} && git commit -m ${commitMessage}`;
|
|
273
|
-
await
|
|
291
|
+
await pushWithRetry(repoPath);
|
|
274
292
|
console.log('Changes pushed to the remote repository.');
|
|
275
293
|
|
|
276
294
|
console.log('🧹 Cleaning up...');
|
package/src/next/util.js
CHANGED
|
@@ -85,11 +85,21 @@ export async function getChangedAppPackages() {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export async function setGitInfo() {
|
|
88
|
+
console.log(`setGitInfo: SET_GITOPS_USER=${process.env.SET_GITOPS_USER || '(unset)'}`);
|
|
88
89
|
if (process.env.SET_GITOPS_USER) {
|
|
90
|
+
const before = (await $`git remote get-url origin`).stdout.trim();
|
|
91
|
+
console.log(`setGitInfo: origin before = ${before}`);
|
|
92
|
+
|
|
89
93
|
await $`git config --global user.email "gitops@docupace.com"`;
|
|
90
94
|
await $`git config --global user.name "Gitops"`;
|
|
91
95
|
const repoUrl = `git@bitbucket.org:dp-cloud/${process.env.BITBUCKET_REPO_SLUG}.git`;
|
|
92
96
|
console.log(`Setting new origin: ${repoUrl}`);
|
|
93
97
|
await $`git remote set-url origin ${repoUrl}`;
|
|
98
|
+
|
|
99
|
+
const after = (await $`git remote get-url origin`).stdout.trim();
|
|
100
|
+
console.log(`setGitInfo: origin after = ${after}`);
|
|
101
|
+
} else {
|
|
102
|
+
const current = (await $`git remote get-url origin`).stdout.trim();
|
|
103
|
+
console.log(`setGitInfo: SET_GITOPS_USER not set, leaving origin as-is = ${current}`);
|
|
94
104
|
}
|
|
95
105
|
}
|