Skip to content

weft v0.11.0 — phase 2 of the devtools plan: the weft command, weft dev, discovery, doctor

Choose a tag to compare

@wajihtba wajihtba released this 08 Oct 15:11
· 323 commits to main since this release

weft v0.11.0

Phase 2 of the devtools plan (start and find): the weft command,
weft dev, the port policy, discovery, the stable dev token, the
Agents page from runtime registrations, weft doctor, and the panel's
configuration ladder. Tags core/v0.11.0 (the weft.version literal
only; core's API is unchanged since 0.10.1) and v0.11.0 — a minor
bump, as studio/cmd is removed (Changed — breaking).

Added

  • Agents from runtime registrations (plan B4): Studio remembers
    each runtime's registration manifest by (service, manifest hash), in
    memory; /api/manifest serves it when no weft.json is configured
    (the file still wins) and lists every source in sources[] with
    live; two services registering one agent name in different versions
    both appear. /api/meta gains manifest_sources, capabilities_off
    (why an option left a capability off) and manifest_check.source /
    differs; the Agents page, playground and debugger empty states show
    the reason.

  • The weft command (plan B1, cmd/weft): go install github.com/weftgo/weft/cmd/weft@latest yields a weft binary.
    weft studio [--addr] [--db] [--token] [--manifest] [--open] [--no-playground] is setup B (what studio/cmd served) with the
    playground on (studio.Playground(true), inert until an app's
    runtime connects; --no-playground turns it off), the manifest from
    --manifest / WEFT_MANIFEST else the nearest weft.json from the
    working directory upward (one line says which, or that none was
    found), and --open (default on when stdout is a terminal) opening
    the UI with the token in the URL fragment. The Studio API over a
    terminal: weft runs [--agent] [--since] [--failed] [--limit] [--json] (GET /api/runs; --limit 50 by default, and a limit
    that hid runs says so on stderr), weft open <run id> [--open] [--with-token] (prints the bare <url>/runs/<id>; the token goes
    only to the browser, or to stdout on --with-token), weft export <run id> [--format json|jsonl|otlp] to stdout and weft export <run id> --wefttest <dir> [--test name] [--force] (the wefttest fixtures
    unzipped into <dir>/<name>/, where wefttest.Replay(t, dir) reads
    them; a non-empty target needs --force, which replaces its *.json
    fixtures); weft doctor; weft version. The API clients never
    follow a redirect. The API clients take
    --url (WEFT_STUDIO_URL, default http://127.0.0.1:7331) and
    --token (WEFT_STUDIO_TOKEN). Exit codes: 0, 1 a failure, 2 a
    usage error.

  • weft dev (plan B1.2, FEATURES D7): weft dev [studio's flags] [--no-watch] [--watch dir] [-- command…] (default go run .)
    starts Studio in-process as weft studio does (same flags and port
    policy) and runs the app with WEFT_ENV=dev (kept when already set
    non-empty), WEFT_STUDIO_URL, WEFT_STUDIO_TOKEN and WEFT_DB (the
    Studio's SQLite file) set — plain variables the app can set itself.
    The app runs in its own process group and restarts on a .go save
    (fsnotify, 300 ms debounce; SIGTERM, 5 s, SIGKILL); a build failure
    waits for the next save; --no-watch exits with the app's code
    (127 when it cannot start); a directory arriving with .go files
    restarts too. Ctrl-C, SIGTERM and SIGHUP stop the app, then Studio
    (Linux adds Pdeathsig for go run; a SIGKILL of weft dev can
    still orphan the app's binary). Each start prints one line:
    studio <url>[#token=…] · app pid <n> · runtime rt_… registered
    (the token only when generated; "no runtime registered yet" after
    5 s, then a later line). Reuse follows the port policy: without a
    fixed token a running token-walled Studio is skipped for the next
    port (plan B3's stable token changes this). The
    discovery file is plan B3's. examples/studio-local runs under it:
    with WEFT_STUDIO_URL set it listens on 8080, keeps its local sink
    (otel.Local(""), explicit) and registers its runtime with that
    Studio.

  • make studio-bin builds ./weft from ./cmd/weft (ignored by
    git).

  • Studio's port policy (plan B2, internal/listen, wired into
    weft studio): 127.0.0.1:7331 is the one default. On a busy port
    the binary asks GET /api/meta there (bearer: --token /
    WEFT_STUDIO_TOKEN when set): a Studio on the same database file is
    reused — studio already running at http://127.0.0.1:7331 (pid 1234), reusing, exit 0, no database or listener opened — and anything else (another
    program, a Studio on another database, one whose meta this token
    cannot read) moves Studio to the next free port in 7331–7340 with
    one line naming the skipped address and why; the banner prints the
    real address. All ten busy is exit 1 naming the range. studio.New
    is unchanged: an embedded Studio is the app's own listener.

  • WEFT_STUDIO_ADDR, the --addr mirror. Either pins the
    address: busy is exit 1 with the address in the error — no probe, no
    fallback.

  • /api/meta pid: the serving process's id, under db.path's
    guard (loopback with no Token, or the server token; never a panel
    token).

  • weft doctor [--url URL] [--token TOK] (plan B5): checks a
    running Studio and prints one line per check — reachable, token
    accepted, the database's path and size, the content it stores,
    connected runtimes (and, when none, what this shell's WEFT_ENV and
    WEFT_STUDIO_URL say about why), the panel bundle's version, and
    whether weft.json is stale against the latest runs. Every line
    reads a field of GET /api/meta (internal/doctor.Lines is the
    table); the flags mirror WEFT_STUDIO_URL / WEFT_STUDIO_TOKEN. An
    unreachable Studio is the first line within a 5 s timeout and exit 1;
    a redirect is reported, never followed.

  • weft studio --manifest path (default $WEFT_MANIFEST; the flag
    wins): the app's weft.json, read once at start and served as
    studio.Manifest — /api/manifest and the doctor's weft.json check
    in setup B. An unreadable file is a start error.

  • /api/meta explains itself: content (Studio's ingest policy,
    "as_received", and the latest run's content mark — full,
    stripped, none or unmarked — with a note and the fix naming
    otel.NoContent() or weft.Content(false)), pricing (false
    until a pricing table exists), retention (null: none
    configured), runtimes (runtimes holding a command stream now),
    panel_version (the embedded /panel.js stamp) and
    manifest_check ({agents, checked, stale}: each manifest agent's
    hash against its latest run's weft.manifest.hash; null without a
    manifest or for a panel token), and auth_required (whether a
    studio.Token is configured). content.error and
    manifest_check.error (omitted when empty) report a read that
    failed or a manifest that does not parse — logged through the
    process's slog default too — instead of reading as an empty
    database.

  • obsdb/sqlite: (*DB).Path() — the database file's absolute
    path ("" for :memory:).

  • The discovery file (plan B3, internal/discovery): weft studio
    and weft dev write studio.json — {url, token, db, pid, started, version}, mode 0600, the bound port in it — to ./.weft/ when it
    exists, else $XDG_RUNTIME_DIR/weft/, else os.UserCacheDir()/weft/,
    and remove it on a clean exit. otel.Install/otel.Start and
    runtime.Install read it when WEFT_STUDIO_URL is unset: an app
    with defer otel.Install()() exports to the running Studio (and its
    runtime dials it) with no configuration, one INFO line naming the
    Studio joined. The file is trusted only when its url is loopback, it
    is fresh (pid alive, under 24 h) and, on unix, it is 0600 and the
    reading user's; anything else is ignored at Debug and removed by the
    next writer. A second Studio's exit restores the first's file; the
    writer drops .weft/.gitignore (*) when there is none. An explicit
    otel.Studio(...) or WEFT_STUDIO_URL switches the read off;
    WEFT_DISCOVERY=off turns it off; otel.NoEnv() ignores it with
    the rest of the environment.

  • A stable dev token per database: <db>.token beside the SQLite
    file (.weft/weft.db.token), 32 random bytes base64url, 0600,
    created on the first start and served by every later one, so the
    token survives a restart and a second bare start's probe reuses the
    running Studio (the probe sends it only to an address the user's
    discovery file names). --rotate-token (weft studio, weft dev;
    an action, no environment mirror) writes a new one.

  • GET <base>/panel-config.json (studio): {endpoint, version, capabilities} for the devtools panel, answered to a loopback (or
    AllowOrigins) Host and a same-origin, AllowOrigins or loopback
    Origin only (loopback by the Host's rule — localhost,
    *.localhost, 127.0.0.0/8, [::1] — even with AllowOrigins set) —
    a 404 otherwise; /api/meta lists the new panel-config capability.

  • The devtools panel's configuration ladder (plan C2): each field
    (endpoint, public-id, token, position, open, auto)
    resolves on its own through mount(opts) → the <weft-devtools>
    element's attributes → <meta name="weft:endpoint|public-id|token| position|open|auto"> → the panel's <script> tag (the running
    classic script, else the first with data-weft, any src) →
    panel-config.json beside the script (endpoint only, same origin
    only, never a token; asked only when nothing above named an endpoint)
    → the script's own directory. A <weft-devtools> element, a
    mount(opts) or data-auto="false" with no Studio answering shows
    one line, Studio not reachable at <endpoint> · retry; the dock the
    script mounted by itself still removes itself silently. The rung
    table is in studio/README.md.

Dependencies

  • github.com/fsnotify/fsnotify v1.9.0 (framework module only, for
    weft dev's watcher; core is untouched).

Changed

  • runtime/examples/local serves on 127.0.0.1:7331 (was 7391, for
    no documented reason): one default port everywhere.
  • The dev token is stable per database and no longer printed by
    default
    : weft studio's banner and weft dev's one line print the
    bare URL and name the token file (the stable token is the panel
    tokens' signing key, like a fixed one); --open still hands the
    browser the link with the token, weft open --with-token prints it,
    and weft dev's app still gets WEFT_STUDIO_TOKEN. Only a database
    with no file (:memory:, ClickHouse) gets a per-process token,
    printed as before. --token / WEFT_STUDIO_TOKEN override as
    before and never touch the file.

Changed — breaking

  • The panel no longer finds its script tag by file name (the
    panel(-vX)?.js pattern is gone). A tag carrying none of the panel's
    data-* attributes — the bare <script type="module" src="/studio/panel.js"> that relied on window.__WEFT__, or a
    renamed or proxied bundle — is no longer found: its endpoint falls to
    the page's own directory, Studio does not answer there, and the auto
    dock removes itself silently. Migration: add data-weft to the
    script tag (<script type="module" src="/studio/panel.js" data-weft>), or configure a higher rung (a weft:* meta tag, the
    element, mount(opts)). Tags with data-public-id, data-endpoint
    or another panel attribute keep working. A <weft-devtools> element
    in your markup with no Studio answering now shows the not-reachable
    line instead of an empty element.
  • studio/cmd is removed: the setup-B binary is weft studio
    (go install github.com/weftgo/weft/cmd/weft@latest), every flag
    kept. studio --version is weft version; studio doctor is
    weft doctor. Scripts that ran go run ./studio/cmd or
    go install github.com/weftgo/weft/studio/cmd@… run weft studio.
  • /api/meta's db is an object: {kind, path, size} instead of
    the kind string. Read db.kind where you read db. path and
    size (bytes, the WAL sidecar included) are served only to a
    loopback request with no studio.Token and to the server token —
    omitted for panel tokens and AllowOrigins hosts, and for a
    database with no file.

The panel release asset: panel-v0.11.0.js (sha256 beside it).