Skip to content

Latest commit

 

History

History
396 lines (331 loc) · 26 KB

DEVELOPMENT.md

File metadata and controls

396 lines (331 loc) · 26 KB

Development

This repository contains the Rust compiler, JavaScript host services, build integrations, and developer tools. The workspace uses pnpm through Vite+. Tasks are defined in each package's vite.config.ts#run.tasks.

Setup and verification

Install Vite+, Rust, and the platform linker. The required Rust version is declared in Cargo.toml. Vite+ uses the pnpm version selected by the workspace.

Run from the repository root:

vp i --frozen-lockfile
vp run verify

verify runs check and test. Browser, WASM, framework acceptance, and timing checks have separate tasks and prerequisites. See Tests and verification for the task inventory, generated files, cache behavior, and fixture workflows. Run generation tasks explicitly and review their output before comparing fixtures.

The CI workflow runs check, test:rust, test:js, and test:fixtures:projects on Linux for pull requests and pushes to main. The test:js job then runs test:browser and test:wasm with its builds. The Release workflow runs the same jobs and adds test:tailwind, which takes about 15 minutes. The CI workflow skips changes that contain only documentation. It also skips the version pull request and pushes that change .changeset/ledger.yaml, because the Release workflow runs the same tasks for them. A new push to a branch cancels the running workflow for that branch.

CODEOWNERS requests a maintainer review for workflows, generated release files, dependencies, scripts that privileged workflows run, license files, and agent instructions. The Protected paths workflow fails when a pull request from a fork changes .github/, .changeset/ledger.yaml, or a CHANGELOG.md file. Update both files together when you change this set of paths.

The Browser acceptance workflow tests the primary tier daily and the compatibility tier weekly. The Release workflow calls it for the primary tier on the version pull request and again after the merge. It also tests the compatibility tier on the version pull request and in a dry run. Each run splits the tier across four runners with --shard. Start Actions → Browser acceptance → Run workflow to test either tier manually.

.github/workflows/actions.lock records the commit of each action that the workflows use, and GitHub Actions runs those commits. After you add a workflow or change a uses: value, run gh actions-lock and commit the result. Install the command with gh extension install github/gh-actions-lock. Review a change of a locked commit before you commit it.

Package build tasks build their dependencies through the task graph. Applications that use the workspace integration declare dependsOn: ["@stillwind/integration#build"] for their development and build tasks. This prerequisite builds the integration's dist/ files before the application loads them.

Before you publish stillwind, run vp run stillwind#prepare-publish. It builds the package and the website, and it copies the website's Markdown pages into packages/core/docs/, which git ignores.

Release builds of the Linux bindings set NATIVE_USE_NAPI_CROSS=true, so they link with the glibc 2.17 toolchain from @napi-rs/cross-toolchain. The toolchain downloads during the build and only runs on Linux. packages/native/check-glibc.ts fails the release when a binding requires a newer glibc.

Set NATIVE_TARGET to a Rust target triple to build the binding for a target other than the host. The Release workflow builds the darwin-x64 binding on an Apple silicon runner with NATIVE_TARGET=x86_64-apple-darwin, and the Desktop package job tests it on an Intel runner.

Before you publish the platform packages of @stillwind/native, run vp run @stillwind/native#prepare-publish. It checks that packages/native/RUST-THIRD-PARTY-LICENSES is current and copies it into each platform package, where git ignores the copy.

Package releases

All publishable packages release at one shared version. versioning.fixed in pnpm-workspace.yaml lists them, and versioning.ignore lists every private package. These lists accept exact names only, so add a new package to one of them.

Record each new feature and each other user-visible package change with vp exec pnpm change, and commit the intent file with the change. vp exec pnpm change status shows the release plan.

vp exec pnpm version -r consumes the intents, updates the CHANGELOG.md of each released package, and creates no Git tag. The first release of each package does not use intents: set its manifest to the intended version and publish it.

When .changeset/ changes on main, the Version PR workflow runs pnpm version -r and keeps the result in a pull request from version/main with the release label. The workflow also runs node tools/workspace/sync-version.ts, which copies the new version to the Rust workspace. Fixture generators remove the version from the Stillwind notice, so a release does not change expected outputs.

The Release workflow builds and tests the version pull request like a release, including browser acceptance for both tiers. It also builds the npm packages, adds the platform binaries, and packs every package with pnpm publish -r --dry-run. It tests a pull request only when the pull request comes from version/main in this repository. The upload jobs run only on main and skip this run. The Sites workflow checks both sites and every browser scenario for this pull request, whatever its change. github-actions[bot] creates this pull request, so its Release and Sites runs wait for a maintainer to approve them in the Actions tab. Wait for the Release run to pass, then merge the pull request with Squash and merge.

The merge updates .changeset/ledger.yaml on main. This change starts the Release workflow again, which builds and tests everything on main. It builds the npm packages while the tests run. After the tests succeed, the workflow uploads the npm packages to npm's staging area and publishes the VS Code extension. A version with a prerelease suffix receives the npm tag next; any other version receives latest.

To check a release without uploading packages, start Actions → Release → Run workflow on main with the default dry run. A dry run runs the same checks as the version pull request but skips the upload jobs, so it does not wait for a deployment review. Clear dry-run to run the uploads again after a failed release.

Staged versions are not installable until a maintainer approves them with 2FA. Review them with vp exec pnpm stage list, then run vp exec pnpm stage approve and select every package. pnpm approves packages in dependency order and skips a package when one of its workspace dependencies is not approved. Use vp exec pnpm stage reject <stage-id> to remove a staged version. After the approval succeeds, advance stable to the release commit.

After both uploads succeed, the Release workflow creates a draft GitHub Release named v<version> for the release commit. The draft contains the VS Code extension packages, SHA256SUMS, and build provenance attestations for the packages. Its body lists links to the changelog sections since the previous published GitHub Release. tools/workspace/release-draft.ts writes this body. The body starts with a short request for a coding agent, and an HTML comment holds the detailed instructions from tools/workspace/release-notes-prompt.md. Give the request to an agent in your checkout of this repository. The agent reads the draft with gh, follows the instructions, and replaces the body with the notes. It also checks that npm, Visual Studio Marketplace, and Open VSX serve the new version and that stable points to the release commit. When npm and both marketplaces serve the version, the agent proposes to publish the draft and publishes it only after you tell it to. Review the notes before you approve. GitHub creates the tag when the draft is published. A rerun of the workflow keeps the notes of an existing draft and replaces its assets, and it skips a published release.

npm setup

  1. Create the npm-release GitHub environment with a Branch rule for main.
  2. For a package that does not exist on npm yet, add a short-lived granular token as the NPM_TOKEN environment secret.
  3. After the first publication, add this repository's release.yml and the npm-release environment as the trusted publisher of each package, allow staged publishing for it, then delete NPM_TOKEN.

npm adds provenance only to trusted publications from a public repository.

Site deployments

Sites builds and checks the website and playground on pull requests and pushes to main and stable. Pushes to main update the main Preview of each Worker. Pushes to stable deploy production:

Application Worker Production URL
Website stillwind-website https://stillwind.dev
Playground stillwind-play https://play.stillwind.dev

The main Preview uses https://main.preview.stillwind.dev/ for the website and https://main.play-preview.stillwind.dev/ for the playground. Each Worker has a dedicated parent domain for Previews. A Preview name becomes its first subdomain, so pull request 123 uses https://pr-123.preview.stillwind.dev/ and https://pr-123.play-preview.stillwind.dev/.

Pull request Previews

Pull requests do not publish Previews by default. Two controls request one:

Control Who can use it Pull requests Effect
preview label People with triage access This repository Publishes each new commit
/preview comment People with write access This repository Publishes the current head once
/preview <sha> comment People with write access This repository or forks Publishes that head commit once

The <sha> value must be the full SHA of the current head commit. The label has no effect on a fork, because a fork can add commits after a maintainer reviews it. A fork's Sites run also needs approval, and that run must finish before /preview <sha>.

Sites builds each pull request with these Preview origins for the sites in its change plan. Links to other sites use the main Preview. The Site Preview workflow publishes that build without rebuilding it, so a Preview requires a build from the last 7 days. It accepts builds from failed runs, because a Preview can help to investigate the failure.

Site Preview runs from the default branch and holds the Cloudflare token, so it never executes pull request code. Workflow files in a pull request can change what its runs report and upload. The workflow therefore reads labels, head commits, and permissions from the GitHub API. It takes only static assets from the artifact, and it writes the Worker configuration from the default branch. Site Preview Request only starts Site Preview after labeling, because public repositories disable pull_request_target by default.

The workflow reports the URLs in one comment on the pull request and adds the preview:active label. A daily run deletes the Previews of closed pull requests that have this label. Removing the preview label stops updates and keeps the Preview until the pull request closes.

Until the first package release, stable tracks the selected initial site snapshot. It then contains the code and documentation for the published packages. Apply documentation fixes there and bring them into main. After a package release succeeds, advance stable to its release commit. If automation updates stable with GITHUB_TOKEN, explicitly dispatch Sites on stable: pushes made with that token do not start another workflow.

The applications declare their Worker settings in cloudflare.config.ts. Vite+ builds the static sites and tools/sites writes Cloudflare Build Output under .deploy/<app>/.cloudflare/output/v0/. The build excludes source maps and internal build files, checks asset limits, and preserves WASM isolation headers. Both applications register siteHtml() from tools/site-html in astro.config.ts. After astro build, it minifies the inline scripts of the rendered pages and appends a Content-Security-Policy rule for /* to the build's _headers. The application sets the directives, and the integration adds the hash of each inline script in the output to script-src. An inline script that the build does not write, such as the playground preview frame's script, must be returned from runtimeInlineScripts. Development servers send no policy, so check a policy change with a production build. Preview builds add noindex and block crawlers in robots.txt. PUBLIC_WEBSITE_URL and PUBLIC_PLAYGROUND_URL select the two origins used by metadata, sitemaps, agent indexes, and navigation between the sites. CI uses the configured Preview URLs on main, the production URLs on stable, and the URLs described in Pull request Previews on pull requests. check:urls checks the generated files before deployment. The deployment checks also compare the published canonical and social card URLs with the actual deployment URL.

The lockfile fixes the versions of Cloudflare CLI cf and its Build Output libraries. Update them together and verify both deployment modes when upgrading during the beta. The deployment job downloads the checked Build Output and uses cf with --prebuilt. It does not build application code. Only that step receives the Cloudflare token. Pull request runs have no deployment credentials. The GitHub deployments and Actions summary link to the deployed sites. Deployments to the same Worker run one at a time across both branches. HTTP checks verify documentation routes, Preview indexing rules, and WASM headers after deployment. The playground acceptance suite tests browser behavior before deployment. The workflow builds the selected sites in one job. Six browser jobs cover formatting and completions, compiler and TypeScript diagnostics, HMR, shared links, file loading, and worker recovery. They consume the same acceptance build and use independent browser state. A failed scenario can reuse that build when retried. Build and verify always runs and requires every selected job to succeed. It accepts a skipped job only when the change plan omitted it. Production validation consumes the Build Output prepared earlier in the build job.

tools/workspace/impact.ts resolves workspace packages and their transitive consumers. The Sites plan adds Rust and generator inputs and selects each application's checks. Website-only changes skip the playground build and browser jobs. Browser test changes select their scenarios without deploying. Developer documentation and unrelated packages skip site work. Unclassified inputs, missing Git history, manual runs, and the version pull request select both sites and all scenarios. PRs compare against their base. Pushes compare against the last successful Sites run on the branch, so failed or cancelled runs cannot hide unpublished changes. An obsolete run fails its deployment head check and cannot become that baseline. The plan appears in the Actions summary. It rejects a browser matrix that does not cover every playground test file; register new scenarios in tools/sites/ci.ts.

verify:sites:website and verify:sites:play run the corresponding build checks locally. Package builds and unit or framework tests can use the same impact helper to select their own jobs. The Sites workflow checks only its applications and their required artifacts.

The build job restores Vite Task results after installing dependencies. Task fingerprints track sources and deployment URLs before reusing outputs. Cargo and Vite Task use a new GitHub cache key for each run, so successful builds save their updated caches. Cargo archives are saved only when the build changes files under target/.

To inspect a browser failure, download its evidence artifact and open trace.zip with Playwright Trace Viewer. To test a downloaded browser assets artifact locally, set STILLWIND_PLAY_TEST_DIRECTORY to its extracted directory and run vp test --run from e2e/play. This runs the downloaded build without rebuilding it.

Cloudflare setup

  1. Activate the stillwind.dev zone in the target Cloudflare account and choose its workers.dev subdomain. GitHub Actions manages builds and deployments; leave Workers Builds disconnected from this repository.
  2. Create a token scoped to that account. Grant Workers Admin at the product scope for initial Worker creation, plus Workers Routes Write for the stillwind.dev zone. After provisioning, Workers Editor is sufficient when deployments keep the existing Worker and domain connections. Deleting a Preview requires Workers Admin, so create a second token with Admin scoped to stillwind-website and stillwind-play for Preview cleanup.
  3. Record the account ID. The first Preview deployment initializes each Worker without production routes, because Custom Domains require an existing deployment. It attaches preview.stillwind.dev and play-preview.stillwind.dev for Preview traffic only. Cloudflare provisions wildcard DNS records and certificates. Later Preview deployments preserve the existing production deployment. The first deployment from stable attaches the production Custom Domains.
  4. Verify HTTPS on both main Preview URLs after certificate issuance. Preview builds explicitly send X-Robots-Tag: noindex on custom domains.

See Workers permissions and Preview Custom Domains.

GitHub setup

  1. Enable GitHub Actions and allow the actions pinned in sites.yml.
  2. Under Settings → Environments, create sites-preview, sites-pr-preview, sites-pr-preview-cleanup, and sites-production. For each environment, set Deployment branches and tags to Selected branches and tags. Add a Branch rule for main in sites-preview, sites-pr-preview, and sites-pr-preview-cleanup, and a Branch rule for stable in sites-production. Create the preview and preview:active labels.
  3. Add CLOUDFLARE_API_TOKEN under Environment secrets in each environment. sites-pr-preview-cleanup uses the cleanup token. The other environments can initially use the first token created above. After provisioning, sites-pr-preview must use a Workers Editor token, because its job handles pull request artifacts. Add CLOUDFLARE_ACCOUNT_ID as a repository variable under Settings → Secrets and variables → Actions. Add repository variables SITES_PREVIEW_WEBSITE_URL and SITES_PREVIEW_PLAYGROUND_URL with the main Preview URL of each Worker. These must be absolute HTTPS origins. They are https://main.preview.stillwind.dev/ and https://main.play-preview.stillwind.dev/. Remove any repository secret with the same token name after configuring both environments, so deployments require the environment secret.
  4. Require the Build and verify check on pull requests to main and stable and disable force pushes on stable.
  5. Merge the workflow into main to deploy Preview. Create stable from the reviewed commit and push it to deploy production. Later runs can also be started from Actions → Sites → Run workflow, selecting main or stable.

Both environments deploy automatically after their checks pass. GitHub Pro supports environments, environment secrets, and deployment branch rules for private repositories. Required reviewers and wait timers on that plan require a public repository. See Managing environments.

For local verification, set PUBLIC_WEBSITE_URL and PUBLIC_PLAYGROUND_URL to the intended origins and set SITE_DEPLOYMENT to preview or production, then run vp run verify:sites. Both URLs default to the production origins. Use localhost origins to test navigation between local development servers. Production output also supports vp run @stillwind-internal/tools-sites#check:production, which performs a cf deploy --prebuilt --dry-run without credentials. To deploy prepared output locally, set CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN, then run vp run @stillwind-internal/tools-sites#deploy -- website main or select play and stable as needed. The branch argument must match the prepared mode.

To roll back a site, select the previous version in that Worker's Cloudflare Deployments dashboard. Each Worker is deployed and rolled back separately. Repository transfers also require reviewing Actions policies and token access in the destination organization.

Package versions

All publishable packages share one version until 1.0.0. In 0.x releases, breaking changes use minor and other changes use patch. versioning.maxBump: minor stops an accidental major bump, so remove that setting when you release 1.0.0.

After 1.0.0, stillwind, @stillwind/native and its platform packages, @stillwind/config, @stillwind/projection, and @stillwind/host stay in the fixed group. @stillwind/integration, @stillwind/analysis, @stillwind/cli, and @stillwind/language-server also stay there until they need separate releases.

@stillwind/plugin-iconify, prettier-plugin-stillwind, and @stillwind/vscode-extension can leave the fixed group when two conditions hold: the public APIs they depend on are stable, and they need releases apart from the other packages. A package that leaves the group keeps its current version and declares the supported stillwind versions in peerDependencies.

Repository layout

Directory Responsibility
crates/ Compiler, CSS design systems, source models, project sessions, and bindings
packages/ Public application APIs, build integrations, host services, and editor tooling
plugins/ Optional utility plugins
apps/ Website, playground, fixture viewer, and design tools
e2e/ Browser acceptance across frameworks and bundlers
fixtures/ Reviewed compiler output and Tailwind compatibility cases
benchmarks/ Explicit performance measurements and budgets
tools/ Generated data, fixtures, and shared task configuration

Publishable packages use stillwind, @stillwind/*, or prettier-plugin-stillwind. Private applications, acceptance fixtures, and tools use @stillwind-internal/*. The root package is @stillwind-internal/root.

README files describe package usage. Internal README files state their role and link to the relevant contracts. Keep repository workflows in this document, package-specific development details in DEVELOPMENT.md, and shared contracts in docs/. Record unresolved design and validation work in docs/TODO.md.

Contracts and development guides

Document Responsibility
Architecture Crate ownership, service entrypoints, diagnostics, and the Node binding
Macros Accepted inputs, purity, composition, refs, animations, and custom properties
CSS ordering Declaration, module, and delivery precedence
CSS emission Linking, output slots, explicit values, and minification
Generated identifiers Identity, naming callbacks, escaping, and collisions
Runtime Lowering and framework materialization
Analysis Configuration, analysis services, lint rules, and editor contracts
SFCs Projection, checker facts, and source coordinates
Host services Module ownership and editor preview lifetimes
Build integrations Adapter lifetimes, transport, and verification
VS Code extension Desktop/Web bundles and VSIX packaging
Browser acceptance Fixture execution, manifests, and artifacts
Benchmarks Workloads and measurement boundaries

Private applications document their local workflows beside their sources: Website, Playground, and Shape Lab.