Brings the parallel CI paths into line with the lockfile-pinned
release path and tightens the supply-chain audit surface:
studio-tauri-smoke.yml: run lockfile_supply_chain_audit.py before
the Tauri CLI install, and install via `npm ci --prefix studio`
against the committed studio/package-lock.json (was a mutable
`npm install --save-dev` post-audit). This relocates the existing
pre-install lockfile supply-chain audit step; the step's name and
command are preserved verbatim so its purpose is unchanged, only
its position relative to the install. The earlier security
rationale about lifecycle scripts and the postinstall-dropper
class is preserved on the Frontend build step where it actually
applies (vite/esbuild lifecycle scripts run on the frontend
install); the Tauri CLI install step gets a new rationale tied to
`npm ci` semantics.
security-audit.yml:
* add studio/package.json and studio/package-lock.json to the PR
path filter so a Tauri CLI lockfile change cannot bypass the
workflow,
* extend OSV-Scanner, scan_npm_packages.py (with LOG3 and exit-
code propagation), and the install-script diff to cover
studio/package-lock.json,
* add an npm audit step for the Tauri CLI holder project,
* extend the npm-provenance-and-install-scripts job with
--ignore-scripts installs + npm audit signatures for the
oxc-validator and Tauri CLI holder projects; the existing
frontend audit-signatures step is renamed to "(Studio
frontend, informational)" purely for disambiguation against
the two new sibling steps, with its log path rerouted through
$GITHUB_WORKSPACE so a single artifact upload can collect all
three logs,
* update the lockfile-audit step summary to list the Tauri CLI
holder lockfile,
* fix the stale "Initially non-blocking" comment on the now-
blocking npm scan-packages step.
build.sh and studio/setup.ps1 (oxc): pass --no-fund --no-audit to
npm ci for parity with the other call sites.
studio/setup.sh and studio/setup.ps1: restore the bun.lock
exclusion in the frontend staleness check so a leftover local
bun.lock from the migration does not trigger a spurious rebuild.
scripts/lockfile_supply_chain_audit.py: emit a HIGH-severity
missing-lockfile Finding when a requested lockfile does not
exist, so a deleted default cannot silently pass the audit. Uses
the script's own Finding accumulator pattern (sibling
scripts/scan_npm_packages.py implements the same intent via an
rc=2 hard-fail, its single-lockfile-per-invocation idiom; this
script aggregates multiple lockfiles so Finding is the natural
channel).
scripts/check_frontend_dep_removal.py: add studio/package.json
and studio/package-lock.json to EXPECTED_NOISE_FILES; the new
Tauri CLI holder manifests must not count as frontend dep usage.
99 lines
3.2 KiB
Bash
99 lines
3.2 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
# PyPI/Studio release publishing must use `./build.sh publish` (or an
|
|
# equivalent stamp -> build -> verify-dist -> upload flow) so packaged Studio
|
|
# artifacts include the display-only Studio release version.
|
|
|
|
# 1. Build frontend (Vite outputs to dist/)
|
|
cd studio/frontend
|
|
|
|
# Clean stale dist to force a full rebuild
|
|
rm -rf dist
|
|
|
|
# Tailwind v4's oxide scanner respects .gitignore in parent directories.
|
|
# Python venvs create a .gitignore with "*" (ignore everything), which
|
|
# prevents Tailwind from scanning .tsx source files for class names.
|
|
# Temporarily hide any such .gitignore during the build, then restore it.
|
|
_HIDDEN_GITIGNORES=()
|
|
_dir="$(pwd)"
|
|
while [ "$_dir" != "/" ]; do
|
|
_dir="$(dirname "$_dir")"
|
|
if [ -f "$_dir/.gitignore" ] && grep -qx '\*' "$_dir/.gitignore" 2>/dev/null; then
|
|
mv "$_dir/.gitignore" "$_dir/.gitignore._twbuild"
|
|
_HIDDEN_GITIGNORES+=("$_dir/.gitignore")
|
|
fi
|
|
done
|
|
|
|
_restore_gitignores() {
|
|
for _gi in "${_HIDDEN_GITIGNORES[@]+"${_HIDDEN_GITIGNORES[@]}"}"; do
|
|
mv "${_gi}._twbuild" "$_gi" 2>/dev/null || true
|
|
done
|
|
}
|
|
trap _restore_gitignores EXIT
|
|
|
|
# Frontend installs always use npm ci against the committed lockfile.
|
|
# There is no bun.lock anywhere in the repo, so a bun-first branch
|
|
# would always miss and silently regenerate (or fail under
|
|
# --frozen-lockfile). Keep this single path until/unless a real
|
|
# bun.lock lands.
|
|
if ! npm ci --no-fund --no-audit; then
|
|
echo "❌ ERROR: npm ci failed" >&2
|
|
exit 1
|
|
fi
|
|
npm run build # outputs to studio/frontend/dist/
|
|
|
|
_restore_gitignores
|
|
trap - EXIT
|
|
|
|
# Validate CSS output -- catch truncated Tailwind builds before packaging
|
|
MAX_CSS_SIZE=$(find dist/assets -name '*.css' -exec wc -c {} + 2>/dev/null | sort -n | tail -1 | awk '{print $1}')
|
|
if [ -z "$MAX_CSS_SIZE" ]; then
|
|
echo "❌ ERROR: No CSS files were emitted into dist/assets."
|
|
echo " The frontend build may have failed silently."
|
|
exit 1
|
|
fi
|
|
if [ "$MAX_CSS_SIZE" -lt 100000 ]; then
|
|
echo "❌ ERROR: Largest CSS file is only $((MAX_CSS_SIZE / 1024))KB (expected >100KB)."
|
|
echo " Tailwind may not have scanned all source files."
|
|
echo " Check for .gitignore files blocking the Tailwind oxide scanner."
|
|
exit 1
|
|
fi
|
|
echo "✅ Frontend CSS validated (${MAX_CSS_SIZE} bytes)"
|
|
|
|
cd ../..
|
|
|
|
# 2. Clean old artifacts
|
|
rm -rf build dist *.egg-info
|
|
|
|
# 3. Stamp display-only Studio release metadata for packaged builds.
|
|
_STUDIO_BUILD_INFO="studio/backend/utils/_studio_release_build.py"
|
|
_STUDIO_BUILD_INFO_BACKUP="$(mktemp)"
|
|
cp "$_STUDIO_BUILD_INFO" "$_STUDIO_BUILD_INFO_BACKUP"
|
|
_restore_studio_build_info() {
|
|
cp "$_STUDIO_BUILD_INFO_BACKUP" "$_STUDIO_BUILD_INFO" 2>/dev/null || true
|
|
rm -f "$_STUDIO_BUILD_INFO_BACKUP"
|
|
}
|
|
trap _restore_studio_build_info EXIT
|
|
|
|
if [ "${1:-}" = "publish" ]; then
|
|
STUDIO_STAMPED_VERSION="$(python scripts/stamp_studio_release.py --require-release)"
|
|
else
|
|
STUDIO_STAMPED_VERSION="$(python scripts/stamp_studio_release.py)"
|
|
fi
|
|
|
|
# 4. Build wheel/sdist
|
|
python -m build
|
|
|
|
if [ "${1:-}" = "publish" ]; then
|
|
python scripts/stamp_studio_release.py --verify-dist dist --expected "$STUDIO_STAMPED_VERSION"
|
|
fi
|
|
|
|
_restore_studio_build_info
|
|
trap - EXIT
|
|
|
|
# 5. Optionally publish
|
|
if [ "${1:-}" = "publish" ]; then
|
|
python -m twine upload dist/*
|
|
fi
|