diff --git a/publish.sh b/publish.sh new file mode 100755 index 00000000..4d64857e --- /dev/null +++ b/publish.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# publish.sh — publish the @videojs/* packages as standalone git branches. +# +# bun (and npm) cannot install a single package out of a monorepo over git: +# git-subdirectory dependencies are unimplemented (oven-sh/bun#15506). So a +# plain `github:zoriya/v10#` reference would resolve to the repo-root +# `video.js` package, which cannot satisfy `@videojs/react` + `@videojs/core`. +# +# This script works around that "gitpkg" style: it builds the packages and +# pushes each one to its OWN branch whose root is that package's built output +# (dist/ + package.json). A consumer lists ALL SIX packages as top-level deps, +# each a plain github: ref to its branch: +# +# "@videojs/react": "github:zoriya/v10#build26-react" +# "@videojs/core": "github:zoriya/v10#build26-core" +# "@videojs/media": "github:zoriya/v10#build26-media" +# ...store, spf, utils +# +# Each package's internal `@videojs/*` deps are rewritten from `dependencies` +# to `peerDependencies` (range "*"). This is the crucial part: bun cannot +# resolve an @videojs package as a NESTED git/tarball dep — a nested git dep +# shared by 2+ parents fails outright, and a nested tarball reachable via 3+ +# paths is mis-deduped ("failed to resolve"). @videojs/utils has five parents, +# so any nested form is unusable. As peers, bun does no nested resolution: the +# six top-level installs (each a single github: fetch) satisfy every package's +# peers. External deps (hls.js, signal-polyfill, ...) stay in `dependencies`. +# +# The per-package trees are assembled in throwaway git repos under $TMPDIR and +# pushed straight to the remote; this repo's own git/jj working state is never +# touched (aside from build artifacts written under packages/*/dist). +# +# Usage: ./publish.sh +# +# Environment overrides: +# VJS_REMOTE git remote to push branches to +# (default: git@github.com:zoriya/v10.git) +# VJS_REF_PREFIX prefix for rewritten sibling dep specs; the dep's published +# commit SHA is appended +# (default: https://codeload.github.com/zoriya/v10/tar.gz/) +# VJS_TAG branch-name prefix / build tag (default: build26) +# BIOME_BINARY path to a biome that runs on this host (auto-detected from +# PATH when unset; required on NixOS, where the bundled +# @biomejs native binary cannot run). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$ROOT" + +REMOTE="${VJS_REMOTE:-git@github.com:zoriya/v10.git}" +OWNER="${VJS_OWNER:-zoriya/v10}" +REF_PREFIX="${VJS_REF_PREFIX:-https://codeload.github.com/zoriya/v10/tar.gz/}" +TAG="${VJS_TAG:-build26}" + +# Topological order (deps before dependents); also the exact runtime closure of +# @videojs/react, which is all omni needs. +PKGS=(utils store spf media core react) + +# --- toolchain -------------------------------------------------------------- +# turbo needs a `pnpm` binary on PATH; this repo uses corepack. CI=true stops +# pnpm from trying to purge node_modules when there is no TTY. +export CI=true +SHIMDIR="$(mktemp -d)" +trap 'rm -rf "$SHIMDIR"' EXIT +if ! command -v pnpm >/dev/null 2>&1; then + printf '#!/usr/bin/env bash\nexec corepack pnpm "$@"\n' > "$SHIMDIR/pnpm" + chmod +x "$SHIMDIR/pnpm" + export PATH="$SHIMDIR:$PATH" +fi +if [ -z "${BIOME_BINARY:-}" ] && command -v biome >/dev/null 2>&1; then + export BIOME_BINARY="$(command -v biome)" +fi + +# --- build ------------------------------------------------------------------ +# --env-mode=loose lets BIOME_BINARY through turbo's (strict-by-default) env +# filter so the i18n codegen's `biome check` step can run. +echo ">> building @videojs/react and its dependency closure" +./node_modules/.bin/turbo run build --filter=@videojs/react... --env-mode=loose + +HEAD_SHA="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)" + +# --- publish each package to its own branch -------------------------------- +# Cross-package deps are pinned to each sibling's immutable commit SHA via its +# codeload tarball URL (see header). Packages are published in topological +# order, so each dep's SHA is known before its dependents are assembled. +# SHAS maps "@videojs/" -> published commit SHA. +declare -A SHAS + +for pkg in "${PKGS[@]}"; do + src="$ROOT/packages/$pkg" + if [ ! -d "$src/dist" ]; then + echo "!! packages/$pkg has no dist/ — build failed?" >&2 + exit 1 + fi + + work="$(mktemp -d)" + cp -R "$src/dist" "$work/dist" + cp "$src/package.json" "$work/package.json" + if [ -f "$src/README.md" ]; then cp "$src/README.md" "$work/README.md"; fi + if [ -d "$src/docs" ]; then cp -R "$src/docs" "$work/docs"; fi + + # Rewrite internal @videojs/* deps -> pinned SHA refs of already-published + # siblings; drop devDeps and scripts so bun installs a pure prebuilt package + # (no prepare/postinstall). SHAS_JSON carries the pkg->sha map. + SHAS_JSON="$( + { printf '{'; first=1 + for k in "${!SHAS[@]}"; do + [ $first -eq 1 ] || printf ',' + printf '"%s":"%s"' "$k" "${SHAS[$k]}"; first=0 + done + printf '}'; } + )" + REF_PREFIX="$REF_PREFIX" SHAS_JSON="$SHAS_JSON" node -e ' + const fs = require("fs"); + const file = process.argv[1]; + const pkg = JSON.parse(fs.readFileSync(file, "utf8")); + const prefix = process.env.REF_PREFIX; + const shas = JSON.parse(process.env.SHAS_JSON); + const internal = ["utils","store","spf","media","core","react"] + .map((s) => "@videojs/" + s); + // Normalize any `workspace:*` spec (e.g. the optional @videojs/element + // peer) to "*" since npm publish would resolve it and it is not published. + for (const field of ["dependencies", "peerDependencies", "optionalDependencies"]) { + const deps = pkg[field]; + if (!deps) continue; + for (const name of Object.keys(deps)) { + if (String(deps[name]).startsWith("workspace:")) deps[name] = "*"; + } + } + // Move internal @videojs/* deps from `dependencies` to `peerDependencies`. + // bun cannot resolve these as NESTED git/tarball deps (nested-git bug + + // 3-path diamond dedup bug). As peers with a plain "*" range, bun does no + // nested resolution: the consumer installs all six @videojs packages at + // top level (each a single github: fetch, no nesting) and those satisfy + // the peers of every package. External deps (hls.js, signal-polyfill, etc) + // stay in `dependencies` and install normally. + if (pkg.dependencies) { + pkg.peerDependencies = pkg.peerDependencies || {}; + for (const name of Object.keys(pkg.dependencies)) { + if (internal.includes(name)) { + delete pkg.dependencies[name]; + pkg.peerDependencies[name] = "*"; + } + } + } + delete pkg.devDependencies; + delete pkg.scripts; + fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n"); + ' "$work/package.json" + + git -C "$work" init -q + git -C "$work" add -A -f + git -C "$work" -c user.email=publish@local -c user.name=publish \ + commit -qm "build($pkg): $TAG from $HEAD_SHA" + sha="$(git -C "$work" rev-parse HEAD)" + SHAS["@videojs/$pkg"]="$sha" + git -C "$work" branch -M "$TAG-$pkg" + echo ">> pushing $TAG-$pkg ($sha) -> $REMOTE" + git -C "$work" push -f "$REMOTE" "$TAG-$pkg" + rm -rf "$work" || true +done + +echo "" +echo ">> done. Add ALL of these as top-level deps in the consumer:" +for pkg in "${PKGS[@]}"; do + echo " \"@videojs/$pkg\": \"github:${OWNER}#${TAG}-$pkg\"," +done +echo " (immutable SHA pins are also available, e.g. github:zoriya/v10#${SHAS[@videojs/react]})"