Skip to content

Development

Joël Deffner edited this page Sep 1, 2026 · 2 revisions

Development

Working on steamwand itself. For using the published package, start at Getting Started.

Setup

pnpm install

Node 18 or newer, pnpm 10. There is no native build step: the only runtime dependency is koffi, and the steam_api redistributables are committed under runtime/.

Repo layout

scripts/
  generate.ts        binding generator (steam_api.json -> src/generated)
  smoke.ts           read-only live smoke, 30 checks over 10 interfaces
  workbench.ts       local web UI over the whole binding
  workbench.html     its single-page front end
src/
  index.ts           init(), the Steam class, the public exports
  api/
    errors.ts        SteamResultError, SteamInitError, eResultName
    guards.ts        the ok() and must() throw helpers the layers share
    workshop.ts      the curated workshop layer
    stats.ts         achievements and per-user stats
    cloud.ts         Steam Cloud files
    leaderboards.ts  find or create, upload, download
    lobbies.ts       lobby create/join/data/chat/search
  runtime/
    native.ts        koffi library load, core exports, lazy func() cache
    dispatch.ts      manual dispatch pump, call results, SteamApiCallError
    platform.ts      platform detection, lib path, callback pack
    struct.ts        offset-table struct decoding
    types.ts         SteamParamStringArray_t and stringArray()
  generated/         GENERATED, committed, never hand-edited
runtime/
  win64/steam_api64.dll  linux64/libsteam_api.so  osx/libsteam_api.dylib
test/
  offsets.test.ts            offline layout regression
  live/workshop.live.test.ts full workshop round trip
  live/curated.live.test.ts  stats, cloud, leaderboards, lobbies
sdk.lock.json        which SDK the generated output came from

Handwritten code is everything under src/ except src/generated/. To change generated output, change scripts/generate.ts and regenerate: see Regenerating.

sdk/ and steamworks_sdk_*.zip are gitignored. The SDK must never be committed.

Scripts

Script Command Needs
pnpm build tsc -p tsconfig.build.json nothing
pnpm typecheck tsc --noEmit nothing
pnpm test vitest run nothing
pnpm generate tsx scripts/generate.ts a local SDK at ./sdk or $STEAMWORKS_SDK
pnpm smoke tsx scripts/smoke.ts a running, logged-in Steam client
pnpm test:live cross-env STEAM_LIVE=1 vitest run test/live a running, logged-in Steam client
pnpm workbench tsx scripts/workbench.ts a running, logged-in Steam client

tsconfig.json covers src, scripts, and test, so pnpm typecheck sees everything. tsconfig.build.json narrows the input to src and emits dist/ with declarations. The published package ships dist, runtime, README.md, and LICENSE.

Tests

pnpm test

test/offsets.test.ts is the offline regression net. It pins both the win64 and posix offsets of the workshop struct set and the five workshop callback ids. It needs no Steam client and runs in CI on all three platforms. If an SDK bump moves an offset, this is what tells you.

pnpm test:live

Both live files are guarded by describe.skipIf(!process.env.STEAM_LIVE), so pnpm test walks over them silently. pnpm test:live sets STEAM_LIVE=1 and runs both on Spacewar (appid 480).

test/live/workshop.live.test.ts is the full workshop round trip: init, create a private throwaway item, upload content and default-language text, submit a German translation through SetItemUpdateLanguage, query both languages back, check the item appears in the user's item list, then delete it. afterAll deletes the item even when a test fails, so it does not leave workshop garbage behind. The upload steps have 120 second timeouts.

test/live/curated.live.test.ts covers the other four layers: the achievement schema and display text, the current player count and the global percentages, one cloud file written, read back, listed and deleted, the quota reads, a leaderboard lookup that accepts either the found or the null path, and one lobby created, written to, chatted in and left. It touches nothing durable: one temporary cloud file and one private throwaway lobby, both cleaned up, and it neither unlocks an achievement nor uploads a score.

pnpm smoke

Read-only. One call per argument and return shape (string, bool, float, uint64, out buffer, call result, struct decode) across 10 interfaces. It prints PASS or FAIL per check and exits non-zero on any failure. Use it to confirm a regenerated binding still talks to Steam. Note that a few checks assert against the machine's own library (an installed Crusader Kings III, a known workshop item), so a fresh machine can report failures that are not steamwand's fault.

Keep tests focused. The offset tests exist because a silent offset shift would decode garbage; do not add broad snapshot tests over generated output.

Workbench

pnpm workbench

Then open http://localhost:4879. The server binds 127.0.0.1 only. It is a manual test bench over the whole binding: initialize under any app id, list every generated interface and its methods, call any method with typed arguments ("123n" becomes a bigint, a string array becomes a SteamParamStringArray_t, { "buf": 260 } becomes an out buffer), await a call result and decode it with any layout, watch any callback by struct name, and drive the curated workshop layer.

The workshop panel performs real, destructive actions. createItem creates a real workshop item on your account, submitUpdate uploads and publishes, and deleteItem deletes permanently. Use appid 480 and private visibility while testing.

One app id per process: after close, restart the workbench rather than initializing again.

CI

.github/workflows/ci.yml runs on pushes to main and on every pull request. Matrix: windows-latest, ubuntu-latest, macos-latest, with fail-fast: false, pnpm 10, Node 22, pnpm install --frozen-lockfile. Then:

pnpm typecheck
pnpm test
pnpm build

Live tests are deliberately not in CI: they need a logged-in Steam client, which a runner does not have. The three-platform matrix is what keeps the posix layouts honest.

Release

Publishing is tag-driven. .github/workflows/publish.yml triggers on tags matching v*.

  1. Bump version in package.json and commit it.

  2. Tag and push:

    git tag v0.1.1 && git push origin v0.1.1

The workflow runs on ubuntu-latest with Node 24, does pnpm install --frozen-lockfile, pnpm typecheck, pnpm test, pnpm build, then npm publish --access public. It uses npm OIDC trusted publishing (permissions: id-token: write), so there is no NPM_TOKEN secret to rotate. The package name is steamwand.js.

Editing the wiki

These pages live in the wiki repository, not in the main repository. Clone it and edit the files directly:

git clone https://github.com/JDeffner/steamwand.js.wiki.git

One file is one page. Home.md is the landing page, _Sidebar.md is the navigation. Links between pages use the page name with no extension, for example [Core API](Core-API). Commit and push, and the change is live.

Clone this wiki locally