mirror of
https://github.com/penpot/penpot.git
synced 2026-09-21 03:45:05 -04:00
Corepack is gone from Node 25+, so every `corepack enable` call fails. pnpm now ships as a system binary (devenv, CI runners and Docker images install it directly) and auto-downloads the version pinned in `packageManager` on mismatch. Scripts, workflows and Dockerfiles call `pnpm` straight away; the three deploy workflows use a single `pnpm/setup@v2` step; and the new `scripts/sync-pnpm-version` stamps all 35 `packageManager` fields from the system pnpm, replacing the `corepack use` sweep. AI-assisted-by: muse-spark-1.3-contributor
186 lines
5.6 KiB
Bash
Executable File
186 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Sync the `packageManager` field of every first-party package.json to the
|
|
# system pnpm version.
|
|
#
|
|
# Only `pnpm` and `node` are required (plus registry access to resolve the
|
|
# integrity hash, unless --field is given).
|
|
#
|
|
# Run it from the repo root with the log redirected to a file (never pipe
|
|
# tool output through filters).
|
|
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: scripts/sync-pnpm-version [options] [version]
|
|
|
|
Stamp every first-party package.json's `packageManager` field with the
|
|
system pnpm version (default: `pnpm --version`).
|
|
|
|
Options:
|
|
--field FIELD Use FIELD verbatim, skip the registry lookup.
|
|
For tests and offline use.
|
|
--check Verify all fields match; do not write. Exit 1 on drift.
|
|
--install After stamping, run `pnpm install` in each workspace
|
|
root to refresh lockfile metadata.
|
|
--root DIR Scan DIR instead of the repo root. For tests.
|
|
-h, --help Show this help.
|
|
|
|
external/ (vendored trees with their own lifecycles), .opencode/,
|
|
.pnpm-store/, bundles/ and docker build-context copies are never stamped.
|
|
EOF
|
|
}
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
FIELD=""
|
|
CHECK=0
|
|
INSTALL=0
|
|
VERSION=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--field) FIELD="$2"; shift 2 ;;
|
|
--field=*) FIELD="${1#--field=}"; shift ;;
|
|
--check) CHECK=1; shift ;;
|
|
--install) INSTALL=1; shift ;;
|
|
--root) ROOT="$2"; shift 2 ;;
|
|
--root=*) ROOT="${1#--root=}"; shift ;;
|
|
-h | --help) usage; exit 0 ;;
|
|
-*) {
|
|
echo "error: unknown option: $1" >&2
|
|
usage >&2
|
|
exit 64
|
|
} ;;
|
|
*)
|
|
if [[ -n "$VERSION" ]]; then
|
|
echo "error: only one version argument allowed" >&2
|
|
usage >&2
|
|
exit 64
|
|
fi
|
|
VERSION="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$FIELD" ]]; then
|
|
if [[ -z "$VERSION" ]]; then
|
|
VERSION="$(pnpm --version | tr -d '[:space:]')"
|
|
fi
|
|
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-.+)?$ ]]; then
|
|
echo "error: invalid pnpm version: $VERSION" >&2
|
|
exit 64
|
|
fi
|
|
# Same value `npm view pnpm@<v> dist.integrity` returns; pnpm is the one
|
|
# tool guaranteed to exist everywhere, so no npm needed.
|
|
integrity="$(pnpm view "pnpm@${VERSION}" dist.integrity | tr -d '[:space:]')"
|
|
b64="${integrity#sha512-}"
|
|
if ! [[ "$b64" =~ ^[A-Za-z0-9+/=]+$ ]]; then
|
|
echo "error: unexpected integrity for pnpm@${VERSION}: ${integrity}" >&2
|
|
exit 1
|
|
fi
|
|
# node ships with every pnpm, so no python3 needed for base64 -> hex.
|
|
hex="$(node -p "Buffer.from('${b64}','base64').toString('hex')")"
|
|
FIELD="pnpm@${VERSION}+sha512.${hex}"
|
|
fi
|
|
|
|
cd "$ROOT"
|
|
|
|
# Same exclusions as scripts/clean-node-modules, plus build-context copies
|
|
# (bundles/, docker/images/bundle-*) which builds regenerate from the
|
|
# stamped sources.
|
|
mapfile -t files < <(
|
|
find . \
|
|
\( -name .git -o -name node_modules -o -name .pnpm-store \
|
|
-o -name external -o -name .opencode -o -name bundles \
|
|
-o -name .angular \) -type d -prune \
|
|
-o -path './docker/images/bundle-*' -prune \
|
|
-o -name package.json -type f -print | sort
|
|
)
|
|
|
|
if [[ ${#files[@]} -eq 0 ]]; then
|
|
echo "error: no package.json files found under $ROOT" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Stamp one file with node: replaces only the packageManager value when the
|
|
# key exists (indent, key order and the rest stay byte-identical), else
|
|
# inserts the key after "name" using the file's own indent. Prints
|
|
# "updated <rel>" / "unchanged <rel>" / "missing <rel>" / "mismatch <rel>".
|
|
stamp_one() {
|
|
node -e '
|
|
const fs = require("fs");
|
|
const [file, field, check] = process.argv.slice(1);
|
|
const raw = fs.readFileSync(file, "utf8");
|
|
const data = JSON.parse(raw);
|
|
const rel = file.replace(/^\.\//, "");
|
|
if (data.packageManager === field) {
|
|
console.log(`unchanged ${rel}`);
|
|
} else if (check === "1") {
|
|
console.log(`${data.packageManager === undefined ? "missing" : "mismatch"} ${rel}`);
|
|
process.exit(3);
|
|
} else {
|
|
let out;
|
|
const valueRe = /("packageManager"\s*:\s*")[^"]*(")/;
|
|
if (valueRe.test(raw)) {
|
|
out = raw.replace(valueRe, `$1${field}$2`);
|
|
} else {
|
|
const anchor = raw.match(/^(\s*)"(name|version)"\s*:\s*"[^"]*",\s*$/m);
|
|
if (!anchor) {
|
|
console.error(`error: cannot find insertion point in ${rel}`);
|
|
process.exit(1);
|
|
}
|
|
out = raw.replace(
|
|
anchor[0],
|
|
`${anchor[0]}\n${anchor[1]}"packageManager": "${field}",`
|
|
);
|
|
}
|
|
JSON.parse(out); // refuse to write invalid JSON
|
|
fs.writeFileSync(file, out);
|
|
console.log(`updated ${rel}`);
|
|
}
|
|
' "$1" "$FIELD" "$CHECK"
|
|
}
|
|
|
|
failures=0
|
|
updated=0
|
|
unchanged=0
|
|
for file in "${files[@]}"; do
|
|
if [[ "$CHECK" -eq 1 ]]; then
|
|
if stamp_one "$file"; then
|
|
unchanged=$((unchanged + 1))
|
|
else
|
|
rc=$?
|
|
if [[ "$rc" -eq 3 ]]; then
|
|
failures=$((failures + 1))
|
|
else
|
|
exit "$rc"
|
|
fi
|
|
fi
|
|
else
|
|
if [[ "$(stamp_one "$file")" == updated* ]]; then
|
|
updated=$((updated + 1))
|
|
else
|
|
unchanged=$((unchanged + 1))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ "$CHECK" -eq 1 ]]; then
|
|
if [[ "$failures" -gt 0 ]]; then
|
|
echo "check failed: ${failures} file(s) differ from ${FIELD}" >&2
|
|
exit 1
|
|
fi
|
|
echo "all ${unchanged} package.json files match ${FIELD}"
|
|
exit 0
|
|
fi
|
|
|
|
echo "stamped ${FIELD}: ${updated} updated, ${unchanged} unchanged"
|
|
|
|
if [[ "$INSTALL" -eq 1 ]]; then
|
|
# The 11 pnpm workspaces: repo root plus one per module. Members resolve
|
|
# through their parent workspace lockfile, so no per-member install.
|
|
for dir in . backend common docs exporter frontend library mcp media-processor plugins render-wasm; do
|
|
echo "--- pnpm install in ${dir}"
|
|
(cd "$dir" && pnpm install)
|
|
done
|
|
fi
|