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.
Two blockers from the parallel Opus review batch:
1. The Tauri CLI install in release-desktop.yml was the last unfrozen
install path: `npm install --save-dev --prefix studio
@tauri-apps/cli@2.10.1 --no-fund --no-audit` pins the top-level
version but leaves the transitive closure floating, defeats the
pre-install lockfile audit (no lockfile to scan), and skips
integrity verification. Committed a minimal studio/package.json
(devDep @tauri-apps/cli@2.10.1) plus the resolved
studio/package-lock.json (12 packages: CLI + 11 platform-native
binaries, all with integrity hashes, lockfileVersion 3). Switched
the step to `npm ci --prefix studio` and added a pre-install
lockfile_supply_chain_audit.py step ahead of it so any tarball
postinstall is gated by the structural scan. Allowlisted
studio/package-lock.json .gitignore and added it to the
audit script's default scan set.
2. The bun branch was dead code in build.sh, studio/setup.sh, and
studio/setup.ps1: nowhere in the repo is a bun.lock committed,
and `bun install --frozen-lockfile` cannot migrate from
package-lock.json. With no lockfile, every entry to the bun
path either silently regenerates a bun.lock (under permissive
install modes -- a fresh attack surface) or fails outright (under
frozen-lockfile). Removed `npm install -g bun` bootstrap, the
`_try_bun_install` helper + cache-retry, every `if bun.lock &&
command -v bun` guard, and the now-unreachable
"fall back to npm" messaging. All three scripts now have a
single `npm ci` path. bun.lock skip entries in lint-ci.yml +
wheel-smoke.yml are kept as forward-compat sanity checks --
they assert bun.lock is NOT shipped / scanned, which is
stronger after this commit, not weaker.
Smoke-tested locally:
`npm ci --prefix studio` resolves 3 packages (CLI + 2 linux native
binaries), `npx --prefix studio tauri --version` prints
`tauri-cli 2.10.1` exactly.
`python3 scripts/lockfile_supply_chain_audit.py` scans 3 npm + 1
cargo lockfiles, 0 findings.
`bash -n build.sh`, `bash -n studio/setup.sh`, and a pwsh
scriptblock parse of studio/setup.ps1 all succeed.
Two issues codex flagged:
1. bun.lock gate (studio/frontend/.gitignore line 14 ignores bun.lock,
so it is never committed). bun install --frozen-lockfile cannot
migrate from package-lock.json, so without a bun.lock the bun path
always fails. setup.sh then misclassifies that as a corrupt cache,
clears the user's bun cache, and re-runs the same guaranteed-failing
command before falling back to npm. build.sh, studio/setup.sh, and
studio/setup.ps1 now only enter the bun path when bun.lock is
present; otherwise we go straight to npm ci.
2. OXC validator lockfile was outside the npm supply-chain scan
surface. lockfile_supply_chain_audit.py default, npm audit, OSV,
scan_npm_packages.py invocation, and the diff-for-new-install-scripts
step all now cover both lockfiles. security-audit.yml pull_request
paths filter triggers on changes to either. wheel-smoke checks the
built wheel ships the OXC lockfile too.
Verified:
python3 scripts/lockfile_supply_chain_audit.py
> OK: 0 findings across 2 npm + 1 cargo lockfile(s)
python3 scripts/scan_npm_packages.py --lockfile oxc-validator/...
> OK
build.sh and studio/setup.sh both call naked `bun install` and
`npm install`. With caret ranges in package.json (the default for
most deps), those commands resolve a fresh minor/patch from the
registry if one exists, even though the lockfile pins specific
versions. An attacker who hijacks any transitive dep and publishes
a malicious patch release can have it pulled into the release build
or end-user install without anyone noticing.
Both paths now use lockfile-strict mode:
bun install -> bun install --frozen-lockfile
npm install -> npm ci
These install exactly what the committed lockfile pins, verify
cryptographic hashes, and fail fast on any drift between package.json
and the lockfile. The CI workflows that build the frontend already
use `npm ci`; this aligns the local build and end-user setup paths
with the same guarantee.
Verified `npm ci --no-fund --no-audit --dry-run` exits 0 against
the current studio/frontend lockfile (1042 packages, no drift).
* Add Studio web update and release version display
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Show package version in Studio settings
* Break training unload guard barrel cycle
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
* perf(studio): upgrade to Vite 8 + auto-install bun for 3x faster frontend builds
* fix(studio): make bun-to-npm fallback actually reachable
setup.sh used run_quiet() for the bun install attempt, but run_quiet
calls exit on failure. This killed the script before the npm fallback
could run, making the "falling back to npm" branch dead code.
Replace the run_quiet call with a direct bun invocation that captures
output to a temp file (same pattern, but returns instead of exiting).
Also clean up partial node_modules left by a failed bun install before
falling back to npm, in both setup.sh and build.sh. Without this, npm
inherits a corrupted node_modules tree from the failed bun run.
* fix(studio): restore commonjsOptions for dagre CJS interop
The previous commit removed build.commonjsOptions, assuming Vite 8's
Rolldown handles CJS natively. While optimizeDeps.include covers the
dev server (pre-bundling), it does NOT apply to production builds.
The resolve.alias still points @dagrejs/dagre to its .cjs.js entry,
so without commonjsOptions the production bundle fails to resolve
the CJS default export. This causes "TypeError: e is not a function"
on /chat after build (while dev mode works fine).
Restore the original commonjsOptions block to fix production builds.
* fix(studio): use motion/react instead of legacy framer-motion import
* fix(studio): address PR review findings for Vite 8 + bun upgrade
Fixes:
- Remove bun.lock from repo and add to .gitignore (npm is source of truth)
- Use & bun install *> $null pattern in setup.ps1 for reliable $LASTEXITCODE
- Add Remove-Item node_modules before npm fallback in setup.ps1
- Print bun install failure log in setup.sh before discarding
- Add Refresh-Environment after npm install -g bun in setup.ps1
- Tighten Node version check to ^20.19.0 || >=22.12.0 (Vite 8 requirement)
- Add engines field to package.json
- Use string comparison for _install_ok in build.sh
- Remove explicit framer-motion ^11.18.2 from package.json (motion pulls
framer-motion ^12.38.0 as its own dependency — the old pin caused a
version conflict)
* Fix Colab Node bypass and bun.lock stale-build trigger
Gate the Colab Node shortcut on NODE_OK=true so Colab
environments with a Node version too old for Vite 8 fall
through to the nvm install path instead of silently proceeding.
Exclude bun.lock from the stale-build probe in both setup.sh
and setup.ps1 so it does not force unnecessary frontend rebuilds
on every run.
---------
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Shine1i <wasimysdev@gmail.com>
* One liner setup for unsloth studio
* Fix install scripts: system deps, activation bugs, curl/wget support
- install.sh: detect platform (macOS/Linux/WSL) and check for missing
system dependencies (cmake, git, build-essential, libcurl4-openssl-dev).
Prompt user once for permission to install all missing packages via
brew (macOS) or sudo apt-get (Linux/WSL). Add wget fallback via
download() helper since curl is not always present on minimal Linux
installs. Fix nested curl|sh stdin stealing by downloading uv installer
to a tempfile first. Replace venv activation (no-op in a pipe subshell)
with explicit --python flag for uv pip install and direct venv binary
invocation. Add idempotency guard for venv creation. Redirect stdin
on unsloth studio setup to prevent pipe consumption. On macOS, check
for Xcode Command Line Tools and trigger install if missing.
- install.ps1: wrap script body in Install-UnslothStudio function so
that errors use return instead of exit (exit kills the terminal when
run via irm|iex). Remove activate.ps1 invocation entirely -- use
explicit --python path for uv pip install and & $UnslothExe for
studio setup. This avoids both the child-scope activation bug (& vs
dot-source) and the execution policy error on default Windows systems.
Add winget availability check with clear error message. Fix PATH
refresh to append registry paths instead of replacing the session PATH.
Add uv installer fallback via astral.sh PowerShell script if winget
install does not put uv on PATH. Broaden Python version check to
accept 3.11-3.13. Add idempotency guard for venv creation.
- README.md: add wget one-liner alternative for systems without curl.
* Fix Tailwind CSS v4 .gitignore bug on Windows (#4444)
- Add .gitignore hiding workaround to setup.ps1 (matching existing
setup.sh logic) so venv .gitignore files containing "*" don't prevent
Tailwind's oxide scanner from finding .tsx source files
- Add CSS size validation to setup.sh, setup.ps1, and build.sh to catch
truncated Tailwind builds early
- Remove stray force-rebuild overrides that made the "skip build if
current" cache check dead code in both setup scripts
- Add rm -rf dist to build.sh to force clean rebuilds for wheel packaging
* Change default port 8000 to 8888, fix installer bugs, improve UX
- Change default Studio port from 8000 to 8888 across all entry points
(run.py, studio.py, ui.py, colab.py, vite.config.ts, setup scripts)
- Update launch banner: "Launching with studio venv..." to
"Launching Unsloth Studio... Please wait..."
- Add "Open your web browser" banner and rename labels
(Local -> Local Access, External -> Worldwide Web Address)
- Fix venv idempotency: check for bin/python instead of just directory
existence, clean up partial venvs on retry
- Fix build.sh CSS validation: handle empty CSS case that silently
bypassed the check with "integer expression expected"
- Fix install.sh sudo handling: try apt-get without sudo first (works
when root), then escalate with per-package tracking and user prompt
- Fix install.ps1: check exit code from studio setup, fail on error
- Add pciutils to WSL GGUF build dependencies
- Apply same smart apt-get escalation pattern to studio/setup.sh
* Use detected Python version for venv, abort on non-apt Linux
- install.ps1: detect existing Python 3.11/3.12/3.13 and use that
version for venv creation instead of always forcing 3.13
- install.sh: exit with error on non-apt Linux distros when required
packages cannot be auto-installed, instead of silently continuing
* Make sudo permission prompt more prominent with warning banner
* Add Accept [Y/n] sudo prompt to studio/setup.sh for consistency
* Fix native command exit code handling and sudo decline flow
install.ps1: Add $LASTEXITCODE checks after winget (Python), uv venv,
and uv pip install calls. $ErrorActionPreference only catches PowerShell
cmdlet errors, not native executable failures. The Python check also
handles winget returning non-zero for "already installed".
setup.sh: Skip llama-server build when user declines sudo or sudo is
unavailable. Previously the script continued to section 8 which would
fail with confusing errors (e.g. "gcc: command not found") since
build-essential was never installed.
* Move rm -rf llama.cpp inside build branch to preserve existing install
When _SKIP_GGUF_BUILD is set (user declined sudo or sudo unavailable),
the previous rm -rf would destroy an already-working llama-server before
the skip check ran. Move it inside the else branch so existing builds
are preserved when the rebuild is skipped.
---------
Co-authored-by: Daniel Han <danielhanchen@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>