@jenga-ai/agent 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/agents/developer.md +18 -0
- package/agents/scrum-master.md +1 -0
- package/agents/tester.md +18 -0
- package/hooks/on_session_end.sh +27 -0
- package/lib/commands/init.js +23 -68
- package/lib/generate-copilot-instructions.js +142 -0
- package/package.json +18 -17
- package/scripts/postinstall.js +21 -0
- package/skills/commit/SKILL.md +11 -1
- package/skills/dev-done/SKILL.md +46 -0
- package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
- package/skills/init/SKILL.md +7 -6
- package/skills/init/assets/scope-thresholds_template.json +7 -0
- package/skills/init/scripts/init.sh +6 -0
- package/skills/publish/SKILL.md +66 -0
- package/skills/publish/adapters/npm-ci.md +34 -0
- package/skills/publish/adapters/npm.md +18 -0
- package/skills/publish/assets/ci-contract.md +27 -0
- package/skills/publish/assets/publish.example.json +27 -0
- package/skills/publish/schemas/publish.schema.json +20 -0
- package/skills/publish/scripts/npm_ci_pipeline.sh +29 -0
- package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
- package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
- package/skills/publish/scripts/publish_common.sh +16 -0
- package/skills/publish/scripts/show_history.sh +12 -5
- package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
- package/skills/publish/scripts/write_ledger_entry.sh +92 -2
- package/skills/reconcile/SKILL.md +121 -11
- package/skills/reconcile/assets/report_format.md +17 -0
- package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
- package/skills/uncharted/SKILL.md +200 -21
- package/skills/uncharted/scripts/directory-triage.sh +342 -0
- package/skills/uncharted/scripts/elicitation-state.sh +457 -0
- package/templates/SCRUM_BOARD_SCHEMA.md +57 -0
- package/templates/agent-context.md.tpl +23 -11
- package/templates/copilot-instructions.md.tpl +21 -11
- package/mcp/router/README.md +0 -19
- package/mcp/router/embedder.js +0 -23
- package/mcp/router/index.js +0 -204
- package/mcp/router/matcher.js +0 -87
- package/mcp/router/package-lock.json +0 -1048
- package/mcp/router/package.json +0 -11
- package/mcp/router/skill-index.js +0 -104
- package/skills/route/SKILL.md +0 -180
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# npm_stage_pipeline.sh — `/publish stage publish`: submit a version to npm's
|
|
3
|
+
# staged-publishing area, gated by the mandatory build/test pre-deploy gates.
|
|
4
|
+
#
|
|
5
|
+
# Six ordered phases — the order is the point, since gates that ran after
|
|
6
|
+
# staging would be verifying nothing:
|
|
7
|
+
# 1. validate — validate_npm_stage_env.sh (T01); abort on non-zero
|
|
8
|
+
# 2. gates — run_gates.sh pre <target> <config> [--non-interactive]
|
|
9
|
+
# (build/test are mandatory, global, non-disableable)
|
|
10
|
+
# 3. pack — resolve <name>@<version>, confirm unless --non-interactive
|
|
11
|
+
# 4. stage — for `npm` targets: `npm stage publish --tag ... --access
|
|
12
|
+
# ... [--otp ...]` run locally. For `npm-ci` targets,
|
|
13
|
+
# `--provenance` needs a GitHub Actions OIDC token that only
|
|
14
|
+
# exists inside an Actions run, so this phase instead
|
|
15
|
+
# dispatches the `stage` job of the target's generated
|
|
16
|
+
# workflow (see npm_ci_pipeline.sh) via `gh workflow run
|
|
17
|
+
# ... -f mode=stage` and waits on it with `gh run watch`.
|
|
18
|
+
# 5. capture — parse the stage id, falling back to `npm stage list --json`
|
|
19
|
+
# 6. ledger — write_ledger_entry.sh ... staged ... --stage-id <id> --dist-tag <tag>
|
|
20
|
+
#
|
|
21
|
+
# Usage:
|
|
22
|
+
# npm_stage_pipeline.sh <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]
|
|
23
|
+
#
|
|
24
|
+
# Exit codes:
|
|
25
|
+
# 0 success (staged, or --dry-run completed cleanly)
|
|
26
|
+
# 1 user declined the pack confirmation prompt (or no tty was available)
|
|
27
|
+
# 2 a mandatory pre-deploy gate failed; nothing was staged
|
|
28
|
+
# 3 staging failed (locally for `npm`, or the dispatched GitHub Actions
|
|
29
|
+
# run for `npm-ci`), or the stage id could not be captured
|
|
30
|
+
# 4 validation failed (validate_npm_stage_env.sh's own exit code, or bad
|
|
31
|
+
# args/config)
|
|
32
|
+
#
|
|
33
|
+
# Security note: --otp is placed into the exec argv passed directly to `npm`
|
|
34
|
+
# and nowhere else — never interpolated into a printed or logged string. If
|
|
35
|
+
# command tracing (`set -x`) is active when this script starts — inherited
|
|
36
|
+
# via SHELLOPTS/BASH_ENV, or the caller ran `bash -x npm_stage_pipeline.sh
|
|
37
|
+
# ... --otp <otp>` — and an --otp value is present in argv, tracing is
|
|
38
|
+
# disabled before OTP is ever assigned to a variable and stays disabled for
|
|
39
|
+
# the rest of the run (see the "OTP tracing guard" block below for why this
|
|
40
|
+
# has to be the whole run, not just the npm invocation). The captured output
|
|
41
|
+
# of the stage invocation is additionally scrubbed of the literal OTP value
|
|
42
|
+
# defensively, before it is ever printed.
|
|
43
|
+
|
|
44
|
+
set -euo pipefail
|
|
45
|
+
|
|
46
|
+
EXIT_ABORTED=1
|
|
47
|
+
EXIT_GATE_FAILURE=2
|
|
48
|
+
EXIT_STAGE_FAILURE=3
|
|
49
|
+
EXIT_ENV_INVALID=4
|
|
50
|
+
|
|
51
|
+
DEFAULT_REGISTRY="https://registry.npmjs.org"
|
|
52
|
+
|
|
53
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
54
|
+
|
|
55
|
+
if REPO_ROOT="$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel 2>/dev/null)"; then
|
|
56
|
+
:
|
|
57
|
+
else
|
|
58
|
+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# shellcheck source=publish_common.sh
|
|
62
|
+
source "${SCRIPT_DIR}/publish_common.sh"
|
|
63
|
+
|
|
64
|
+
VALIDATE_SCRIPT="${SCRIPT_DIR}/validate_npm_stage_env.sh"
|
|
65
|
+
RUN_GATES_SCRIPT="${SCRIPT_DIR}/run_gates.sh"
|
|
66
|
+
WRITE_LEDGER_SCRIPT="${SCRIPT_DIR}/write_ledger_entry.sh"
|
|
67
|
+
|
|
68
|
+
usage() {
|
|
69
|
+
cat <<'USAGE'
|
|
70
|
+
Usage: npm_stage_pipeline.sh <target> <path-to-publish.json> [--dry-run] [--non-interactive] [--otp <otp>]
|
|
71
|
+
|
|
72
|
+
Phases (in order): validate, gates, pack, stage, capture, ledger.
|
|
73
|
+
USAGE
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fail_usage() {
|
|
77
|
+
usage >&2
|
|
78
|
+
printf 'npm stage pipeline error: %s\n' "$1" >&2
|
|
79
|
+
exit "${EXIT_ENV_INVALID}"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
log_info() {
|
|
83
|
+
printf '→ %s\n' "$1"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
log_warn() {
|
|
87
|
+
printf '⚠ %s\n' "$1" >&2
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
# OTP tracing guard — must run before argv is parsed, i.e. before OTP is
|
|
92
|
+
# ever assigned to a variable. If the caller invoked this script with
|
|
93
|
+
# command tracing already enabled (`set -x`, inherited via SHELLOPTS/
|
|
94
|
+
# BASH_ENV, or `bash -x npm_stage_pipeline.sh ... --otp <otp>`) and an
|
|
95
|
+
# --otp value is present anywhere in argv, tracing is disabled immediately
|
|
96
|
+
# and stays disabled for the remainder of this run.
|
|
97
|
+
#
|
|
98
|
+
# A narrower window — e.g. suspending tracing only around the `npm stage
|
|
99
|
+
# publish` call itself and restoring it afterward — is NOT sufficient:
|
|
100
|
+
# bash's xtrace prints the *expanded* value of $OTP for every subsequent
|
|
101
|
+
# line that so much as references the variable. A plain truthiness check
|
|
102
|
+
# like `[[ -n "$OTP" ]]` is traced as `+ [[ -n 123456 ]]`, leaking the code
|
|
103
|
+
# just as surely as printing it directly. The only reliable guard is to
|
|
104
|
+
# suspend tracing before the first assignment and never re-enable it for
|
|
105
|
+
# the rest of the run.
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
OTP_PRESENT_IN_ARGV=0
|
|
108
|
+
for _arg in "$@"; do
|
|
109
|
+
case "${_arg}" in
|
|
110
|
+
--otp|--otp=*)
|
|
111
|
+
OTP_PRESENT_IN_ARGV=1
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
done
|
|
115
|
+
unset _arg
|
|
116
|
+
if (( OTP_PRESENT_IN_ARGV )); then
|
|
117
|
+
case "$-" in
|
|
118
|
+
*x*) set +x ;;
|
|
119
|
+
esac
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Arg parsing
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
DRY_RUN=0
|
|
126
|
+
NON_INTERACTIVE=0
|
|
127
|
+
OTP=""
|
|
128
|
+
|
|
129
|
+
POSITIONAL=()
|
|
130
|
+
while (( $# )); do
|
|
131
|
+
case "$1" in
|
|
132
|
+
--dry-run)
|
|
133
|
+
DRY_RUN=1
|
|
134
|
+
shift
|
|
135
|
+
;;
|
|
136
|
+
--non-interactive)
|
|
137
|
+
NON_INTERACTIVE=1
|
|
138
|
+
shift
|
|
139
|
+
;;
|
|
140
|
+
--otp)
|
|
141
|
+
[[ $# -ge 2 ]] || fail_usage "--otp requires a value"
|
|
142
|
+
OTP="$2"
|
|
143
|
+
shift 2
|
|
144
|
+
;;
|
|
145
|
+
--otp=*)
|
|
146
|
+
OTP="${1#*=}"
|
|
147
|
+
shift
|
|
148
|
+
;;
|
|
149
|
+
-h|--help)
|
|
150
|
+
usage
|
|
151
|
+
exit 0
|
|
152
|
+
;;
|
|
153
|
+
--)
|
|
154
|
+
shift
|
|
155
|
+
while (( $# )); do
|
|
156
|
+
POSITIONAL+=("$1")
|
|
157
|
+
shift
|
|
158
|
+
done
|
|
159
|
+
;;
|
|
160
|
+
-*)
|
|
161
|
+
fail_usage "unknown option: $1"
|
|
162
|
+
;;
|
|
163
|
+
*)
|
|
164
|
+
POSITIONAL+=("$1")
|
|
165
|
+
shift
|
|
166
|
+
;;
|
|
167
|
+
esac
|
|
168
|
+
done
|
|
169
|
+
|
|
170
|
+
[[ ${#POSITIONAL[@]} -ge 1 ]] || fail_usage "<target> is required"
|
|
171
|
+
TARGET_NAME="${POSITIONAL[0]}"
|
|
172
|
+
[[ ${#POSITIONAL[@]} -ge 2 ]] || fail_usage "<path-to-publish.json> is required"
|
|
173
|
+
CONFIG_PATH_ARG="${POSITIONAL[1]}"
|
|
174
|
+
[[ ${#POSITIONAL[@]} -le 2 ]] || fail_usage "unexpected extra positional arguments"
|
|
175
|
+
|
|
176
|
+
command -v jq >/dev/null 2>&1 || fail_usage "jq is required but not found on PATH"
|
|
177
|
+
command -v npm >/dev/null 2>&1 || fail_usage "npm is required but not found on PATH"
|
|
178
|
+
|
|
179
|
+
CONFIG_PATH="$(publish_normalize_path "${CONFIG_PATH_ARG}")"
|
|
180
|
+
[[ -f "${CONFIG_PATH}" ]] || fail_usage "publish config not found at '${CONFIG_PATH}'"
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# Phase 1: validate
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
log_info "[validate] checking staging environment for target '${TARGET_NAME}'..."
|
|
186
|
+
|
|
187
|
+
VALIDATE_STATUS=0
|
|
188
|
+
bash "${VALIDATE_SCRIPT}" "${TARGET_NAME}" "${CONFIG_PATH}" || VALIDATE_STATUS=$?
|
|
189
|
+
if [[ ${VALIDATE_STATUS} -ne 0 ]]; then
|
|
190
|
+
printf 'npm stage pipeline: environment validation failed (exit %s); nothing staged.\n' "${VALIDATE_STATUS}" >&2
|
|
191
|
+
exit "${VALIDATE_STATUS}"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Phase 2: gates — build/test are mandatory global pre-deploy gates for
|
|
196
|
+
# npm/npm-ci targets (enforced inside run_gates.sh itself; not repeated or
|
|
197
|
+
# re-implemented here) and cannot be disabled via target config.
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
log_info "[gates] running mandatory pre-deploy gates (build, test) for target '${TARGET_NAME}'..."
|
|
200
|
+
|
|
201
|
+
GATES_CMD=(bash "${RUN_GATES_SCRIPT}" pre "${TARGET_NAME}" "${CONFIG_PATH}")
|
|
202
|
+
(( NON_INTERACTIVE )) && GATES_CMD+=(--non-interactive)
|
|
203
|
+
|
|
204
|
+
GATES_STATUS=0
|
|
205
|
+
"${GATES_CMD[@]}" || GATES_STATUS=$?
|
|
206
|
+
if [[ ${GATES_STATUS} -ne 0 ]]; then
|
|
207
|
+
printf 'npm stage pipeline: pre-deploy gates failed; nothing staged.\n' >&2
|
|
208
|
+
exit "${EXIT_GATE_FAILURE}"
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# Phase 3: pack — resolve <name>@<version> and confirm
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
TARGET_JSON="$(jq -c --arg t "${TARGET_NAME}" '.targets[]? | select(.name == $t)' "${CONFIG_PATH}")"
|
|
215
|
+
[[ -n "${TARGET_JSON}" ]] || fail_usage "target '${TARGET_NAME}' was not found in '${CONFIG_PATH}'"
|
|
216
|
+
|
|
217
|
+
TARGET_TYPE="$(printf '%s' "${TARGET_JSON}" | jq -r '.type // empty')"
|
|
218
|
+
if [[ "${TARGET_TYPE}" != "npm" && "${TARGET_TYPE}" != "npm-ci" ]]; then
|
|
219
|
+
fail_usage "target '${TARGET_NAME}' has type '${TARGET_TYPE}'; staging only supports 'npm' and 'npm-ci'"
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
PACKAGE_NAME="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.package_name // empty')"
|
|
223
|
+
[[ -n "${PACKAGE_NAME}" ]] || fail_usage "target '${TARGET_NAME}' is missing required field 'npm.package_name'"
|
|
224
|
+
|
|
225
|
+
NPM_ACCESS="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.access // empty')"
|
|
226
|
+
[[ -n "${NPM_ACCESS}" ]] || fail_usage "target '${TARGET_NAME}' is missing required field 'npm.access'"
|
|
227
|
+
|
|
228
|
+
DIST_TAG="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.dist_tag // "latest"')"
|
|
229
|
+
REGISTRY="$(printf '%s' "${TARGET_JSON}" | jq -r --arg d "${DEFAULT_REGISTRY}" '.npm.registry // $d')"
|
|
230
|
+
|
|
231
|
+
PACKAGE_JSON="${REPO_ROOT}/package.json"
|
|
232
|
+
[[ -f "${PACKAGE_JSON}" ]] || fail_usage "package.json not found at '${PACKAGE_JSON}'"
|
|
233
|
+
|
|
234
|
+
PACKAGE_VERSION="$(jq -r '.version // empty' "${PACKAGE_JSON}")"
|
|
235
|
+
[[ -n "${PACKAGE_VERSION}" ]] || fail_usage "package.json is missing a 'version' field"
|
|
236
|
+
|
|
237
|
+
PACKAGE_SPEC="${PACKAGE_NAME}@${PACKAGE_VERSION}"
|
|
238
|
+
|
|
239
|
+
echo "========== NPM STAGE PIPELINE =========="
|
|
240
|
+
printf 'Target: %s (%s)\n' "${TARGET_NAME}" "${TARGET_TYPE}"
|
|
241
|
+
printf 'Package: %s\n' "${PACKAGE_SPEC}"
|
|
242
|
+
printf 'Dist tag: %s\n' "${DIST_TAG}"
|
|
243
|
+
printf 'Access: %s\n' "${NPM_ACCESS}"
|
|
244
|
+
printf 'Registry: %s\n' "${REGISTRY}"
|
|
245
|
+
printf 'Mode: %s\n' "$( (( DRY_RUN )) && echo 'dry-run' || echo 'live stage' )"
|
|
246
|
+
echo "=========================================="
|
|
247
|
+
|
|
248
|
+
if (( NON_INTERACTIVE == 0 )); then
|
|
249
|
+
PACK_CONFIRM_FD=""
|
|
250
|
+
if [[ -t 0 ]] && exec 3</dev/tty 2>/dev/null; then
|
|
251
|
+
PACK_CONFIRM_FD=3
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
if [[ -z "${PACK_CONFIRM_FD}" ]]; then
|
|
255
|
+
log_warn "no interactive tty available for pack confirmation; treating as declined. Pass --non-interactive to bypass."
|
|
256
|
+
PACK_RESPONSE=""
|
|
257
|
+
else
|
|
258
|
+
printf 'Stage %s to target "%s"? [y/N] ' "${PACKAGE_SPEC}" "${TARGET_NAME}" >&2
|
|
259
|
+
if ! IFS= read -r -u "${PACK_CONFIRM_FD}" PACK_RESPONSE; then
|
|
260
|
+
PACK_RESPONSE=""
|
|
261
|
+
fi
|
|
262
|
+
exec 3<&- 2>/dev/null || true
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
case "${PACK_RESPONSE:-}" in
|
|
266
|
+
y|Y|yes|YES)
|
|
267
|
+
: ;;
|
|
268
|
+
*)
|
|
269
|
+
printf 'npm stage pipeline: pack confirmation declined; nothing staged.\n' >&2
|
|
270
|
+
exit "${EXIT_ABORTED}"
|
|
271
|
+
;;
|
|
272
|
+
esac
|
|
273
|
+
else
|
|
274
|
+
log_info "[pack] --non-interactive: skipping confirmation prompt for ${PACKAGE_SPEC}"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
# Phase 4: stage
|
|
279
|
+
#
|
|
280
|
+
# `npm` targets stage locally via `npm stage publish`. `npm-ci` targets pass
|
|
281
|
+
# `--provenance`, which requires a GitHub Actions OIDC token that only
|
|
282
|
+
# exists inside an Actions run — there is no local equivalent — so instead
|
|
283
|
+
# this dispatches the `stage` job of the target's generated workflow (the
|
|
284
|
+
# same workflow file/OIDC Trusted Publisher link `npm_ci_pipeline.sh` uses
|
|
285
|
+
# for `/publish deploy`) via `gh workflow run ... -f mode=stage` and waits
|
|
286
|
+
# for it with `gh run watch`.
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
STAGE_OUTPUT=""
|
|
289
|
+
STAGE_STATUS=0
|
|
290
|
+
|
|
291
|
+
if [[ "${TARGET_TYPE}" == "npm-ci" ]]; then
|
|
292
|
+
command -v gh >/dev/null 2>&1 || fail_usage "'gh' CLI is required to stage an 'npm-ci' target (staging is dispatched as a GitHub Actions workflow run) but was not found on PATH"
|
|
293
|
+
|
|
294
|
+
GITHUB_REPO="$(printf '%s' "${TARGET_JSON}" | jq -r '.github_repo // empty')"
|
|
295
|
+
[[ -n "${GITHUB_REPO}" ]] || fail_usage "target '${TARGET_NAME}' is missing required field 'github_repo' (needed to dispatch the stage job)"
|
|
296
|
+
|
|
297
|
+
WORKFLOW_PATH="$(printf '%s' "${TARGET_JSON}" | jq -r '.workflow_path // ".github/workflows/npm-publish.yml"')"
|
|
298
|
+
WORKFLOW_FILENAME="$(basename "${WORKFLOW_PATH}")"
|
|
299
|
+
|
|
300
|
+
if (( DRY_RUN )); then
|
|
301
|
+
log_info "[dry-run] would dispatch: gh workflow run ${WORKFLOW_FILENAME} --repo ${GITHUB_REPO} -f mode=stage (tag=${DIST_TAG}, access=${NPM_ACCESS})"
|
|
302
|
+
log_info "[dry-run] no workflow run triggered; no ledger entry written, no registry-mutating call made."
|
|
303
|
+
exit 0
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
log_info "[stage] dispatching 'stage' job of ${WORKFLOW_FILENAME} on ${GITHUB_REPO}..."
|
|
307
|
+
if ! gh workflow run "${WORKFLOW_FILENAME}" --repo "${GITHUB_REPO}" -f mode=stage; then
|
|
308
|
+
printf 'npm stage pipeline: gh workflow run failed; nothing staged.\n' >&2
|
|
309
|
+
exit "${EXIT_STAGE_FAILURE}"
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
RUN_ID="$(gh run list --workflow "${WORKFLOW_FILENAME}" --repo "${GITHUB_REPO}" --limit 1 --json databaseId --jq '.[0].databaseId')"
|
|
313
|
+
[[ -n "${RUN_ID}" ]] || fail_usage "dispatched the stage workflow but could not resolve its run id via 'gh run list'"
|
|
314
|
+
|
|
315
|
+
log_info "[stage] waiting on run ${RUN_ID}..."
|
|
316
|
+
gh run watch "${RUN_ID}" --repo "${GITHUB_REPO}" --exit-status || STAGE_STATUS=$?
|
|
317
|
+
|
|
318
|
+
RUN_URL="$(gh run view "${RUN_ID}" --repo "${GITHUB_REPO}" --json url --jq '.url' 2>/dev/null || true)"
|
|
319
|
+
STAGE_OUTPUT="staged via GitHub Actions workflow run: ${RUN_URL}"
|
|
320
|
+
else
|
|
321
|
+
STAGE_CMD_BASE=(npm stage publish --tag "${DIST_TAG}" --access "${NPM_ACCESS}")
|
|
322
|
+
[[ -n "${REGISTRY}" ]] && STAGE_CMD_BASE+=(--registry "${REGISTRY}")
|
|
323
|
+
|
|
324
|
+
DISPLAY_CMD=("${STAGE_CMD_BASE[@]}")
|
|
325
|
+
(( DRY_RUN )) && DISPLAY_CMD+=(--dry-run)
|
|
326
|
+
[[ -n "${OTP}" ]] && DISPLAY_CMD+=(--otp '********')
|
|
327
|
+
|
|
328
|
+
log_info "[stage] resolved command: $(printf '%q ' "${DISPLAY_CMD[@]}")"
|
|
329
|
+
|
|
330
|
+
EXEC_CMD=("${STAGE_CMD_BASE[@]}")
|
|
331
|
+
(( DRY_RUN )) && EXEC_CMD+=(--dry-run)
|
|
332
|
+
# Tracing was already suspended for the rest of this run (if it was
|
|
333
|
+
# active) by the "OTP tracing guard" above, the moment argv was found to
|
|
334
|
+
# contain --otp — before OTP was ever assigned to a variable. Nothing
|
|
335
|
+
# further to do here.
|
|
336
|
+
[[ -n "${OTP}" ]] && EXEC_CMD+=(--otp "${OTP}")
|
|
337
|
+
|
|
338
|
+
STAGE_OUTPUT="$(cd "${REPO_ROOT}" && "${EXEC_CMD[@]}" 2>&1)" || STAGE_STATUS=$?
|
|
339
|
+
|
|
340
|
+
# Defensive redaction: strip the literal OTP from captured output before
|
|
341
|
+
# it is ever printed, in case npm echoed the argv back in an error
|
|
342
|
+
# message. Quoting the pattern operand of ${var//pattern/repl} forces a
|
|
343
|
+
# literal (non-glob) match, so this is safe even if the OTP happens to
|
|
344
|
+
# contain characters that are special to bash's extglob pattern matching.
|
|
345
|
+
if [[ -n "${OTP}" ]]; then
|
|
346
|
+
STAGE_OUTPUT="${STAGE_OUTPUT//"${OTP}"/********}"
|
|
347
|
+
fi
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
printf '%s\n' "${STAGE_OUTPUT}"
|
|
351
|
+
|
|
352
|
+
if [[ ${STAGE_STATUS} -ne 0 ]]; then
|
|
353
|
+
printf 'npm stage pipeline: staging failed (exit %s); nothing staged.\n' "${STAGE_STATUS}" >&2
|
|
354
|
+
exit "${EXIT_STAGE_FAILURE}"
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
if (( DRY_RUN )); then
|
|
358
|
+
log_info "[dry-run] npm stage publish --dry-run completed; no ledger entry written, no registry-mutating call made."
|
|
359
|
+
exit 0
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# ---------------------------------------------------------------------------
|
|
363
|
+
# Phase 5: capture — parse the stage id
|
|
364
|
+
# ---------------------------------------------------------------------------
|
|
365
|
+
log_info "[capture] parsing stage id from stage output..."
|
|
366
|
+
|
|
367
|
+
STAGE_ID=""
|
|
368
|
+
|
|
369
|
+
# Attempt 1: direct-output parsing. Tolerant of label variants npm may use
|
|
370
|
+
# ("stage id:", "stageId:", "stage_id="), case-insensitive.
|
|
371
|
+
STAGE_ID="$(printf '%s\n' "${STAGE_OUTPUT}" \
|
|
372
|
+
| grep -Eio '\bstage[ _-]?id["'"'"']?[[:space:]]*[:=][[:space:]]*["'"'"']?[A-Za-z0-9._-]+' \
|
|
373
|
+
| head -n 1 \
|
|
374
|
+
| grep -Eo '[A-Za-z0-9._-]+$' || true)"
|
|
375
|
+
|
|
376
|
+
# Attempt 2: the captured output is itself JSON (e.g. if npm's stage publish
|
|
377
|
+
# supports --json the way `npm publish --json` does).
|
|
378
|
+
if [[ -z "${STAGE_ID}" ]] && printf '%s' "${STAGE_OUTPUT}" | jq -e . >/dev/null 2>&1; then
|
|
379
|
+
STAGE_ID="$(printf '%s' "${STAGE_OUTPUT}" | jq -r '.id // .stageId // .stage_id // empty' 2>/dev/null || true)"
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
# Attempt 3: fall back to `npm stage list <package>@<version> --json` and
|
|
383
|
+
# extract the most recent matching entry's id.
|
|
384
|
+
if [[ -z "${STAGE_ID}" ]]; then
|
|
385
|
+
log_warn "could not parse a stage id directly from stage output; falling back to 'npm stage list --json'..."
|
|
386
|
+
|
|
387
|
+
LIST_STATUS=0
|
|
388
|
+
LIST_OUTPUT="$(npm stage list "${PACKAGE_SPEC}" --json 2>&1)" || LIST_STATUS=$?
|
|
389
|
+
|
|
390
|
+
if [[ ${LIST_STATUS} -ne 0 ]]; then
|
|
391
|
+
printf '%s\n' "${LIST_OUTPUT}" >&2
|
|
392
|
+
printf 'npm stage pipeline: staged successfully but stage id capture failed (npm stage list also exited %s).\n' "${LIST_STATUS}" >&2
|
|
393
|
+
exit "${EXIT_STAGE_FAILURE}"
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
if printf '%s' "${LIST_OUTPUT}" | jq -e . >/dev/null 2>&1; then
|
|
397
|
+
STAGE_ID="$(printf '%s' "${LIST_OUTPUT}" | jq -r --arg pkg "${PACKAGE_NAME}" --arg ver "${PACKAGE_VERSION}" '
|
|
398
|
+
( if (type == "array") then . else (.stages? // .items? // []) end ) as $entries
|
|
399
|
+
| [ $entries[]? | select(((.name // .package // "") == $pkg) and ((.version // "") == $ver)) ]
|
|
400
|
+
| sort_by(.stagedAt // .staged_at // .created // .createdAt // "")
|
|
401
|
+
| last
|
|
402
|
+
| (.id // .stageId // .stage_id // empty)
|
|
403
|
+
' 2>/dev/null || true)"
|
|
404
|
+
fi
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
if [[ -z "${STAGE_ID}" ]]; then
|
|
408
|
+
printf 'npm stage pipeline: staged successfully but the stage id could not be captured from either the direct output or "npm stage list --json". Run "npm stage list %s --json" manually to recover it.\n' "${PACKAGE_SPEC}" >&2
|
|
409
|
+
exit "${EXIT_STAGE_FAILURE}"
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
echo ""
|
|
413
|
+
echo "=========================================="
|
|
414
|
+
printf ' STAGE ID: %s\n' "${STAGE_ID}"
|
|
415
|
+
echo "=========================================="
|
|
416
|
+
echo ""
|
|
417
|
+
|
|
418
|
+
# ---------------------------------------------------------------------------
|
|
419
|
+
# Phase 6: ledger — record the staged entry
|
|
420
|
+
# ---------------------------------------------------------------------------
|
|
421
|
+
log_info "[ledger] recording 'staged' ledger entry for ${PACKAGE_SPEC}..."
|
|
422
|
+
|
|
423
|
+
LEDGER_CMD=(bash "${WRITE_LEDGER_SCRIPT}" "${TARGET_NAME}" "${TARGET_TYPE}" staged "" \
|
|
424
|
+
--version "${PACKAGE_VERSION}" --config "${CONFIG_PATH}" --stage-id "${STAGE_ID}" --dist-tag "${DIST_TAG}")
|
|
425
|
+
"${LEDGER_CMD[@]}"
|
|
426
|
+
|
|
427
|
+
log_info "[done] ${PACKAGE_SPEC} staged as ${STAGE_ID} (dist-tag: ${DIST_TAG}). Awaiting 'npm_stage_inspect.sh test' and approval."
|
|
@@ -167,6 +167,22 @@ publish_history_has_version() {
|
|
|
167
167
|
jq -e --arg version "$version" 'type == "array" and any(.[]?; (.version? // "") == $version)' "$history_file" >/dev/null 2>&1
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
# publish_history_has_passing_stage_test <history_file> <stage_id>
|
|
171
|
+
#
|
|
172
|
+
# True (exit 0) when the ledger contains at least one `stage_tested` entry
|
|
173
|
+
# for the exact given stage id with result == "pass". Used by the
|
|
174
|
+
# npm_stage_inspect.sh `approve` interlock. A missing/unreadable history
|
|
175
|
+
# file is treated as "no passing test on record" (exit 1), not an error —
|
|
176
|
+
# the caller decides how to report that.
|
|
177
|
+
publish_history_has_passing_stage_test() {
|
|
178
|
+
local history_file="$1"
|
|
179
|
+
local stage_id="$2"
|
|
180
|
+
[[ -f "$history_file" ]] || return 1
|
|
181
|
+
jq -e --arg id "$stage_id" \
|
|
182
|
+
'type == "array" and any(.[]?; (.platform_state? // "") == "stage_tested" and (.stage_id? // "") == $id and (.result? // "") == "pass")' \
|
|
183
|
+
"$history_file" >/dev/null 2>&1
|
|
184
|
+
}
|
|
185
|
+
|
|
170
186
|
publish_resolve_last_publish_tag() {
|
|
171
187
|
local history_file="$1"
|
|
172
188
|
local ref="${2:-HEAD}"
|
|
@@ -87,9 +87,16 @@ if (( JSON_OUTPUT )); then
|
|
|
87
87
|
exit 0
|
|
88
88
|
fi
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
# Staged-publishing states (`staged`, `stage_tested`) are NOT published — a
|
|
91
|
+
# version sitting there must never read as released. The State column
|
|
92
|
+
# already carries a distinct value for each (vs. `uploaded` for a real
|
|
93
|
+
# release), and the Stage ID column below makes a staged/tested/approved/
|
|
94
|
+
# rejected row visibly distinct at a glance even without reading the State
|
|
95
|
+
# column closely: only staged-publishing entries ever have a non-"-" stage
|
|
96
|
+
# id.
|
|
97
|
+
printf '%-10s %-20s %-20s %-14s %-24s %s\n' 'Version' 'Target' 'Date' 'State' 'Stage ID' 'Tag'
|
|
98
|
+
printf '%-10s %-20s %-20s %-14s %-24s %s\n' '-------' '------' '----' '-----' '--------' '---'
|
|
99
|
+
printf '%s\n' "$FILTERED_JSON" | jq -r '.[] | [(.version // "-"), (.target // "-"), (.timestamp // .completed_at // .started_at // "-"), (.platform_state // .state // "-"), (.stage_id // "-"), (.git_tag // .version // "-")] | @tsv' | \
|
|
100
|
+
while IFS=$'\t' read -r version target date state stage_id tag; do
|
|
101
|
+
printf '%-10s %-20s %-20s %-14s %-24s %s\n' "$version" "$target" "$date" "$state" "$stage_id" "$tag"
|
|
95
102
|
done
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# validate_npm_stage_env.sh — Preflight gate for `/publish stage`.
|
|
3
|
+
#
|
|
4
|
+
# npm's staged-publishing workflow (npm CLI >= 11.15.0, Node >= 22.14.0) only exists on
|
|
5
|
+
# recent toolchains, only applies to `npm`/`npm-ci` targets, and only works for a package
|
|
6
|
+
# that has already had at least one non-staged publish. This script checks all four
|
|
7
|
+
# preconditions up front so the rest of the `/publish stage` pipeline never has to discover
|
|
8
|
+
# one of them has failed midway through a staging attempt.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# validate_npm_stage_env.sh <target> <path-to-publish.json>
|
|
12
|
+
#
|
|
13
|
+
# Exit codes:
|
|
14
|
+
# 0 all four preflight checks passed
|
|
15
|
+
# 4 one of the checks failed (config/environment invalid), matching
|
|
16
|
+
# skills/publish/assets/ci-contract.md
|
|
17
|
+
#
|
|
18
|
+
# Security note: the registry probe (check 4) never prints npm's raw stdout/stderr. Only
|
|
19
|
+
# messages composed by this script are emitted, so nothing npm writes — which could in
|
|
20
|
+
# principle include auth-related registry response detail — reaches stdout, stderr, or any
|
|
21
|
+
# log this script's caller might capture.
|
|
22
|
+
|
|
23
|
+
set -u
|
|
24
|
+
|
|
25
|
+
EXIT_ENV_INVALID=4
|
|
26
|
+
|
|
27
|
+
NPM_MIN_VERSION="11.15.0"
|
|
28
|
+
NODE_MIN_VERSION="22.14.0"
|
|
29
|
+
DEFAULT_REGISTRY="https://registry.npmjs.org"
|
|
30
|
+
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Shared helpers (repo-root resolution, path normalization) — reused rather
|
|
35
|
+
# than re-implemented, per the repo-root-then-project/configs/ convention
|
|
36
|
+
# already established in publish_common.sh.
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# shellcheck source=publish_common.sh
|
|
39
|
+
source "${SCRIPT_DIR}/publish_common.sh"
|
|
40
|
+
|
|
41
|
+
fail() {
|
|
42
|
+
printf 'stage env validation failed: %s\n' "$1" >&2
|
|
43
|
+
exit "${EXIT_ENV_INVALID}"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
usage() {
|
|
47
|
+
cat <<'USAGE'
|
|
48
|
+
Usage: validate_npm_stage_env.sh <target> <path-to-publish.json>
|
|
49
|
+
USAGE
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# Semver comparison — component-wise numeric compare, boundary inclusive.
|
|
54
|
+
# A naive string or float compare gets "11.9.0 < 11.15.0" backwards (float
|
|
55
|
+
# compare would read 11.9 > 11.15); comparing each dotted component as a
|
|
56
|
+
# base-10 integer gets it right regardless of leading zeros.
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
version_ge() {
|
|
59
|
+
local version="$1" min="$2"
|
|
60
|
+
local -a v_parts m_parts
|
|
61
|
+
IFS='.' read -r -a v_parts <<< "$version"
|
|
62
|
+
IFS='.' read -r -a m_parts <<< "$min"
|
|
63
|
+
|
|
64
|
+
local i v m
|
|
65
|
+
for i in 0 1 2; do
|
|
66
|
+
v="${v_parts[i]:-0}"
|
|
67
|
+
m="${m_parts[i]:-0}"
|
|
68
|
+
# Strip any non-digit suffix (pre-release/build metadata) so `10#` never
|
|
69
|
+
# trips over a stray character; default to 0 if nothing numeric remains.
|
|
70
|
+
v="${v//[!0-9]/}"
|
|
71
|
+
m="${m//[!0-9]/}"
|
|
72
|
+
v="${v:-0}"
|
|
73
|
+
m="${m:-0}"
|
|
74
|
+
if ((10#$v > 10#$m)); then
|
|
75
|
+
return 0
|
|
76
|
+
elif ((10#$v < 10#$m)); then
|
|
77
|
+
return 1
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
return 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Argument & dependency sanity checks
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
TARGET_NAME="${1:-}"
|
|
87
|
+
CONFIG_PATH_ARG="${2:-}"
|
|
88
|
+
|
|
89
|
+
if [[ -z "${TARGET_NAME}" ]]; then
|
|
90
|
+
usage >&2
|
|
91
|
+
fail "missing target name argument"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
command -v jq >/dev/null 2>&1 || fail "jq is required but not found on PATH"
|
|
95
|
+
command -v npm >/dev/null 2>&1 || fail "'npm' was not found on PATH. Install Node.js (which provides npm) from https://nodejs.org/ or via your package manager, then re-run."
|
|
96
|
+
command -v node >/dev/null 2>&1 || fail "'node' was not found on PATH. Install Node.js from https://nodejs.org/ or via your package manager, then re-run."
|
|
97
|
+
|
|
98
|
+
# Resolve the config path: normalize a supplied relative/absolute path via
|
|
99
|
+
# publish_common.sh, or fall back to the repo-root-then-project/configs/
|
|
100
|
+
# search already used elsewhere in this skill.
|
|
101
|
+
if [[ -n "${CONFIG_PATH_ARG}" ]]; then
|
|
102
|
+
CONFIG_PATH="$(publish_normalize_path "${CONFIG_PATH_ARG}")"
|
|
103
|
+
elif [[ -f "${PUBLISH_REPO_ROOT}/publish.json" ]]; then
|
|
104
|
+
CONFIG_PATH="${PUBLISH_REPO_ROOT}/publish.json"
|
|
105
|
+
elif [[ -f "${PUBLISH_DEFAULT_CONFIG_FILE}" ]]; then
|
|
106
|
+
CONFIG_PATH="${PUBLISH_DEFAULT_CONFIG_FILE}"
|
|
107
|
+
else
|
|
108
|
+
usage >&2
|
|
109
|
+
fail "missing publish.json path argument, and no default config found at '${PUBLISH_REPO_ROOT}/publish.json' or '${PUBLISH_DEFAULT_CONFIG_FILE}'"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
[[ -f "${CONFIG_PATH}" ]] || fail "publish config not found at '${CONFIG_PATH}'"
|
|
113
|
+
jq -e . "${CONFIG_PATH}" >/dev/null 2>&1 || fail "publish config at '${CONFIG_PATH}' is not valid JSON"
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Check 1 — npm CLI version >= 11.15.0
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
NPM_VERSION_RAW="$(npm --version 2>/dev/null)"
|
|
119
|
+
NPM_VERSION="${NPM_VERSION_RAW#v}"
|
|
120
|
+
if [[ -z "${NPM_VERSION}" ]]; then
|
|
121
|
+
fail "could not determine npm CLI version ('npm --version' produced no output)"
|
|
122
|
+
fi
|
|
123
|
+
if ! version_ge "${NPM_VERSION}" "${NPM_MIN_VERSION}"; then
|
|
124
|
+
fail "npm CLI version ${NPM_VERSION} is below the minimum required for staged publishing (${NPM_MIN_VERSION}). Upgrade with 'npm install -g npm@latest' (or any npm >= ${NPM_MIN_VERSION}) and re-run."
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Check 2 — Node version >= 22.14.0
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
NODE_VERSION_RAW="$(node --version 2>/dev/null)"
|
|
131
|
+
NODE_VERSION="${NODE_VERSION_RAW#v}"
|
|
132
|
+
if [[ -z "${NODE_VERSION}" ]]; then
|
|
133
|
+
fail "could not determine Node.js version ('node --version' produced no output)"
|
|
134
|
+
fi
|
|
135
|
+
if ! version_ge "${NODE_VERSION}" "${NODE_MIN_VERSION}"; then
|
|
136
|
+
fail "Node.js version ${NODE_VERSION} is below the minimum required for staged publishing (${NODE_MIN_VERSION}). Upgrade Node.js to ${NODE_MIN_VERSION} or newer (https://nodejs.org/) and re-run."
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# ---------------------------------------------------------------------------
|
|
140
|
+
# Check 3 — target exists and its type is `npm` or `npm-ci`
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
TARGET_JSON="$(jq -c --arg t "${TARGET_NAME}" '.targets[]? | select(.name == $t)' "${CONFIG_PATH}")"
|
|
143
|
+
if [[ -z "${TARGET_JSON}" ]]; then
|
|
144
|
+
fail "target '${TARGET_NAME}' was not found in '${CONFIG_PATH}'"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
TARGET_TYPE="$(printf '%s' "${TARGET_JSON}" | jq -r '.type // empty')"
|
|
148
|
+
if [[ "${TARGET_TYPE}" != "npm" && "${TARGET_TYPE}" != "npm-ci" ]]; then
|
|
149
|
+
if [[ -z "${TARGET_TYPE}" ]]; then
|
|
150
|
+
fail "target '${TARGET_NAME}' is missing a type. Staging is only supported for target types 'npm' and 'npm-ci'."
|
|
151
|
+
fi
|
|
152
|
+
fail "target '${TARGET_NAME}' has type '${TARGET_TYPE}'. Staging is only supported for target types 'npm' and 'npm-ci' — use '/publish deploy' for other target types."
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Check 4 — the package already exists on the registry
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
PACKAGE_NAME="$(printf '%s' "${TARGET_JSON}" | jq -r '.npm.package_name // empty')"
|
|
159
|
+
if [[ -z "${PACKAGE_NAME}" ]]; then
|
|
160
|
+
fail "target '${TARGET_NAME}' is missing required field 'npm.package_name'"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
REGISTRY="$(printf '%s' "${TARGET_JSON}" | jq -r --arg d "${DEFAULT_REGISTRY}" '.npm.registry // $d')"
|
|
164
|
+
|
|
165
|
+
# Capture npm's output but never print it — it is not needed for the
|
|
166
|
+
# messages below, and not printing it is a hard guarantee against ever
|
|
167
|
+
# leaking anything npm's registry client writes (including, in principle,
|
|
168
|
+
# auth-related response detail) into this script's own stdout/stderr.
|
|
169
|
+
REGISTRY_PROBE_OUTPUT="$(npm view "${PACKAGE_NAME}" version --registry "${REGISTRY}" 2>&1)"
|
|
170
|
+
REGISTRY_PROBE_EXIT=$?
|
|
171
|
+
|
|
172
|
+
if [[ ${REGISTRY_PROBE_EXIT} -ne 0 ]]; then
|
|
173
|
+
if printf '%s' "${REGISTRY_PROBE_OUTPUT}" | grep -qi 'E404\|404 Not Found\|is not in this registry'; then
|
|
174
|
+
fail "package '${PACKAGE_NAME}' was not found on registry '${REGISTRY}' (404). npm cannot stage a package that has never been published — run '/publish deploy' for the first release; staging becomes available from the second release onward."
|
|
175
|
+
fi
|
|
176
|
+
fail "could not verify that package '${PACKAGE_NAME}' exists on registry '${REGISTRY}' (npm view exited ${REGISTRY_PROBE_EXIT}). Check network connectivity and registry configuration, then re-run."
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# All checks passed
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
printf '[validate] stage environment OK — npm %s, node %s, target "%s" (%s), package "%s" resolves on %s\n' \
|
|
183
|
+
"${NPM_VERSION}" "${NODE_VERSION}" "${TARGET_NAME}" "${TARGET_TYPE}" "${PACKAGE_NAME}" "${REGISTRY}"
|
|
184
|
+
exit 0
|