From 4d811aa841f84a09e3e67ed590e9d161bd4cf035 Mon Sep 17 00:00:00 2001 From: Rito Rhymes Date: Sun, 3 May 2026 21:59:38 -0400 Subject: [PATCH] Roll Out Local Build Workspace for ERCs Add ERCs repo-local setup wrappers and contributor documentation for the V2 multi-repo local build workflow. The setup scripts treat this checkout as /ERCs, reuse or install a released build-eips binary with checksum verification, install or reuse Zola 0.22.1 with pinned archive hashes, run build-eips init --template --platform-dev, and surface doctor failures as warnings after bootstrap. Update CI to use editorial check --against-upstream --format github, update publish to use plain build now that eipw linting is no longer part of build/check/serve, add contributor setup guidance, expand the README local workflow docs, and move homepage badges into structured front matter. This builds on the tracked Build.toml manifest added separately for ERCs repo identity, related proposal repos, canonical base URLs, and theme pin. --- .github/CONTRIBUTING.md | 6 + .github/actions/build-eips/action.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 5 +- Build.toml | 13 + README.md | 215 ++++++++-- content/_index.md | 42 +- scripts/dev-setup | 569 ++++++++++++++++++++++++++ scripts/dev-setup.ps1 | 517 +++++++++++++++++++++++ 9 files changed, 1313 insertions(+), 58 deletions(-) create mode 100644 Build.toml create mode 100755 scripts/dev-setup create mode 100644 scripts/dev-setup.ps1 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 132390597..2d1d1514e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,3 +1,9 @@ Please review [EIP-1](https://eips.ethereum.org/1/) for proposal guidelines. +For workspace bootstrap and direct `build-eips` commands, run +`./scripts/dev-setup` from the repo root. See + for setup +details; the generated `WORKSPACE.md` contains the full command and +configuration reference. + diff --git a/.github/actions/build-eips/action.yml b/.github/actions/build-eips/action.yml index 048fc9d87..ca9b14321 100644 --- a/.github/actions/build-eips/action.yml +++ b/.github/actions/build-eips/action.yml @@ -63,4 +63,4 @@ runs: echo "stdout<> "$GITHUB_OUTPUT" trap write_eof EXIT export PATH="${ZOLA_PATH}:${PATH}" - "$BUILD_EIPS" --staging $BUILD_EIPS_ARGS | awk '{ print; if ($0 == "EOF") exit 1 }' | tee -a "$GITHUB_OUTPUT" + "$BUILD_EIPS" $BUILD_EIPS_ARGS | awk '{ print; if ($0 == "EOF") exit 1 }' | tee -a "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f53ba2ca..ac9a5e28a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: - name: EIPs Build (Check) uses: ./.github/actions/build-eips with: - args: check --format github + args: editorial check --against-upstream --format github markdownlint: if: github.event.pull_request.base.repo.owner.login == 'ethereum' || github.event.pull_request.base.repo.owner.login == 'eips-wg' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1fde71c40..8bf406b88 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,10 +20,7 @@ jobs: - name: EIPs Build (Build) uses: ./.github/actions/build-eips with: - # Disable eipw lints because there will never be any changed files - # between this checkout and `master` because, well, we just checked - # out `master`. - args: build --no-lint + args: build - name: Upload Artifact id: artifact diff --git a/Build.toml b/Build.toml new file mode 100644 index 000000000..d20153e43 --- /dev/null +++ b/Build.toml @@ -0,0 +1,13 @@ +name = "ERCs" + +[locations.ERCs] +repository = "https://github.com/eips-wg/ERCs.git" +base-url = "https://eips-wg.github.io/ERCs/" + +[locations.EIPs] +repository = "https://github.com/eips-wg/EIPs.git" +base-url = "https://eips-wg.github.io/EIPs/" + +[theme] +repository = "https://github.com/eips-wg/theme.git" +commit = "0ddac35da36d311a8401c6cfb79c9991f78b647d" diff --git a/README.md b/README.md index 1ca9d49eb..fd257b5e3 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,213 @@ > [!IMPORTANT] -> Please direct application-level proposals (ERCs) to [`ethereum/ERCs`] and all -> other proposals (EIPs) to [`ethereum/EIPs`]. +> Please direct application-level proposals (ERCs) to [`ethereum/ERCs`] and all other proposals (EIPs) to [`ethereum/EIPs`]. # Ethereum Request for Comments (ERCs) -The ERC project standardizes and provides high-quality documentation for -the Ethereum application layer. This repository tracks past and ongoing -improvements to the Ethereum application ecosystem in the form of Ethereum -Request for Comments (ERCs). [EIP-1] governs how ERCs are published. +The ERC project standardizes and provides high-quality documentation for the Ethereum application layer. This repository tracks past and ongoing improvements to the Ethereum application ecosystem in the form of Ethereum Request for Comments (ERCs). [EIP-1] governs how ERCs are published. ## Contributing > [!WARNING] -> Before you write an ERC, ideas MUST be thoroughly discussed on -> [Ethereum Magicians][ethmag] or [Ethereum Research][ethres]. Once consensus -> is reached, thoroughly read and review [EIP-1], which describes the EIP -> process. +> Before you write an ERC, ideas MUST be thoroughly discussed on [Ethereum Magicians][ethmag] or [Ethereum Research][ethres]. Once consensus is reached, thoroughly read and review [EIP-1], which describes the EIP process. -To create a new proposal, **copy** the [Proposal Template][template] into the -[`contents`] directory and rename it to `99999.md` (or `99999/index.md` if you -have additional assets). The template has more detailed instructions. +To create a new proposal, **copy** the [Proposal Template][template] into the [`contents`] directory and rename it to `99999.md` (or `99999/index.md` if you have additional assets). The template has more detailed instructions. -This repository is for documenting standards and not for help implementing -them. These types of inquiries should be directed to the -[Ethereum Stack Exchange][ethex]. For specific questions and concerns regarding -EIPs, it's best to comment on the relevant discussion thread of the EIP denoted -by the `discussions-to` tag in the EIP's preamble. +This repository is for documenting standards and not for help implementing them. These types of inquiries should be directed to the [Ethereum Stack Exchange][ethex]. For specific questions and concerns regarding EIPs, it's best to comment on the relevant discussion thread of the EIP denoted by the `discussions-to` tag in the EIP's preamble. If you would like to become an EIP Editor, please read [EIP-5069]. ### Validation and Auto-merging -All pull requests in this repository must pass checks before they can be -automatically merged: +All pull requests in this repository must pass checks before they can be automatically merged: - [eip-review-bot] determines when PRs can be automatically merged [^1] - EIP-1 rules are enforced using [`eipw`] [^2] - Markdown best practices are checked using [markdownlint] [^2] +- `build-eips` runs targeted proposal validation and site checks for changed proposal files [^2] -### Building Locally +Before opening or updating a pull request, run the same CI-style command locally as described in [Editorial Validation](#editorial-validation): `build-eips editorial check --against-upstream --format github`. -It is possible to run the above checks and preview how proposals will render -using [`build-eips`]. See its documentation for more details. +## Local Workflows + +The ERCs repo uses the shared `build-eips` multi-repo workspace for local site builds, live previews, and editorial validation. The setup script bootstraps the surrounding workspace so, with just a few commands, you can: + +* run targeted `eipw` editorial checks for proposals before opening or updating pull requests +* build and serve the ERCs site with the local theme repo and sibling proposal repos like EIPs +* include tracked local edits without committing them first +* render only selected proposals to save time when a full site build is unnecessary +* diagnose missing workspace pieces with `build-eips doctor` + +Run the commands below from this ERCs repo. From the workspace root, use `-C ERCs` before the command. + +### Minimum Requirements + +Local workspace commands require these tools on `PATH`: + +* Git +* `build-eips` +* Zola 0.22.1 + +Git must be installed separately. The setup script locates or installs `build-eips` and Zola, adds locally installed tool directories to `PATH` for the current shell session, and prints guidance for making those `PATH` changes permanent. + +### Bootstrap The Workspace + +Run the setup script once from this repo. + +Linux and macOS: + +```sh +./scripts/dev-setup +``` + +Windows PowerShell: + +```powershell +.\scripts\dev-setup.ps1 +``` + +The setup script initializes the workspace one directory above this repo, runs `build-eips doctor`, and prints the next local commands. + +After setup, the generated workspace guide is available at `../WORKSPACE.md`. Use that file for the full command reference and workspace details. + +After setup, the workspace has this layout: + +```text +EIPs-project/ +├── .build-eips.toml +├── WORKSPACE.md +├── .local-build/ +├── EIPs/ +├── ERCs/ +└── theme/ +``` + +### Build And Serve Locally + +Build the full static site, then preview that built output: + +```bash +build-eips build +build-eips preview +``` + +`preview` serves the last output written by `build`. Run `build` again before `preview` when you want to inspect fresh output. + +Use `serve` when you want a live development server that livereloads changes instead of a reusable build output: + +```bash +build-eips serve +``` + +`serve` runs a fresh temporary site build each time it is invoked (without using `build`), starts a local development server, and watches tracked local edits. Its output cannot be reused by `preview`. + +Use `check` to quickly validate whether the site will build cleanly without producing the full built site: + +```bash +build-eips check +``` + +By default, `check`, `build`, and `serve` use the local workspace in dirty mode, which includes tracked working-tree edits from this repo. `preview` serves the last output written by `build`. Use `--clean` when you want to ignore tracked local proposal edits for one command: + +```bash +build-eips check --clean +build-eips build --clean +build-eips serve --clean +``` + +For parity workflows, remote-sibling modes, source overrides, side-by-side build roots, and detailed dirty-mode behavior, see `../WORKSPACE.md`. + +### Local Settings + +Local build settings live in `../.build-eips.toml`, which the setup script generates. Use that workspace file to change the local server address or local site URL: + +```toml +[server] +host = "127.0.0.1" +port = 1111 + +[site] +base_url = "http://127.0.0.1:1111" +``` + +`serve` and `preview` use `[server]` for the local bind address. `build` and `serve` use `[site].base_url` when generating links. + +CLI flags such as `--host`, `--port`, and `--base-url` override the workspace config for one run: + +```bash +build-eips serve --host 0.0.0.0 --port 3000 --base-url http://127.0.0.1:3000 +``` + +### Render Specific Proposals Only + +Full local `build` and `serve` runs can take time because they process every proposal file. When you want to quickly test a single proposal or a specific batch, add a list of desired proposal numbers to the workspace `.build-eips.toml`: + +```toml +[render] +only = [555, 678] +``` + +Add one or more proposal numbers in `[render].only`, separated by commas. It's empty by default, but whenever it is populated, the regular `build` and `serve` commands render only those proposal pages. Links and references to excluded proposals are rewritten to the canonical public site. + +Use CLI `--only` when you want a one-run target list; it also overrides any proposals in `[render].only` for that run: + +```bash +build-eips serve --only 555 +build-eips build --only 555 +build-eips build --only 555 678 +``` + +Multiple proposal numbers in the CLI are space-separated; no commas. + +### Editorial Validation + +Use editorial commands to validate proposal files before opening or updating a pull request. + +- `editorial lint` runs targeted `eipw` proposal-rule checks. +- `editorial check` runs `editorial lint`, then checks that the selected proposal changes will not prevent the full site from building cleanly. + +Check one or more specific proposals by number: + +```bash +build-eips editorial check 1 +build-eips editorial check 1 123 +``` + +For the closest match to PR CI, use `editorial check` against the proposal files changed versus upstream: + +```bash +build-eips editorial check --against-upstream --format github +``` + +Both commands accept the same selector modes: + +* proposal numbers or repo-relative proposal paths for explicit targets +* `--working-tree` for tracked dirty proposal files +* `--against-upstream` for proposal files changed versus the upstream merge-base +* `--batch ` for a repeatable target list + +They also accept `eipw` options such as `--format github`. + +Use a batch file when you want to lint or check the same proposal set repeatedly. A batch file is a plain text file with one proposal number per line: + +```txt +1 +7949 +``` + +```bash +build-eips editorial lint --batch ../editor-batch.txt +build-eips editorial check --batch ../editor-batch.txt +``` + +### Full Workspace Reference + +For parity workflows, remote-sibling modes, source overrides, side-by-side build roots, and detailed dirty-mode behavior, see `../WORKSPACE.md`. ## Preferred Citation Format -The canonical URL for an EIP that has achieved draft status at any point is at -. For example, the canonical URL for EIP-1 is -. +The canonical URL for an EIP that has achieved draft status at any point is at . For example, the canonical URL for EIP-1 is . -Consider any document not published at as a -working paper. Additionally, consider published EIPs with a status of "draft", -"review", or "last call" to be incomplete drafts, and note that their -specification is likely to be subject to change. +Consider any document not published at as a working paper. Additionally, consider published EIPs with a status of "draft", "review", or "last call" to be incomplete drafts, and note that their specification is likely to be subject to change. [^1]: [^2]: diff --git a/content/_index.md b/content/_index.md index 23de81bcf..b8823d37c 100644 --- a/content/_index.md +++ b/content/_index.md @@ -1,15 +1,29 @@ --- title: Home +extra: + homepage_badges: + - href: https://discord.gg/Nz6rtfJ8Cu + image: https://dcbadge.limes.pink/api/server/Nz6rtfJ8Cu?style=flat + alt: Badge for EIP Editor Discord channel + - href: https://discord.gg/EVTQ9crVgQ + image: https://dcbadge.limes.pink/api/server/EVTQ9crVgQ?style=flat + alt: Badge for Ethereum R&D Discord channel + - href: https://discord.gg/mRzPXmmYEA + image: https://dcbadge.limes.pink/api/server/mRzPXmmYEA?style=flat + alt: Badge for Ethereum Wallets Discord channel + - href: https://discord.gg/9FxN6CfaQR + image: https://dcbadge.limes.pink/api/server/9FxN6CfaQR?style=flat + alt: Badge for ERCRef Discord channel + - href: ./atom.xml + image: https://img.shields.io/badge/rss-Everything-red.svg + alt: RSS feed for everything + - href: ./status/last-call/atom.xml + image: https://img.shields.io/badge/rss-Last%20Calls-red.svg + alt: RSS feed for last calls --- # EIPs & ERCs -[![Badge for EIP Editor Discord channel][badge-discord-ech]][discord-ech] -[![Badge for Ethereum R&D Discord channel][badge-discord-rd]][discord-rd] -[![Badge for Ethereum Wallets Discord channel][badge-discord-wallet]][discord-wallet] -[![RSS Feed for Everything][badge-rss-all]][rss-all] -[![RSS Feed for Last Call][badge-rss-last-call]][rss-last-call] - Ethereum Improvement Proposals (EIPs) describe standards for the Ethereum @@ -107,22 +121,6 @@ Informational EIPs do not necessarily represent Ethereum community consensus or a recommendation, so users and implementers are free to ignore Informational EIPs or follow their advice. - -[discord-ech]: https://discord.gg/Nz6rtfJ8Cu -[badge-discord-ech]: https://dcbadge.limes.pink/api/server/Nz6rtfJ8Cu?style=flat - -[discord-rd]: https://discord.gg/EVTQ9crVgQ -[badge-discord-rd]: https://dcbadge.limes.pink/api/server/EVTQ9crVgQ?style=flat - -[discord-wallet]: https://discord.gg/mRzPXmmYEA -[badge-discord-wallet]: https://dcbadge.limes.pink/api/server/mRzPXmmYEA?style=flat - -[rss-all]: ./atom.xml -[badge-rss-all]: https://img.shields.io/badge/rss-Everything-red.svg - -[rss-last-call]: ./status/last-call/atom.xml -[badge-rss-last-call]: https://img.shields.io/badge/rss-Last%20Calls-red.svg - [ethpm]: https://github.com/ethereum/pm/ [template]: https://github.com/ethereum/EIPs/blob/master/docs/template.md?plain=1 diff --git a/scripts/dev-setup b/scripts/dev-setup new file mode 100755 index 000000000..37a73d3eb --- /dev/null +++ b/scripts/dev-setup @@ -0,0 +1,569 @@ +#!/bin/sh + +set -eu + +say() { + printf '%s\n' "$*" +} + +die() { + printf 'error: %s\n' "$*" >&2 + exit 1 +} + +shell_quote() { + printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")" +} + +normalize_dir_for_path_compare() { + dir=$1 + [ -n "$dir" ] || return 1 + + while [ "$dir" != "/" ] && [ "${dir%/}" != "$dir" ]; do + dir=${dir%/} + done + + case "$dir" in + /*) + absolute_dir=$dir + ;; + *) + absolute_dir=$(pwd -P)/$dir + ;; + esac + + if [ -d "$absolute_dir" ]; then + (CDPATH= cd -P -- "$absolute_dir" 2>/dev/null && pwd -P) + else + printf '%s\n' "$absolute_dir" + fi +} + +path_contains_dir() { + target=$(normalize_dir_for_path_compare "$1") || return 1 + old_ifs=$IFS + IFS=: + for path_entry in ${PATH:-}; do + [ -n "$path_entry" ] || continue + candidate=$(normalize_dir_for_path_compare "$path_entry") || continue + if [ "$candidate" = "$target" ]; then + IFS=$old_ifs + return 0 + fi + done + IFS=$old_ifs + return 1 +} + +move_dir_to_front_of_script_path() { + dir=$1 + target=$(normalize_dir_for_path_compare "$dir") || return 1 + new_path= + old_ifs=$IFS + IFS=: + for path_entry in ${PATH:-}; do + [ -n "$path_entry" ] || continue + candidate=$(normalize_dir_for_path_compare "$path_entry") || continue + if [ "$candidate" = "$target" ]; then + continue + fi + + if [ -n "$new_path" ]; then + new_path=$new_path:$path_entry + else + new_path=$path_entry + fi + done + IFS=$old_ifs + + if [ -n "$new_path" ]; then + updated_path=$dir:$new_path + else + updated_path=$dir + fi + + if [ "${PATH:-}" != "$updated_path" ]; then + PATH=$updated_path + add_path_note "$dir" + fi +} + +add_path_note() { + dir=$1 + target=$(normalize_dir_for_path_compare "$dir") || return 1 + old_ifs=$IFS + IFS=' +' + for path_note in $PATH_NOTES; do + candidate=$(normalize_dir_for_path_compare "$path_note") || continue + if [ "$candidate" = "$target" ]; then + IFS=$old_ifs + return 0 + fi + done + IFS=$old_ifs + + case " +$PATH_NOTES +" in + *" +$dir +"*) + return 0 + ;; + esac + + if [ -n "$PATH_NOTES" ]; then + PATH_NOTES=$PATH_NOTES' +'$dir + else + PATH_NOTES=$dir + fi +} + +pick_home_install_dir() { + [ -n "${HOME:-}" ] || return 1 + dir=$HOME/.local/bin + mkdir -p "$dir" 2>/dev/null || return 1 + [ -d "$dir" ] || return 1 + [ -w "$dir" ] || return 1 + printf '%s\n' "$dir" +} + +pick_writable_path_dir() { + old_ifs=$IFS + IFS=: + for dir in ${PATH:-}; do + [ -n "$dir" ] || continue + [ -d "$dir" ] || continue + [ -w "$dir" ] || continue + printf '%s\n' "$dir" + IFS=$old_ifs + return 0 + done + IFS=$old_ifs + return 1 +} + +pick_install_dir() { + if dir=$(pick_home_install_dir); then + printf '%s\n' "$dir" + return 0 + fi + + pick_writable_path_dir +} + +download() { + url=$1 + destination=$2 + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" -o "$destination" + return 0 + fi + + if command -v wget >/dev/null 2>&1; then + wget -qO "$destination" "$url" + return 0 + fi + + die "need curl or wget to download release binaries" +} + +file_sha256() { + path=$1 + + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$path" | awk '{ print $1 }' | tr 'A-F' 'a-f' + return 0 + fi + + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$path" | awk '{ print $1 }' | tr 'A-F' 'a-f' + return 0 + fi + + if command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 -r "$path" | awk '{ print $1 }' | tr 'A-F' 'a-f' + return 0 + fi + + die "need sha256sum, shasum, or openssl to verify release checksums" +} + +normalized_sidecar_text() { + sidecar_path=$1 + bom=$(printf '\357\273\277') + cr=$(printf '\r') + first_three=$(dd if="$sidecar_path" bs=3 count=1 2>/dev/null || true) + + if [ "$first_three" = "$bom" ]; then + dd if="$sidecar_path" bs=3 skip=1 2>/dev/null | tr -d "$cr" + else + tr -d "$cr" < "$sidecar_path" + fi +} + +verify_archive_checksum() { + archive_path=$1 + sidecar_path=$2 + archive_name=$3 + + [ -f "$sidecar_path" ] || die "missing checksum sidecar: $sidecar_path" + + checksum_lines=$(normalized_sidecar_text "$sidecar_path" | sed '/^[[:space:]]*$/d') + checksum_line_count=$(printf '%s\n' "$checksum_lines" | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ') + [ "$checksum_line_count" = "1" ] || die "checksum sidecar must contain exactly one checksum line" + + set -f + set -- $checksum_lines + set +f + [ "$#" -eq 2 ] || die "checksum sidecar must contain only a hash and archive filename" + + expected_hash=$(printf '%s' "$1" | tr 'A-F' 'a-f') + expected_name=$2 + + [ ${#expected_hash} -eq 64 ] || die "checksum sidecar hash must be 64 hex characters" + case "$expected_hash" in + *[!0123456789abcdef]*) + die "checksum sidecar hash must be hex" + ;; + esac + case "$expected_name" in + */*|*\\*) + die "checksum sidecar filename must be a basename" + ;; + esac + [ "$expected_name" = "$archive_name" ] || die "checksum sidecar filename '$expected_name' does not match '$archive_name'" + + actual_hash=$(file_sha256 "$archive_path") + [ "$actual_hash" = "$expected_hash" ] || die "checksum mismatch for $archive_name" +} + +verify_file_hash() { + archive_path=$1 + expected_hash=$2 + archive_name=$3 + + actual_hash=$(file_sha256 "$archive_path") + [ "$actual_hash" = "$expected_hash" ] || die "checksum mismatch for $archive_name" +} + +cleanup_tmpdir() { + if [ -n "${INSTALL_TMP_TO_CLEAN:-}" ]; then + rm -f "$INSTALL_TMP_TO_CLEAN" + INSTALL_TMP_TO_CLEAN= + fi + if [ -n "${TMPDIR_TO_CLEAN:-}" ]; then + rm -rf "$TMPDIR_TO_CLEAN" + TMPDIR_TO_CLEAN= + fi +} + +cleanup_for_signal() { + cleanup_tmpdir + trap - EXIT INT TERM HUP QUIT + exit 1 +} + +set_cleanup_traps() { + trap cleanup_tmpdir EXIT + trap cleanup_for_signal INT TERM HUP QUIT +} + +clear_cleanup_traps() { + trap - EXIT INT TERM HUP QUIT +} + +ensure_install_dir() { + [ -n "${INSTALL_DIR:-}" ] && return 0 + + INSTALL_DIR=$(pick_install_dir) || die "could not find a writable install directory" + move_dir_to_front_of_script_path "$INSTALL_DIR" +} + +install_build_eips() { + case "$(uname -s)" in + Linux) + archive_name=build-eips-ubuntu.tar.xz + ;; + Darwin) + archive_name=build-eips-macos.tar.xz + ;; + *) + die "unsupported operating system for automatic build-eips installation" + ;; + esac + + command -v tar >/dev/null 2>&1 || die "need tar to unpack the build-eips release archive" + ensure_install_dir + + tmpdir=$(mktemp -d) + TMPDIR_TO_CLEAN=$tmpdir + set_cleanup_traps + + archive_path=$tmpdir/$archive_name + sidecar_path=$tmpdir/$archive_name.sha256 + release_base_url=https://github.com/eips-wg/preprocessor/releases/latest/download + + say "Installing build-eips from $release_base_url/$archive_name" + download "$release_base_url/$archive_name" "$archive_path" + download "$release_base_url/$archive_name.sha256" "$sidecar_path" + verify_archive_checksum "$archive_path" "$sidecar_path" "$archive_name" + + tar -xf "$archive_path" -C "$tmpdir" + [ -f "$tmpdir/build-eips" ] || die "release archive did not contain build-eips" + + install_tmp=$(mktemp "$INSTALL_DIR/.build-eips.XXXXXX") || die "could not create temporary install file in $INSTALL_DIR" + INSTALL_TMP_TO_CLEAN=$install_tmp + cp "$tmpdir/build-eips" "$install_tmp" + chmod +x "$install_tmp" + mv "$install_tmp" "$INSTALL_DIR/build-eips" + INSTALL_TMP_TO_CLEAN= + + BUILD_EIPS=$INSTALL_DIR/build-eips + cleanup_tmpdir + clear_cleanup_traps +} + +unsupported_zola_platform() { + os=$1 + arch=$2 + die "Unsupported platform $os/$arch for automatic Zola install. Install Zola 0.22.1 manually from https://github.com/getzola/zola/releases and ensure it is on PATH." +} + +is_linux_musl() { + if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + return 0 + fi + + set -- /lib/ld-musl-*.so.1 + [ -e "$1" ] +} + +select_zola_release() { + os=$(uname -s) + arch=$(uname -m) + + case "$os" in + Linux) + if is_linux_musl; then + case "$arch" in + x86_64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-x86_64-unknown-linux-musl.tar.gz + ZOLA_ARCHIVE_HASH=227df99b664421240a8ba77747067c51259b08159125d5603763b3b173b9a881 + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac + else + case "$arch" in + x86_64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-x86_64-unknown-linux-gnu.tar.gz + ZOLA_ARCHIVE_HASH=0ca09aa40376aaa9ddfb512ff9ad963262ef95edb0d0f2d5ec6961b6f5cf22ef + ;; + aarch64|arm64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-aarch64-unknown-linux-gnu.tar.gz + ZOLA_ARCHIVE_HASH=8af437ec6352f33ccd24d7a1cfcb54a3db95d3ce376dc69525b4ef3fb6b8c1d1 + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac + fi + ;; + Darwin) + case "$arch" in + x86_64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-x86_64-apple-darwin.tar.gz + ZOLA_ARCHIVE_HASH=3898709e154ae0593933264a540c869348bdb10d7f1b03a42dfb78d63703b3b5 + ;; + arm64|aarch64) + ZOLA_ARCHIVE_NAME=zola-v0.22.1-aarch64-apple-darwin.tar.gz + ZOLA_ARCHIVE_HASH=46ac45a9e7628dba8593b124ee8794f4f9aa1c6b569918ecd4bbc5d0be190515 + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac + ;; + *) + unsupported_zola_platform "$os" "$arch" + ;; + esac +} + +parse_zola_version() { + output=$1 + + set -f + set -- $output + set +f + [ "$#" -ge 2 ] || return 1 + + ZOLA_FOUND_VERSION=$2 + version_core=$(printf '%s\n' "$ZOLA_FOUND_VERSION" | sed -n 's/^\([0-9][0-9]*\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\).*/\1 \2 \3/p') + [ -n "$version_core" ] || return 1 + + set -- $version_core + ZOLA_VERSION_MAJOR=$1 + ZOLA_VERSION_MINOR=$2 + ZOLA_VERSION_PATCH=$3 + ZOLA_VERSION_SUFFIX=$(printf '%s\n' "$ZOLA_FOUND_VERSION" | sed -n 's/^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*//p') + + return 0 +} + +zola_version_relation() { + if [ "$ZOLA_VERSION_MAJOR" -lt 0 ]; then + printf '%s\n' below + elif [ "$ZOLA_VERSION_MAJOR" -gt 0 ]; then + printf '%s\n' newer + elif [ "$ZOLA_VERSION_MINOR" -lt 22 ]; then + printf '%s\n' below + elif [ "$ZOLA_VERSION_MINOR" -gt 22 ]; then + printf '%s\n' newer + elif [ "$ZOLA_VERSION_PATCH" -lt 1 ]; then + printf '%s\n' below + elif [ "$ZOLA_VERSION_PATCH" -gt 1 ]; then + printf '%s\n' newer + elif [ -n "$ZOLA_VERSION_SUFFIX" ]; then + printf '%s\n' below + else + printf '%s\n' equal + fi +} + +install_zola() { + command -v tar >/dev/null 2>&1 || die "need tar to unpack the zola release archive" + select_zola_release + ensure_install_dir + + tmpdir=$(mktemp -d) + TMPDIR_TO_CLEAN=$tmpdir + set_cleanup_traps + + archive_path=$tmpdir/$ZOLA_ARCHIVE_NAME + release_base_url=https://github.com/getzola/zola/releases/download/v0.22.1 + + say "Installing zola 0.22.1 from $release_base_url/$ZOLA_ARCHIVE_NAME" + download "$release_base_url/$ZOLA_ARCHIVE_NAME" "$archive_path" + verify_file_hash "$archive_path" "$ZOLA_ARCHIVE_HASH" "$ZOLA_ARCHIVE_NAME" + + tar -xzf "$archive_path" -C "$tmpdir" + [ -f "$tmpdir/zola" ] || die "zola release archive did not contain zola" + + install_tmp=$(mktemp "$INSTALL_DIR/.zola.XXXXXX") || die "could not create temporary zola install file in $INSTALL_DIR" + INSTALL_TMP_TO_CLEAN=$install_tmp + cp "$tmpdir/zola" "$install_tmp" + chmod +x "$install_tmp" + mv "$install_tmp" "$INSTALL_DIR/zola" + INSTALL_TMP_TO_CLEAN= + + ZOLA=$INSTALL_DIR/zola + cleanup_tmpdir + clear_cleanup_traps +} + +ensure_zola() { + if command -v zola >/dev/null 2>&1; then + ZOLA=$(command -v zola) + elif [ -n "${HOME:-}" ] && [ -x "$HOME/.local/bin/zola" ]; then + INSTALL_DIR=$HOME/.local/bin + ZOLA=$INSTALL_DIR/zola + move_dir_to_front_of_script_path "$INSTALL_DIR" + else + install_zola + return 0 + fi + + if [ -n "${ZOLA:-}" ]; then + zola_output=$("$ZOLA" --version 2>/dev/null || true) + if parse_zola_version "$zola_output"; then + relation=$(zola_version_relation) + case "$relation" in + below) + say "Found zola $ZOLA_FOUND_VERSION below supported 0.22.1. Installing zola 0.22.1." + install_zola + ;; + equal) + say "Using existing zola $ZOLA_FOUND_VERSION at $ZOLA" + ;; + newer) + say "Found zola $ZOLA_FOUND_VERSION. build-eips is tested with zola 0.22.1 or newer. Continuing with the installed version." + ;; + esac + else + say "Found zola with unparseable version output. Installing zola 0.22.1." + install_zola + fi + fi +} + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +REPO_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +WORKSPACE_ROOT=$(CDPATH= cd -- "$REPO_ROOT/.." && pwd) +INSTALL_DIR= +PATH_NOTES= +TMPDIR_TO_CLEAN= +INSTALL_TMP_TO_CLEAN= + +if command -v build-eips >/dev/null 2>&1; then + BUILD_EIPS=$(command -v build-eips) + say "Using existing build-eips at $BUILD_EIPS" +elif [ -n "${HOME:-}" ] && [ -x "$HOME/.local/bin/build-eips" ]; then + BUILD_EIPS=$HOME/.local/bin/build-eips + INSTALL_DIR=$HOME/.local/bin + move_dir_to_front_of_script_path "$INSTALL_DIR" + say "Using existing build-eips at $BUILD_EIPS" +else + install_build_eips +fi + +ensure_zola + +say "Active proposal repo: $REPO_ROOT" +say "Workspace root: $WORKSPACE_ROOT" + +say "Bootstrapping workspace at $WORKSPACE_ROOT" +if ! "$BUILD_EIPS" -C "$REPO_ROOT" init "$WORKSPACE_ROOT" --template --platform-dev; then + die "build-eips init failed" +fi + +say "Running build-eips doctor" +if ! "$BUILD_EIPS" -C "$REPO_ROOT" doctor; then + say "Warning: build-eips doctor reported issues above. Fix them before relying on direct build-eips commands." +fi + +WORKSPACE_DOC=$WORKSPACE_ROOT/WORKSPACE.md +say "" +if [ -f "$WORKSPACE_DOC" ]; then + say "Workspace docs: $WORKSPACE_DOC (../WORKSPACE.md from this repo)" +else + say "Warning: workspace docs were not found at $WORKSPACE_DOC after build-eips init" +fi + +if [ -n "$PATH_NOTES" ]; then + say "" + say "Updated PATH for this script process only:" + old_ifs=$IFS + IFS=' +' + for path_note in $PATH_NOTES; do + say " $path_note" + done + say "To make this permanent in your shell, add:" + for path_note in $PATH_NOTES; do + say " export PATH=$(shell_quote "$path_note"):\$PATH" + done + IFS=$old_ifs +fi + +say "" +say "Next commands:" +say " cd $(shell_quote "$REPO_ROOT")" +say " build-eips serve" +say " build-eips check" +say " build-eips doctor" diff --git a/scripts/dev-setup.ps1 b/scripts/dev-setup.ps1 new file mode 100644 index 000000000..cf87c14ec --- /dev/null +++ b/scripts/dev-setup.ps1 @@ -0,0 +1,517 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Say { + param([string]$Message) + + Write-Host $Message +} + +function Die { + param([string]$Message) + + throw "error: $Message" +} + +function ConvertTo-NormalizedDirectoryPath { + param([string]$Directory) + + $trimmed = $Directory.Trim('"') + $trimChars = [char[]]@([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + + try { + return ([System.IO.Path]::GetFullPath($trimmed)).TrimEnd($trimChars) + } catch { + return $trimmed.TrimEnd($trimChars) + } +} + +function Test-DirectoryOnPath { + param([string]$Directory) + + if ([string]::IsNullOrWhiteSpace($env:Path)) { + return $false + } + + $target = ConvertTo-NormalizedDirectoryPath -Directory $Directory + foreach ($entry in ($env:Path -split ";")) { + if ([string]::IsNullOrWhiteSpace($entry)) { + continue + } + + $candidate = ConvertTo-NormalizedDirectoryPath -Directory $entry + if ([string]::Equals($candidate, $target, [System.StringComparison]::OrdinalIgnoreCase)) { + return $true + } + } + + return $false +} + +function Add-PathNote { + param([string]$InstallDir) + + $target = ConvertTo-NormalizedDirectoryPath -Directory $InstallDir + foreach ($pathNote in $script:PathNotes) { + $candidate = ConvertTo-NormalizedDirectoryPath -Directory $pathNote + if ([string]::Equals($candidate, $target, [System.StringComparison]::OrdinalIgnoreCase)) { + return + } + } + + $script:PathNotes += $InstallDir +} + +function Move-DirectoryToFrontOfSessionPath { + param([string]$InstallDir) + + $target = ConvertTo-NormalizedDirectoryPath -Directory $InstallDir + $remainingEntries = @() + + if (-not [string]::IsNullOrWhiteSpace($env:Path)) { + foreach ($entry in ($env:Path -split ";")) { + if ([string]::IsNullOrWhiteSpace($entry)) { + continue + } + + $candidate = ConvertTo-NormalizedDirectoryPath -Directory $entry + if ([string]::Equals($candidate, $target, [System.StringComparison]::OrdinalIgnoreCase)) { + continue + } + + $remainingEntries += $entry + } + } + + $newEntries = @($InstallDir) + if ($remainingEntries.Count -gt 0) { + $newEntries += $remainingEntries + } + + $updatedPath = [string]::Join(";", $newEntries) + if (-not [string]::Equals($env:Path, $updatedPath, [System.StringComparison]::Ordinal)) { + $env:Path = $updatedPath + + Add-PathNote -InstallDir $InstallDir + } +} + +function Find-BuildEipsOnPath { + foreach ($commandName in @("build-eips", "build-eips.exe")) { + $commands = @(Get-Command -Name $commandName -CommandType Application -ErrorAction SilentlyContinue) + if ($commands.Count -gt 0) { + return $commands[0].Source + } + } + + return $null +} + +function Find-ZolaOnPath { + foreach ($commandName in @("zola", "zola.exe")) { + $commands = @(Get-Command -Name $commandName -CommandType Application -ErrorAction SilentlyContinue) + if ($commands.Count -gt 0) { + return $commands[0].Source + } + } + + return $null +} + +function Assert-InstallDirWritable { + param([string]$InstallDir) + + $probePath = $null + + try { + New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null + $probeName = ".build-eips-write-test-{0}.tmp" -f ([System.Guid]::NewGuid().ToString("N")) + $probePath = Join-Path -Path $InstallDir -ChildPath $probeName + [System.IO.File]::WriteAllText($probePath, "") + Remove-Item -LiteralPath $probePath -Force + $probePath = $null + } catch { + Die ("install directory cannot be created or written ({0}): {1}" -f $InstallDir, $_.Exception.Message) + } finally { + if (($null -ne $probePath) -and (Test-Path -LiteralPath $probePath)) { + Remove-Item -LiteralPath $probePath -Force -ErrorAction SilentlyContinue + } + } +} + +function Invoke-ReleaseDownload { + param( + [string]$Url, + [string]$Destination + ) + + $previousProgressPreference = $ProgressPreference + $ProgressPreference = "SilentlyContinue" + try { + Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing + } catch { + Die ("failed to download {0}: {1}" -f $Url, $_.Exception.Message) + } finally { + $ProgressPreference = $previousProgressPreference + } +} + +function Test-AsciiHexHash { + param([string]$Hash) + + return $Hash -match "^[0-9a-fA-F]{64}$" +} + +function Assert-ArchiveChecksum { + param( + [string]$ArchivePath, + [string]$SidecarPath, + [string]$ArchiveName + ) + + if (-not (Test-Path -LiteralPath $SidecarPath -PathType Leaf)) { + Die "missing checksum sidecar: $SidecarPath" + } + + $sidecarText = [System.IO.File]::ReadAllText($SidecarPath) + $checksumLines = @($sidecarText -split "\r?\n" | Where-Object { $_.Trim().Length -gt 0 }) + if ($checksumLines.Count -ne 1) { + Die "checksum sidecar must contain exactly one checksum line" + } + + $fields = @($checksumLines[0].Trim() -split "\s+") + if ($fields.Count -ne 2) { + Die "checksum sidecar must contain only a hash and archive filename" + } + + $expectedHash = $fields[0].ToLowerInvariant() + $expectedName = $fields[1] + + if (-not (Test-AsciiHexHash -Hash $expectedHash)) { + Die "checksum sidecar hash must be 64 hex characters" + } + if ($expectedName -match '[/\\]') { + Die "checksum sidecar filename must be a basename" + } + if ($expectedName -ne $ArchiveName) { + Die ("checksum sidecar filename '{0}' does not match '{1}'" -f $expectedName, $ArchiveName) + } + + $actualHash = (Get-FileHash -Algorithm SHA256 -Path $ArchivePath).Hash.ToLowerInvariant() + if ($actualHash -ne $expectedHash) { + Die "checksum mismatch for $ArchiveName" + } +} + +function Assert-FileSha256 { + param( + [string]$ArchivePath, + [string]$ExpectedHash, + [string]$ArchiveName + ) + + $actualHash = (Get-FileHash -Algorithm SHA256 -Path $ArchivePath).Hash.ToLowerInvariant() + if ($actualHash -ne $ExpectedHash.ToLowerInvariant()) { + Die "checksum mismatch for $ArchiveName" + } +} + +function Install-BuildEips { + param( + [string]$InstallDir, + [string]$BuildEipsPath + ) + + $archiveName = "build-eips-windows.zip" + $releaseBaseUrl = "https://github.com/eips-wg/preprocessor/releases/latest/download" + $tmpRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("build-eips-" + [System.Guid]::NewGuid().ToString("N")) + $archivePath = Join-Path -Path $tmpRoot -ChildPath $archiveName + $sidecarPath = Join-Path -Path $tmpRoot -ChildPath "$archiveName.sha256" + $extractDir = Join-Path -Path $tmpRoot -ChildPath "extract" + + try { + Assert-InstallDirWritable -InstallDir $InstallDir + + New-Item -ItemType Directory -Path $extractDir -Force | Out-Null + + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + + Say "Installing build-eips from $releaseBaseUrl/$archiveName" + Invoke-ReleaseDownload -Url "$releaseBaseUrl/$archiveName" -Destination $archivePath + Invoke-ReleaseDownload -Url "$releaseBaseUrl/$archiveName.sha256" -Destination $sidecarPath + Assert-ArchiveChecksum -ArchivePath $archivePath -SidecarPath $sidecarPath -ArchiveName $archiveName + + Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force + + $extractedBuildEips = Join-Path -Path $extractDir -ChildPath "build-eips.exe" + if (-not (Test-Path -LiteralPath $extractedBuildEips -PathType Leaf)) { + Die "release archive did not contain expected build-eips.exe" + } + + try { + Move-Item -LiteralPath $extractedBuildEips -Destination $BuildEipsPath -Force + } catch { + Die ("build-eips.exe is in use. Close any running build-eips process and re-run this script. Details: {0}" -f $_.Exception.Message) + } + + return $BuildEipsPath + } catch { + Die ("failed to install build-eips: {0}" -f $_.Exception.Message) + } finally { + if (Test-Path -LiteralPath $tmpRoot) { + Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +function Get-ZolaReleaseAsset { + $architecture = $env:PROCESSOR_ARCHITECTURE + if ([string]::IsNullOrWhiteSpace($architecture)) { + $architecture = "unknown" + } + if (($architecture -eq "x86") -and (-not [string]::IsNullOrWhiteSpace($env:PROCESSOR_ARCHITEW6432))) { + $architecture = $env:PROCESSOR_ARCHITEW6432 + } + + switch ($architecture.ToUpperInvariant()) { + "AMD64" { + return @{ + ArchiveName = "zola-v0.22.1-x86_64-pc-windows-msvc.zip" + Hash = "2c8b368f5abdf2b2478748f9549a761fd6599238e18948eccb76a7cae51f5dc1" + } + } + default { + Die "Unsupported platform Windows/$architecture for automatic Zola install. Install Zola 0.22.1 manually from https://github.com/getzola/zola/releases and ensure it is on PATH." + } + } +} + +function Install-Zola { + param( + [string]$InstallDir, + [string]$ZolaPath + ) + + $asset = Get-ZolaReleaseAsset + $archiveName = $asset.ArchiveName + $archiveHash = $asset.Hash + $releaseBaseUrl = "https://github.com/getzola/zola/releases/download/v0.22.1" + $tmpRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("zola-" + [System.Guid]::NewGuid().ToString("N")) + $archivePath = Join-Path -Path $tmpRoot -ChildPath $archiveName + $extractDir = Join-Path -Path $tmpRoot -ChildPath "extract" + + try { + Assert-InstallDirWritable -InstallDir $InstallDir + + New-Item -ItemType Directory -Path $extractDir -Force | Out-Null + + [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + + Say "Installing zola 0.22.1 from $releaseBaseUrl/$archiveName" + Invoke-ReleaseDownload -Url "$releaseBaseUrl/$archiveName" -Destination $archivePath + Assert-FileSha256 -ArchivePath $archivePath -ExpectedHash $archiveHash -ArchiveName $archiveName + + Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force + + $extractedZola = Join-Path -Path $extractDir -ChildPath "zola.exe" + if (-not (Test-Path -LiteralPath $extractedZola -PathType Leaf)) { + Die "zola release archive did not contain expected zola.exe" + } + + try { + Move-Item -LiteralPath $extractedZola -Destination $ZolaPath -Force + } catch { + Die ("zola.exe is in use. Close any running zola process and re-run this script. Details: {0}" -f $_.Exception.Message) + } + + return $ZolaPath + } catch { + Die ("failed to install zola: {0}" -f $_.Exception.Message) + } finally { + if (Test-Path -LiteralPath $tmpRoot) { + Remove-Item -LiteralPath $tmpRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} + +function Get-ZolaVersionInfo { + param([string]$ZolaPath) + + try { + $output = & $ZolaPath --version 2>$null + if ($LASTEXITCODE -ne 0) { + return $null + } + } catch { + return $null + } + + $fields = @(($output -join " ") -split "\s+" | Where-Object { $_.Length -gt 0 }) + if ($fields.Count -lt 2) { + return $null + } + + $versionToken = $fields[1] + if ($versionToken -notmatch "^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$") { + return $null + } + + return @{ + VersionToken = $versionToken + Version = [version]("{0}.{1}.{2}" -f $Matches[1], $Matches[2], $Matches[3]) + Suffix = $Matches[4] + } +} + +function Get-ZolaVersionRelation { + param([hashtable]$VersionInfo) + + $minimumVersion = [version]"0.22.1" + if ($VersionInfo.Version -lt $minimumVersion) { + return "below" + } + if ($VersionInfo.Version -gt $minimumVersion) { + return "newer" + } + if (-not [string]::IsNullOrEmpty($VersionInfo.Suffix)) { + return "below" + } + + return "equal" +} + +function Install-PinnedZola { + $defaultPaths = Get-DefaultInstallPaths + $installedZola = Install-Zola -InstallDir $defaultPaths.InstallDir -ZolaPath $defaultPaths.ZolaPath + Move-DirectoryToFrontOfSessionPath -InstallDir $defaultPaths.InstallDir + + return $installedZola +} + +function Initialize-Zola { + $zolaPath = Find-ZolaOnPath + if ($null -eq $zolaPath) { + $defaultPaths = Get-DefaultInstallPaths + if (Test-Path -LiteralPath $defaultPaths.ZolaPath -PathType Leaf) { + $zolaPath = $defaultPaths.ZolaPath + Move-DirectoryToFrontOfSessionPath -InstallDir $defaultPaths.InstallDir + } + } + + if ($null -eq $zolaPath) { + return (Install-PinnedZola) + } + + $versionInfo = Get-ZolaVersionInfo -ZolaPath $zolaPath + if ($null -eq $versionInfo) { + Say "Found zola with unparseable version output. Installing zola 0.22.1." + return (Install-PinnedZola) + } + + $relation = Get-ZolaVersionRelation -VersionInfo $versionInfo + switch ($relation) { + "below" { + Say ("Found zola {0} below supported 0.22.1. Installing zola 0.22.1." -f $versionInfo.VersionToken) + return (Install-PinnedZola) + } + "equal" { + Say ("Using existing zola {0} at {1}" -f $versionInfo.VersionToken, $zolaPath) + return $zolaPath + } + "newer" { + Say ("Found zola {0}. build-eips is tested with zola 0.22.1 or newer. Continuing with the installed version." -f $versionInfo.VersionToken) + return $zolaPath + } + } +} + +function ConvertTo-PowerShellQuotedPath { + param([string]$Path) + + return "'{0}'" -f ($Path -replace "'", "''") +} + +function Get-DefaultInstallPaths { + if ([string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { + Die "LOCALAPPDATA is not set; cannot determine the user-local install directory" + } + + $installDir = Join-Path -Path (Join-Path -Path $env:LOCALAPPDATA -ChildPath "build-eips") -ChildPath "bin" + $buildEipsPath = Join-Path -Path $installDir -ChildPath "build-eips.exe" + $zolaPath = Join-Path -Path $installDir -ChildPath "zola.exe" + + return @{ + InstallDir = $installDir + BuildEipsPath = $buildEipsPath + ZolaPath = $zolaPath + } +} + +$PathNotes = @() + +$ScriptDir = (Resolve-Path -LiteralPath $PSScriptRoot).ProviderPath +$RepoRoot = (Resolve-Path -LiteralPath (Split-Path -Path $ScriptDir -Parent)).ProviderPath +$WorkspaceRoot = (Resolve-Path -LiteralPath (Split-Path -Path $RepoRoot -Parent)).ProviderPath + +$BuildEipsPath = Find-BuildEipsOnPath +if ($null -ne $BuildEipsPath) { + Say "Using existing build-eips at $BuildEipsPath" +} else { + $defaultPaths = Get-DefaultInstallPaths + $DefaultInstallDir = $defaultPaths.InstallDir + $DefaultBuildEipsPath = $defaultPaths.BuildEipsPath + + if (Test-Path -LiteralPath $DefaultBuildEipsPath -PathType Leaf) { + $BuildEipsPath = $DefaultBuildEipsPath + Move-DirectoryToFrontOfSessionPath -InstallDir $DefaultInstallDir + Say "Using existing build-eips at $BuildEipsPath" + } else { + $BuildEipsPath = Install-BuildEips -InstallDir $DefaultInstallDir -BuildEipsPath $DefaultBuildEipsPath + Move-DirectoryToFrontOfSessionPath -InstallDir $DefaultInstallDir + } +} + +$ZolaPath = Initialize-Zola + +Say "Active proposal repo: $RepoRoot" +Say "Workspace root: $WorkspaceRoot" +Say "If PowerShell blocks this script, run:" +Say " powershell -ExecutionPolicy Bypass -File .\scripts\dev-setup.ps1" + +Say "Bootstrapping workspace at $WorkspaceRoot" +& $BuildEipsPath -C $RepoRoot init $WorkspaceRoot --template --platform-dev +$WorkspaceInitExitCode = $LASTEXITCODE +if ($WorkspaceInitExitCode -ne 0) { + Die "build-eips init failed with exit code $WorkspaceInitExitCode" +} + +Say "Running build-eips doctor" +& $BuildEipsPath -C $RepoRoot doctor +$WorkspaceDoctorExitCode = $LASTEXITCODE +if ($WorkspaceDoctorExitCode -ne 0) { + Say "Warning: build-eips doctor reported issues above. Fix them before relying on direct build-eips commands." +} + +$WorkspaceDocPath = Join-Path -Path $WorkspaceRoot -ChildPath "WORKSPACE.md" +Say "" +if (Test-Path -LiteralPath $WorkspaceDocPath -PathType Leaf) { + Say "Workspace docs: $WorkspaceDocPath (../WORKSPACE.md from this repo)" +} else { + Say "Warning: workspace docs were not found at $WorkspaceDocPath after build-eips init" +} + +if ($PathNotes.Count -gt 0) { + Say "" + Say 'Updated PATH for this PowerShell session only:' + foreach ($pathNote in $PathNotes) { + Say " $pathNote" + } + Say "To make this permanent, add the listed directory or directories to your user Path in Windows Environment Variables." +} + +Say "" +Say "Next commands:" +Say (" cd {0}" -f (ConvertTo-PowerShellQuotedPath -Path $RepoRoot)) +Say " build-eips serve" +Say " build-eips check" +Say " build-eips doctor"