pnpm refuses a git dependency inside another package by default (blockExoticSubdeps), so an app could not install v0.8.0. vue-seo-layer is now a peer dependency (^0.2.0) and a development dependency pinned to v0.2.0; apps list vue-seo-layer#v0.2.0 next to the layer.
222 lines
11 KiB
Bash
Executable File
222 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Usage: scripts/check-git-install.sh <tag>
|
|
# scripts/check-git-install.sh --check-links DIR...
|
|
# scripts/check-git-install.sh --self-test (the lockfile and link checks only)
|
|
#
|
|
# Proves the layer's distribution end to end: a scratch app installs
|
|
# vue-journal-layer from git+https://git.golem15.com/golem15/vue-journal-layer.git#<tag>
|
|
# anonymously, next to vue-seo-layer (a peer dependency, listed by the app at
|
|
# the tag's devDependencies pin) with pnpm's default settings, the installed package.json has no local dependency, the
|
|
# lockfile pins the tag's commit, `nuxt build` of the layer's playground
|
|
# config succeeds, and the built server passes testing/expect-acme.txt
|
|
# against the fixture API.
|
|
#
|
|
# --check-links is the release guard to run before tagging any content
|
|
# layer (README, Releasing): it reads DIR/package.json for each DIR and
|
|
# prints `check-git-install: <dir>: depends on <name> through <spec>` for
|
|
# every dependencies, devDependencies, peerDependencies or
|
|
# optionalDependencies entry whose spec starts with link:, file:,
|
|
# workspace: or portal: (a path that resolves outside the package once it
|
|
# is installed from git), exiting 1 when it finds any or cannot read a
|
|
# package.json.
|
|
# Nothing outside a temporary directory is touched.
|
|
set -euo pipefail
|
|
|
|
REPO_URL="https://git.golem15.com/golem15/vue-journal-layer.git"
|
|
|
|
die() { echo "check-git-install: $*" >&2; exit 1; }
|
|
|
|
# lock_commits FILE prints the commits FILE pins for vue-journal-layer, one
|
|
# per line: the #<commit> of its git+https entries and the commit of a
|
|
# resolution whose repo is the layer's. Other git dependencies are ignored.
|
|
lock_commits() {
|
|
{
|
|
grep -oE 'vue-journal-layer\.git#[0-9a-f]{40}' "$1" | cut -d'#' -f2
|
|
grep -oE 'commit: [0-9a-f]{40}, repo: [^,}]*/vue-journal-layer\.git' "$1" | grep -oE '[0-9a-f]{40}'
|
|
} | sort -u
|
|
}
|
|
|
|
# local_links LABEL DIR... prints one line per local dependency of each
|
|
# DIR/package.json (prefixed with LABEL, else the dir) and fails when there
|
|
# is any, or when a package.json cannot be read.
|
|
local_links() {
|
|
local label="$1"
|
|
shift
|
|
node -e '
|
|
const fs = require("node:fs")
|
|
const path = require("node:path")
|
|
const [label, ...dirs] = process.argv.slice(1)
|
|
const LOCAL = /^(link|file|workspace|portal):/
|
|
let bad = false
|
|
for (const dir of dirs) {
|
|
const who = label || dir
|
|
let pkg
|
|
try {
|
|
pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"))
|
|
} catch (err) {
|
|
console.error(`check-git-install: ${who}: cannot read package.json (${err.message})`)
|
|
bad = true
|
|
continue
|
|
}
|
|
for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
const deps = pkg[section] && typeof pkg[section] === "object" ? pkg[section] : {}
|
|
for (const [name, spec] of Object.entries(deps)) {
|
|
if (typeof spec === "string" && LOCAL.test(spec)) {
|
|
console.error(`check-git-install: ${who}: depends on ${name} through ${spec} (${section})`)
|
|
bad = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
process.exit(bad ? 1 : 0)
|
|
' "$label" "$@"
|
|
}
|
|
|
|
if [[ "${1:-}" == "--check-links" ]]; then
|
|
shift
|
|
[[ $# -gt 0 ]] || die "usage: $0 --check-links DIR..."
|
|
command -v node >/dev/null 2>&1 || die "node is not installed"
|
|
local_links "" "$@" || exit 1
|
|
echo "check-git-install: no local dependencies in $*"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${1:-}" == "--self-test" ]]; then
|
|
lock="$(mktemp)"
|
|
pkgs="$(mktemp -d)"
|
|
trap 'rm -f "$lock"; rm -rf "$pkgs"' EXIT
|
|
j=1111111111111111111111111111111111111111
|
|
s=2222222222222222222222222222222222222222
|
|
cat >"$lock" <<LOCK
|
|
vue-journal-layer:
|
|
specifier: git+https://git.golem15.com/golem15/vue-journal-layer.git#v1.0.0
|
|
version: git+https://git.golem15.com/golem15/vue-journal-layer.git#$j(abcdef0123456789abcdef0123456789)
|
|
vue-journal-layer@git+https://git.golem15.com/golem15/vue-journal-layer.git#$j:
|
|
resolution: {commit: $j, repo: https://git.golem15.com/golem15/vue-journal-layer.git, type: git}
|
|
vue-seo-layer@git+https://git.golem15.com/golem15/vue-seo-layer.git#$s:
|
|
resolution: {commit: $s, repo: https://git.golem15.com/golem15/vue-seo-layer.git, type: git}
|
|
LOCK
|
|
[[ "$(lock_commits "$lock")" == "$j" ]] || die "self-test: lock_commits gave $(lock_commits "$lock" | tr '\n' ' '), want only $j"
|
|
# A local link: dependency is reported and fails; a git+https tag pin passes.
|
|
mkdir -p "$pkgs/linked" "$pkgs/pinned" "$pkgs/dev"
|
|
cat >"$pkgs/linked/package.json" <<'JSON'
|
|
{ "name": "vue-menu-layer", "dependencies": { "vue-seo-layer": "link:../vue-seo-layer", "nuxt": "4.4.8" } }
|
|
JSON
|
|
cat >"$pkgs/pinned/package.json" <<'JSON'
|
|
{ "name": "vue-menu-layer", "dependencies": { "vue-seo-layer": "git+https://git.golem15.com/golem15/vue-seo-layer.git#v0.2.0", "nuxt": "4.4.8" } }
|
|
JSON
|
|
cat >"$pkgs/dev/package.json" <<'JSON'
|
|
{ "name": "x", "devDependencies": { "a": "file:../a" }, "optionalDependencies": { "b": "workspace:*" }, "peerDependencies": { "c": "portal:../c" } }
|
|
JSON
|
|
if out="$(local_links "" "$pkgs/linked" 2>&1)"; then die "self-test: a link: dependency passed"; fi
|
|
[[ "$out" == "check-git-install: $pkgs/linked: depends on vue-seo-layer through link:../vue-seo-layer (dependencies)" ]] \
|
|
|| die "self-test: the link: report was: $out"
|
|
out="$(local_links "" "$pkgs/pinned" 2>&1)" || die "self-test: a git+https pin failed: $out"
|
|
[[ -z "$out" ]] || die "self-test: a git+https pin printed: $out"
|
|
if out="$(local_links "" "$pkgs/dev" 2>&1)"; then die "self-test: file:, workspace: and portal: dependencies passed"; fi
|
|
[[ "$(grep -c 'depends on' <<<"$out")" -eq 3 ]] || die "self-test: want three reports for file:, workspace: and portal:, got: $out"
|
|
if out="$(local_links "the tag's package.json" "$pkgs/missing" 2>&1)"; then die "self-test: a missing package.json passed"; fi
|
|
if out="$(local_links "" "$pkgs/pinned" "$pkgs/linked" 2>&1)"; then die "self-test: one linked package among several passed"; fi
|
|
echo "check-git-install: self-test ok"
|
|
exit 0
|
|
fi
|
|
|
|
TAG="${1:-}"
|
|
[[ -n "$TAG" ]] || die "usage: $0 <tag>"
|
|
|
|
for tool in git pnpm node; do
|
|
command -v "$tool" >/dev/null 2>&1 || die "$tool is not installed"
|
|
done
|
|
|
|
WORK="$(mktemp -d "${TMPDIR:-/tmp}/journal-git-install.XXXXXX")"
|
|
# Nuxt applies its auto-import transform to any path with a
|
|
# /vue-journal-layer/ segment (how it finds a layer inside node_modules), so a
|
|
# scratch app below such a directory would transform every dependency.
|
|
case "$WORK/" in
|
|
*/vue-journal-layer/*) rm -rf "$WORK"; die "TMPDIR must not be inside a directory named vue-journal-layer" ;;
|
|
esac
|
|
cleanup() { rm -rf "$WORK"; }
|
|
trap cleanup EXIT
|
|
|
|
# The commit the tag points at (peeled when annotated).
|
|
REMOTE_REFS="$(GIT_TERMINAL_PROMPT=0 git ls-remote "$REPO_URL" "refs/tags/$TAG" "refs/tags/$TAG^{}")" \
|
|
|| die "git ls-remote $REPO_URL failed"
|
|
TAG_SHA="$(awk -v ref="refs/tags/$TAG^{}" '$2 == ref { print $1 }' <<<"$REMOTE_REFS")"
|
|
[[ -n "$TAG_SHA" ]] || TAG_SHA="$(awk -v ref="refs/tags/$TAG" '$2 == ref { print $1 }' <<<"$REMOTE_REFS")"
|
|
[[ -n "$TAG_SHA" ]] || die "tag $TAG not found on $REPO_URL"
|
|
|
|
# vue-seo-layer is a peer dependency of the layer, so an app lists it next to
|
|
# the layer, as the README's Installation says. The scratch app takes the
|
|
# spec the tag pins for its own development (devDependencies), or
|
|
# SEO_LAYER_SPEC when set. A tag that still lists vue-seo-layer under
|
|
# dependencies (v0.8.0) gets none: pnpm then refuses it as an exotic
|
|
# subdependency, which is what this check is for.
|
|
SEO_SPEC="${SEO_LAYER_SPEC:-}"
|
|
if [[ -z "$SEO_SPEC" ]]; then
|
|
SRC="$(mktemp -d "${TMPDIR:-/tmp}/journal-git-install-src.XXXXXX")"
|
|
GIT_TERMINAL_PROMPT=0 git clone --quiet --depth 1 --branch "$TAG" "$REPO_URL" "$SRC/layer" 2>/dev/null \
|
|
|| { rm -rf "$SRC"; die "git clone of $TAG failed"; }
|
|
SEO_SPEC="$(node -e '
|
|
const pkg = JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"))
|
|
const peer = (pkg.peerDependencies || {})["vue-seo-layer"]
|
|
const dev = (pkg.devDependencies || {})["vue-seo-layer"]
|
|
process.stdout.write(peer && dev ? dev : "")
|
|
' "$SRC/layer/package.json")" || { rm -rf "$SRC"; die "cannot read the tag's package.json"; }
|
|
rm -rf "$SRC"
|
|
fi
|
|
|
|
cd "$WORK"
|
|
|
|
node -e '
|
|
const [repo, tag, seo] = process.argv.slice(1)
|
|
const deps = { nuxt: "4.4.8", vue: "3.5.35" }
|
|
if (seo) deps["vue-seo-layer"] = seo
|
|
deps["vue-journal-layer"] = `git+${repo}#${tag}`
|
|
const pkg = { name: "acme-blog", private: true, type: "module", dependencies: deps }
|
|
require("node:fs").writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n")
|
|
' "$REPO_URL" "$TAG" "$SEO_SPEC"
|
|
|
|
cat >pnpm-workspace.yaml <<'YAML'
|
|
allowBuilds:
|
|
'@parcel/watcher': true
|
|
esbuild: true
|
|
vue-demi: true
|
|
YAML
|
|
|
|
GIT_TERMINAL_PROMPT=0 pnpm install || die "pnpm install failed (a link: or file: dependency in the tag's package.json fails here too; run --check-links before tagging)"
|
|
|
|
# The tag must not carry a local dependency (link:, file:, workspace:,
|
|
# portal:): it would resolve next to the pnpm store entry, not to a package.
|
|
local_links "the tag's package.json" node_modules/vue-journal-layer \
|
|
|| die "the tag's package.json still has a local dependency (see above); pin it to a git+https tag before tagging"
|
|
|
|
# The lockfile must pin the tag's commit, not a branch. Only the
|
|
# vue-journal-layer entries count: the layer's own git dependencies
|
|
# (vue-seo-layer) are pinned in the same file.
|
|
LOCK_SHA="$(lock_commits pnpm-lock.yaml)"
|
|
[[ -n "$LOCK_SHA" ]] || die "pnpm-lock.yaml records no commit for vue-journal-layer"
|
|
[[ "$(wc -l <<<"$LOCK_SHA")" -eq 1 ]] || die "pnpm-lock.yaml records more than one vue-journal-layer commit: $LOCK_SHA"
|
|
[[ "$LOCK_SHA" == "$TAG_SHA" ]] || die "pnpm-lock.yaml pins $LOCK_SHA, tag $TAG is $TAG_SHA"
|
|
|
|
# The scratch app is the layer's playground (its journal options, such as
|
|
# the search route and one post per category page, and its app.vue with the
|
|
# search box and the layout trail), which testing/expect-acme.txt is
|
|
# written against, extending the installed layer instead of '..'.
|
|
PLAYGROUND=node_modules/vue-journal-layer/.playground
|
|
[[ -f "$PLAYGROUND/nuxt.config.ts" && -f "$PLAYGROUND/app/app.vue" ]] \
|
|
|| die "the installed layer has no .playground/nuxt.config.ts and .playground/app/app.vue"
|
|
sed "s#extends: \['\.\.'\]#extends: ['vue-journal-layer']#" "$PLAYGROUND/nuxt.config.ts" >nuxt.config.ts
|
|
grep -q "extends: \['vue-journal-layer'\]" nuxt.config.ts || die "the playground's nuxt.config.ts does not extend '..'"
|
|
mkdir -p app
|
|
cp "$PLAYGROUND/app/app.vue" app/app.vue
|
|
|
|
pnpm exec nuxt build || die "nuxt build failed"
|
|
|
|
node node_modules/vue-journal-layer/testing/smoke-ssr.mjs \
|
|
--output .output \
|
|
--fixtures node_modules/vue-journal-layer/testing/fixtures/acme \
|
|
--expect node_modules/vue-journal-layer/testing/expect-acme.txt \
|
|
|| die "SSR smoke failed"
|
|
|
|
echo "check-git-install: ok $TAG $TAG_SHA"
|