|
| 1 | +#!/usr/bin/env bash |
| 2 | +# The build of @objectstack/console: a copy of this workspace's own |
| 3 | +# @object-ui/console build. Nothing is cloned and nothing is pinned — the |
| 4 | +# Console source lives in ui/apps/console, its @objectstack/* dependencies are |
| 5 | +# `workspace:^`, and the root turbo.json makes `@objectstack/console#build` |
| 6 | +# depend on `@object-ui/console#build`, so the dist copied here was built |
| 7 | +# against this tree's spec and client by construction. Run it through turbo |
| 8 | +# (`pnpm console:build`, or the root `pnpm build`), never directly: a direct |
| 9 | +# run copies whatever ui/apps/console/dist holds, however old. |
| 10 | +# |
| 11 | +# What it ships: ui/apps/console/dist as-is, with the TRACKED repo-root |
| 12 | +# sdui.manifest.json copied over `dist/sdui.manifest.json` (ADR-0080; the CLI's |
| 13 | +# JSX gate reads it there, see packages/cli/src/utils/sdui-manifest.ts). The |
| 14 | +# manifest copy is the one file in this package's dist that comes from outside |
| 15 | +# the Console build, and `scripts/check-changeset-no-major.mjs` reads it out of |
| 16 | +# this script (PACKED_COPY_BUILD_SCRIPTS) to grade what a manifest change ships. |
| 17 | +# |
| 18 | +# Usage: |
| 19 | +# bash packages/console/build-dist.sh |
| 20 | +# bash packages/console/build-dist.sh --check-single-zod <assets-dir> |
| 21 | +# run ONLY the single-zod canary (below) over an |
| 22 | +# existing dist's assets/ and exit with its verdict; |
| 23 | +# copies nothing, writes nothing |
| 24 | + |
| 25 | +set -euo pipefail |
| 26 | + |
| 27 | +# ── Single-zod canary (objectstack#21108) ──────────────────────────── |
| 28 | +# The console bundle must carry exactly ONE zod instance. Two copies split |
| 29 | +# zod's internals: a spec schema built by one instance and walked by the other |
| 30 | +# (`z.toJSONSchema`) throws, so every spec-derived Studio form lost its fields — |
| 31 | +# the New Package dialog could not create a package and the dashboard/report |
| 32 | +# inspectors read "Spec schema unavailable". |
| 33 | +# |
| 34 | +# The console's own Vite build pins every `zod` import to one copy and fails on |
| 35 | +# more than one zod package directory in the emitted chunks (objectui#11353). |
| 36 | +# This is the belt to that brace, measured on what this package SHIPS rather |
| 37 | +# than on how the console build is configured, so it still holds for a build |
| 38 | +# that drops or weakens that guard — one workspace lockfile can still resolve |
| 39 | +# two zod versions. |
| 40 | +# |
| 41 | +# What it counts: zod 4 stamps every instance with a version object literal |
| 42 | +# (`version` in `zod/v4/core/versions`, read back as `_zod.version`), which the |
| 43 | +# minifier emits as `{major:4,minor:6,patch:5}`. One instance = one literal, so |
| 44 | +# the canary requires EXACTLY ONE occurrence across assets/*.js: |
| 45 | +# - two DIFFERENT literals: two zod versions, the shipped defect; |
| 46 | +# - one literal found twice: two instances of the same version — still two |
| 47 | +# instances, which is what the crash keys on; |
| 48 | +# - none at all: the matcher has gone blind (the console always bundles zod), |
| 49 | +# and a negative check that cannot see its subject would pass forever. |
| 50 | +ZOD_VERSION_LITERAL='\{[[:space:]]*major:[[:space:]]*[0-9]+,[[:space:]]*minor:[[:space:]]*[0-9]+,[[:space:]]*patch:[[:space:]]*[0-9]+[[:space:]]*,?[[:space:]]*\}' |
| 51 | + |
| 52 | +assert_single_zod() { |
| 53 | + local assets="$1" |
| 54 | + if [[ ! -d "$assets" ]]; then |
| 55 | + echo "✗ Single-zod canary: ${assets} is not a directory." |
| 56 | + return 1 |
| 57 | + fi |
| 58 | + local files=() |
| 59 | + local f |
| 60 | + for f in "$assets"/*.js; do |
| 61 | + [[ -f "$f" ]] && files+=("$f") |
| 62 | + done |
| 63 | + if (( ${#files[@]} == 0 )); then |
| 64 | + echo "✗ Single-zod canary: no .js files under ${assets} — nothing was measured." |
| 65 | + return 1 |
| 66 | + fi |
| 67 | + local hits="" rc=0 |
| 68 | + hits="$(grep -oHE "$ZOD_VERSION_LITERAL" "${files[@]}")" || rc=$? |
| 69 | + if (( rc > 1 )); then |
| 70 | + echo "✗ Single-zod canary: grep failed (exit ${rc}) reading ${assets}/*.js." |
| 71 | + return 1 |
| 72 | + fi |
| 73 | + # One line per occurrence: <file>:<literal>, the literal's whitespace and any |
| 74 | + # trailing comma dropped so a re-formatted bundle reads the same version. |
| 75 | + local occurrences |
| 76 | + occurrences="$(printf '%s\n' "$hits" | sed -e '/^$/d' -e 's/[[:space:]]//g' -e 's/,}$/}/')" |
| 77 | + local count=0 distinct=0 |
| 78 | + if [[ -n "$occurrences" ]]; then |
| 79 | + count="$(printf '%s\n' "$occurrences" | wc -l | tr -d ' ')" |
| 80 | + distinct="$(printf '%s\n' "$occurrences" | sed 's/^.*:{/{/' | sort -u | wc -l | tr -d ' ')" |
| 81 | + fi |
| 82 | + if (( count == 0 )); then |
| 83 | + echo "✗ Single-zod canary: none of the ${#files[@]} assets/*.js files carries a zod version" |
| 84 | + echo " literal ({major:N,minor:N,patch:N}). The console always bundles zod, so zero means" |
| 85 | + echo " this matcher can no longer see it — and a check that cannot see its subject cannot" |
| 86 | + echo " fail. Fix the matcher (ZOD_VERSION_LITERAL in packages/console/build-dist.sh) before" |
| 87 | + echo " trusting this bundle." |
| 88 | + return 1 |
| 89 | + fi |
| 90 | + if (( count > 1 )); then |
| 91 | + if (( distinct > 1 )); then |
| 92 | + echo "✗ Single-zod canary: the console bundle carries ${distinct} different zod versions" |
| 93 | + echo " (${count} version literals) and must carry exactly one:" |
| 94 | + else |
| 95 | + echo "✗ Single-zod canary: the console bundle carries ${count} copies of ONE zod version" |
| 96 | + echo " literal — two instances of the same version are still two instances:" |
| 97 | + fi |
| 98 | + printf '%s\n' "$occurrences" | sed "s|^${assets%/}/| |" |
| 99 | + echo " A schema built by one zod instance and walked by another breaks every spec-derived" |
| 100 | + echo " Studio form. The workspace lockfile resolves zod twice for the console's closure:" |
| 101 | + echo " dedupe it (one zod line across packages/spec and ui/apps/console), then rebuild." |
| 102 | + return 1 |
| 103 | + fi |
| 104 | + echo "✓ Single-zod canary: exactly one zod version literal $(printf '%s' "$occurrences" | sed 's/^.*:{/{/') in ${occurrences%%:*}." |
| 105 | + echo " (${#files[@]} assets/*.js files read)" |
| 106 | + return 0 |
| 107 | +} |
| 108 | + |
| 109 | +if [[ "${1:-}" == "--check-single-zod" ]]; then |
| 110 | + if [[ -z "${2:-}" ]]; then |
| 111 | + echo "usage: packages/console/build-dist.sh --check-single-zod <assets-dir>" |
| 112 | + exit 2 |
| 113 | + fi |
| 114 | + if assert_single_zod "$2"; then exit 0; fi |
| 115 | + exit 1 |
| 116 | +fi |
| 117 | + |
| 118 | +FRAMEWORK_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" |
| 119 | +CONSOLE_DIST="${FRAMEWORK_ROOT}/ui/apps/console/dist" |
| 120 | +TARGET="${FRAMEWORK_ROOT}/packages/console/dist" |
| 121 | +MANIFEST_SRC="${FRAMEWORK_ROOT}/sdui.manifest.json" |
| 122 | + |
| 123 | +if [[ ! -f "${CONSOLE_DIST}/index.html" ]]; then |
| 124 | + echo "✗ ${CONSOLE_DIST}/index.html is missing — @object-ui/console has not been built." |
| 125 | + echo " Build through turbo, which builds it first: pnpm console:build" |
| 126 | + exit 1 |
| 127 | +fi |
| 128 | +if [[ ! -f "$MANIFEST_SRC" ]]; then |
| 129 | + echo "✗ ${MANIFEST_SRC} is missing — the console dist ships the tracked SDUI manifest." |
| 130 | + echo " Regenerate it: node scripts/gen-sdui-manifest-node.mjs" |
| 131 | + exit 1 |
| 132 | +fi |
| 133 | + |
| 134 | +echo "→ Copying ${CONSOLE_DIST} → ${TARGET}" |
| 135 | +rm -rf "$TARGET" |
| 136 | +cp -R "$CONSOLE_DIST" "$TARGET" |
| 137 | +cp "$MANIFEST_SRC" "${TARGET}/sdui.manifest.json" |
| 138 | + |
| 139 | +# Placed after the copy so it judges the bytes this package publishes. |
| 140 | +if ! assert_single_zod "${TARGET}/assets"; then |
| 141 | + rm -rf "$TARGET" |
| 142 | + exit 1 |
| 143 | +fi |
| 144 | + |
| 145 | +BYTES="$(du -sk "$TARGET" 2>/dev/null | awk '{print $1}')" |
| 146 | +echo "✓ @objectstack/console dist ready (${BYTES} KB) from ui/apps/console, with the tracked sdui.manifest.json" |
0 commit comments