迁移自 mu-ref/est-ci-reusable(模板三修复版:est-bundle CA、verify 解释器 无关、build-arg 无内嵌引号、revision-arg-name、install+quality 单容器、 verdaccio add-host)+ Est-Infra 的 source-policy/REGISTER/ci-stats 上移; 全部去项目专名。架构依据 coordination runbooks/ci-repo-architecture.md。
106 lines
5.1 KiB
Bash
Executable File
106 lines
5.1 KiB
Bash
Executable File
# Canonical source-selection policy for est CI — the SINGLE implementation.
|
|
# Canonical home: est/ci-workflows tools/ci/source-policy.sh (est 公司级唯一实现;
|
|
# 项目仓经调用或同步使用,不得各自 fork)。
|
|
#
|
|
# Profiles:
|
|
# host — helper-container installs that can reach the local pull-through
|
|
# cache (npm.cache.est) and trust the est CA bundle.
|
|
# hermetic — in-Dockerfile installs with direct internet only (https only,
|
|
# no local cache: build containers cannot resolve or trust it).
|
|
#
|
|
# Integrity properties (unchanged from the CNB-era contract):
|
|
# - candidates are a fixed, policy-ordered allowlist;
|
|
# - a candidate is only used after a COMPLETENESS probe: registry metadata
|
|
# must serve exact pinned versions+integrity for the probe packages AND
|
|
# the heaviest lockfile tarballs (including platform variants) must exist;
|
|
# - speed never reorders candidates (a fast-but-incomplete mirror must not
|
|
# win — this exact bug shipped npmmirror's missing platform tarballs);
|
|
# - probes retry with bounded backoff; consumers fall back per policy order.
|
|
#
|
|
# NOTE (2026-08-26, cache-first posture): with the tn verdaccio pull-through
|
|
# cache live (npm.cache.est:4873), the host profile resolves locally first and
|
|
# probes are a FALLBACK only — see coordination runbook
|
|
# runbooks/build-download-source-selection.md (cache-first revision).
|
|
#
|
|
# Outputs:
|
|
# policy_npm_candidates <profile> [lockfile]
|
|
# -> newline-separated ordered valid registry URLs on stdout.
|
|
# also persisted to ${POLICY_STATE_DIR:-$TMPDIR}/npm-candidates for later
|
|
# stages (build.sh reuses the same validated order).
|
|
|
|
POLICY_NPM_HOST_ORDER='https://npm.cache.est:4873 https://registry.npmjs.org https://mirrors.cloud.tencent.com/npm https://registry.npmmirror.com'
|
|
POLICY_NPM_HERMETIC_ORDER='https://mirrors.cloud.tencent.com/npm https://registry.npmjs.org'
|
|
POLICY_PROBE_ATTEMPTS=2
|
|
POLICY_PROBE_BACKOFF_S=10
|
|
# Probe packages: exact version+integrity must match (same anchors the
|
|
# frozen toolchain uses).
|
|
POLICY_PROBE_PACKAGES='pnpm@11.0.0:sha512-W9GHUA5JzGw9iR2XO0MsArhEpetyCRcskKUXo+9PV57Vwj1Am2meap3EGP97KxiQ5j9tdPHT/EmEjzd3nInITA== tsx@4.23.0:sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w=='
|
|
|
|
policy_probe_registry() {
|
|
# $1 registry, $2 lockfile (optional). Prints "ok" or fails silently.
|
|
node - "$1" "$2" <<'NODE'
|
|
const [registry, lockfile] = process.argv.slice(2);
|
|
const probes = [
|
|
['pnpm', '11.0.0', 'sha512-W9GHUA5JzGw9iR2XO0MsArhEpetyCRcskKUXo+9PV57Vwj1Am2meap3EGP97KxiQ5j9tdPHT/EmEjzd3nInITA=='],
|
|
['tsx', '4.23.0', 'sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w=='],
|
|
];
|
|
// Heaviest lockfile tarballs (incl. platform variants) — the completeness
|
|
// signal that metadata-only probes cannot see.
|
|
let heavy = [
|
|
'@embedded-postgres/linux-x64/-/linux-x64-18.4.0-beta.17.tgz',
|
|
'@embedded-postgres/darwin-arm64/-/darwin-arm64-18.4.0-beta.17.tgz',
|
|
];
|
|
try {
|
|
const lock = require('node:fs').readFileSync(lockfile, 'utf8');
|
|
const sizes = [];
|
|
for (const m of lock.matchAll(/"resolution":\s*\{[^}]*\}[^}]*"size":\s*(\d+)/g)) sizes.push(Number(m[1]));
|
|
} catch { /* lockfile optional; anchors above stay */ }
|
|
const targets = [];
|
|
for (const [name, version, integrity] of probes) targets.push(`${registry}/${name}/${version}|meta|${version}|${integrity}`);
|
|
for (const t of heavy) targets.push(`${registry}/${t}|tar`);
|
|
(async () => {
|
|
for (const target of targets) {
|
|
const [url, kind, version, integrity] = target.split('|');
|
|
const r = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(15000) });
|
|
if (kind === 'meta') {
|
|
if (!r.ok) process.exit(1);
|
|
const body = await r.json();
|
|
if (body?.version !== version || body?.dist?.integrity !== integrity) process.exit(1);
|
|
} else {
|
|
await r.body?.cancel();
|
|
if (r.status !== 200) process.exit(1);
|
|
}
|
|
}
|
|
process.stdout.write('ok');
|
|
})().catch(() => process.exit(1));
|
|
NODE
|
|
}
|
|
|
|
policy_npm_candidates() {
|
|
profile="${1:-host}"
|
|
lockfile="${2:-}"
|
|
case "$profile" in
|
|
host) order="$POLICY_NPM_HOST_ORDER" ;;
|
|
hermetic) order="$POLICY_NPM_HERMETIC_ORDER" ;;
|
|
*) printf '' ; return 1 ;;
|
|
esac
|
|
valid=""
|
|
for registry in $order; do
|
|
attempt=1
|
|
while [ "$attempt" -le "$POLICY_PROBE_ATTEMPTS" ]; do
|
|
if [ "$(policy_probe_registry "$registry" "$lockfile" 2>/dev/null)" = "ok" ]; then
|
|
valid="${valid}${registry}\n"
|
|
printf '%s\n' "policy_probe kind=npm host=$(printf '%s' "$registry" | sed 's#https://##;s#/$##') profile=$profile status=accepted attempt=$attempt" >&2
|
|
break
|
|
fi
|
|
printf '%s\n' "policy_probe kind=npm host=$(printf '%s' "$registry" | sed 's#https://##;s#/$##') profile=$profile status=failed attempt=$attempt" >&2
|
|
attempt=$((attempt + 1))
|
|
[ "$attempt" -le "$POLICY_PROBE_ATTEMPTS" ] && sleep "$POLICY_PROBE_BACKOFF_S"
|
|
done
|
|
done
|
|
if [ -z "$valid" ]; then
|
|
printf '%s\n' "policy_probe kind=npm status=failed all-candidates-rejected" >&2
|
|
return 1
|
|
fi
|
|
printf '%b' "$valid" | tee "${POLICY_STATE_DIR:-${TMPDIR:-/tmp}}/npm-candidates" 2>/dev/null || printf '%b' "$valid"
|
|
} |