From 5cc2c6c350010b6070ca3719e7af9a1afc7cf4bf Mon Sep 17 00:00:00 2001 From: jrtxio Date: Wed, 14 Oct 2026 15:35:25 +0800 Subject: [PATCH 1/3] Adopt the glaze native transport on macOS; dual-transport bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run-app now starts with #:transport 'native on macOS (HACKDIGEST_TRANSPORT overrides; other platforms keep the HTTP adapter until their native runs are verified end to end). The page loads from glaze:// and every call rides the GLZ1 message handler — no TCP port, no token, no loopback API. bridge.ts speaks both transports behind the same interface: - apiPost/hasBackend detect window.__glz1 and route through GLZ1 invoke; without it the old relative-fetch path runs unchanged (vite dev, plain-browser). - streamChat/streamLines keep the backend's line protocol (first line OK or ERR , then raw text / ndjson lines) on both transports — the native channel delivers the handler's bytes verbatim as chunk frames, so one parser serves both. Parser errors cannot throw out of onDelta (the native bootstrap swallows callback exceptions), so LineStreamParser records them and the caller raises after settle. - AbortSignal cancels through glaze.invokeStream's signal support, which rejects immediately and closes the stream server side via the cancel op. Also verified on the machine: the app opens from glaze://, loads real data through the native channel, and survives the React startup burst of invokes with a clean log. Tests: 12 new bridge cases cover both transports — invoke value/error pass-through, per-chunk onDelta, ERR-line handling, ndjson lines split across chunks, and the missing-status-line error. --- backend/main.rkt | 31 ++-- packages/core/src/bridge.ts | 270 ++++++++++++++++++++---------- packages/core/test/bridge.test.ts | 184 ++++++++++++++++++++ 3 files changed, 389 insertions(+), 96 deletions(-) create mode 100644 packages/core/test/bridge.test.ts diff --git a/backend/main.rkt b/backend/main.rkt index 18f8166..3c9e48d 100644 --- a/backend/main.rkt +++ b/backend/main.rkt @@ -126,44 +126,44 @@ (hasheq 'ok #t 'version app-version)] [(POST "api/kv/get") - (kv-get-r [key string?]) + (kv-get-r [key GString]) (hasheq 'value (nullable (kv-get (current-db) key)))] [(POST "api/kv/set") - (kv-set-r [key string?] [value string?]) + (kv-set-r [key GString] [value GString]) (kv-set! (current-db) key value) (hasheq 'ok #t)] [(POST "api/translations/get") - (translations-get [itemId exact-integer?] [lang string?]) + (translations-get [itemId GInt64] [lang GString]) (define-values (title text) (translation-get (current-db) itemId lang)) (hasheq 'found (if (or title text) #t 'null) 'title (nullable title) 'text (nullable text))] [(POST "api/translations/get-batch") - (translations-get-batch-r [itemIds (listof exact-integer?)] [lang string?]) + (translations-get-batch-r [itemIds (glist GInt64)] [lang GString]) (hasheq 'rows (for/list ([row (in-list (translations-get-batch (current-db) itemIds lang))]) (match-define (list id title text) row) (list id (nullable title) (nullable text))))] [(POST "api/translations/put") - (translations-put-r [rows list?]) + (translations-put-r [rows (glist (glist GValue))]) (translations-put! (current-db) (jsexpr->trans-rows rows)) (hasheq 'ok #t)] [(POST "api/items/get") - (items-get [id exact-integer?]) + (items-get [id GInt64]) (hasheq 'json (nullable (item-get (current-db) id)))] [(POST "api/items/put") - (items-put-r [items list?]) + (items-put-r [items (glist (glist GValue))]) (items-put! (current-db) (jsexpr->items items)) (hasheq 'ok #t)] [(POST "api/data/read") - (data-read [name string?]) + (data-read [name GString]) (define raw (read-data-file name)) (define masked-json (if (and (string=? name "settings") raw) @@ -175,7 +175,7 @@ (hasheq 'json (nullable masked-json))] [(POST "api/data/write") - (data-write [name string?] [json string?]) + (data-write [name GString] [json GString]) ;; A masked key on the write path echoes the masked read — swap the ;; stored key back so the page can never clear it by saving untouched. (write-data-file @@ -381,12 +381,21 @@ (seed-rollout-bucket!) (surface-install-failure!) + ;; Transport: native (the page loads from glaze:// and every call rides + ;; the GLZ1 message handler — no TCP port) is the production shape on + ;; macOS; other platforms and explicit dev runs opt into HTTP with + ;; HACKDIGEST_TRANSPORT=http. GLAZE_API_TOKEN pins a fixed token for + ;; dev/test automation on the HTTP transport; the native transport has + ;; no token because the message handler is the trust boundary. + (define transport + (or (let ([v (getenv "HACKDIGEST_TRANSPORT")]) + (and (member v '("native" "http")) (string->symbol v))) + (if (eq? (system-type 'os) 'macosx) 'native 'http))) (run-app #:public-dir frontend-dist #:api api #:events bus - ;; GLAZE_API_TOKEN pins a fixed token for dev/test automation; default - ;; generates a random one exchanged for an HttpOnly cookie at boot. + #:transport transport #:api-token (or (getenv "GLAZE_API_TOKEN") #t) #:title "HackDigest" #:width 1160 diff --git a/packages/core/src/bridge.ts b/packages/core/src/bridge.ts index c9973ae..c1f37b1 100644 --- a/packages/core/src/bridge.ts +++ b/packages/core/src/bridge.ts @@ -1,24 +1,65 @@ -// Bridge to the Glaze Racket backend (replaces the Tauri IPC layer). -// The page is served by the same local server, so plain relative fetch -// reaches /api/*; the api-token rides an HttpOnly cookie set by glaze's -// one-time bootstrap. With no backend (plain-browser dev via vite), the -// probe fails and callers fall back to IndexedDB / window.open, matching -// the old isTauri() branches. +// Bridge to the Glaze Racket backend. +// +// Two transports behind one interface: +// - native: the page runs on the controlled glaze:// origin; calls ride +// the GLZ1 message handler (window.__glz1, injected at document start +// by the native bootstrap). No TCP port exists in production. +// - http: the page is served by the loopback server (vite dev, the HTTP +// adapter) and talks plain relative fetch; /api/health probes for the +// backend, and without one callers fall back to IndexedDB / +// window.open, matching the old isTauri() branches. +// +// Streaming routes keep their line protocol on both transports (first +// line `OK` or `ERR `, then raw text or ndjson lines): +// the native channel delivers the handler's bytes verbatim as chunk +// frames, so one parser serves both. + +type OnDelta = (text: string) => void; + +interface Glz1 { + invoke( + path: string, + args?: unknown, + opts?: { method?: string; timeoutMs?: number } + ): Promise; + invokeStream( + path: string, + args?: unknown, + opts?: { method?: string; onDelta?: OnDelta; signal?: AbortSignal } + ): Promise; +} + +declare global { + interface Window { + __glz1?: Glz1; + } +} + +function nativeBridge(): Glz1 | null { + return (typeof window !== 'undefined' && window.__glz1) || null; +} let probe: Promise | null = null; -/** True once the Glaze backend answers /api/health. Memoized per session. */ +/** True once a backend answers — native bridge present, or /api/health ok. Memoized per session. */ export function hasBackend(): Promise { if (!probe) { - probe = fetch('/api/health', { method: 'GET' }) - .then((r) => r.ok) - .catch(() => false); + const g = nativeBridge(); + probe = g + ? Promise.resolve(true) + : fetch('/api/health', { method: 'GET' }) + .then((r) => r.ok) + .catch(() => false); } return probe; } /** POST JSON to the backend; returns the parsed response or null (JSON null). */ export async function apiPost(path: string, body?: unknown): Promise { + const g = nativeBridge(); + if (g) { + return (await g.invoke(path, body ?? {}, { method: 'POST' })) as T; + } const res = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -31,16 +72,106 @@ export async function apiPost(path: string, body?: unknown): Promise { return json as T; } -/** - * Streamed chat: first line is `OK` or `ERR `, then raw - * text deltas. Passes each delta to onDelta as it arrives and returns the - * full text. The AbortSignal cancels the fetch (the backend sees the socket - * drop and stops writing). - */ -export async function streamChat( +// The streaming line protocol shared by both transports. Errors cannot +// throw out of the onDelta callback (the native bootstrap swallows +// callback exceptions by design), so the parser records the error and the +// caller raises it after the stream settles. +class LineStreamParser { + private buf = ''; + private statusDone = false; + error: Error | null = null; + + constructor( + private readonly mode: 'text' | 'lines', + private readonly onItem: OnDelta + ) {} + + push(chunk: string): void { + if (this.error) return; + this.buf += chunk; + if (!this.statusDone) { + const nl = this.buf.indexOf('\n'); + if (nl === -1) return; + const status = this.buf.slice(0, nl); + this.buf = this.buf.slice(nl + 1); + this.statusDone = true; + if (status.startsWith('ERR')) { + const msg = status.slice(3).trim(); + const m = msg.match(/^(\d+)\s?(.*)$/); + this.error = new Error(m && m[2] ? m[2] : msg || 'request failed'); + return; + } + } + if (this.mode === 'text') { + if (this.buf) { + this.onItem(this.buf); + this.buf = ''; + } + return; + } + for (;;) { + const nl = this.buf.indexOf('\n'); + if (nl === -1) break; + const line = this.buf.slice(0, nl).trim(); + this.buf = this.buf.slice(nl + 1); + if (line) this.onItem(line); + } + } + + /** Data held back past the last chunk; throws if no status line ever arrived. */ + finish(): string { + if (!this.statusDone) throw new Error('stream ended before status line'); + const tail = this.buf; + this.buf = ''; + return tail; + } +} + +function deliverTail(p: LineStreamParser, mode: 'text' | 'lines', onItem: OnDelta): void { + const tail = p.finish(); + const last = mode === 'text' ? tail : tail.trim(); + if (last) onItem(last); +} + +// Native transport: chunk frames carry the handler's bytes verbatim; the +// parser reconstructs the same line protocol the HTTP body has. +async function invokeNativeStream( + path: string, + body: unknown, + mode: 'text' | 'lines', + onItem: OnDelta, + signal?: AbortSignal +): Promise { + const g = nativeBridge(); + if (!g) throw new Error('native bridge unavailable'); + const items: string[] = []; + const p = new LineStreamParser(mode, (t) => { + items.push(t); + onItem(t); + }); + try { + await g.invokeStream(path, body, { + method: 'POST', + onDelta: (d) => p.push(d), + signal, + }); + deliverTail(p, mode, (t) => { + items.push(t); + onItem(t); + }); + if (p.error) throw p.error; + return items.join(''); + } catch (e) { + throw p.error ?? e; + } +} + +// HTTP transport: a fetch reader feeds the same parser with body chunks. +async function fetchStream( path: string, body: unknown, - onDelta: (text: string) => void, + mode: 'text' | 'lines', + onItem: OnDelta, signal?: AbortSignal ): Promise { const res = await fetch(path, { @@ -53,39 +184,43 @@ export async function streamChat( const detail = await res.text().catch(() => ''); throw new Error(`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ''}`); } + const items: string[] = []; + const p = new LineStreamParser(mode, (t) => { + items.push(t); + onItem(t); + }); const reader = res.body.getReader(); const decoder = new TextDecoder(); - let buf = ''; - let full = ''; - let statusDone = false; for (;;) { const { done, value } = await reader.read(); if (done) break; - buf += decoder.decode(value, { stream: true }); - if (!statusDone) { - const nl = buf.indexOf('\n'); - if (nl === -1) continue; - const status = buf.slice(0, nl); - buf = buf.slice(nl + 1); - statusDone = true; - if (status.startsWith('ERR')) { - const msg = status.slice(3).trim(); - const m = msg.match(/^(\d+)\s?(.*)$/); - throw new Error(m && m[2] ? m[2] : msg || 'LLM request failed'); - } - } - if (buf) { - full += buf; - onDelta(buf); - buf = ''; - } + p.push(decoder.decode(value, { stream: true })); + if (p.error) throw p.error; } - if (buf) { - full += buf; - onDelta(buf); - } - if (!statusDone) throw new Error('stream ended before status line'); - return full; + deliverTail(p, mode, (t) => { + items.push(t); + onItem(t); + }); + if (p.error) throw p.error; + return items.join(''); +} + +/** + * Streamed chat: first line is `OK` or `ERR `, then raw + * text deltas. Passes each delta to onDelta as it arrives and returns the + * full text. The AbortSignal cancels the stream (the backend sees the + * stream cancelled and stops writing). + */ +export function streamChat( + path: string, + body: unknown, + onDelta: OnDelta, + signal?: AbortSignal +): Promise { + const g = nativeBridge(); + return g + ? invokeNativeStream(path, body, 'text', onDelta, signal) + : fetchStream(path, body, 'text', onDelta, signal); } /** @@ -99,45 +234,10 @@ export async function streamLines( onLine: (line: string) => void, signal?: AbortSignal ): Promise { - const res = await fetch(path, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - signal, - }); - if (!res.ok || !res.body) { - const detail = await res.text().catch(() => ''); - throw new Error(`${res.status} ${res.statusText}${detail ? ` — ${detail}` : ''}`); - } - const reader = res.body.getReader(); - const decoder = new TextDecoder(); - let buf = ''; - let statusDone = false; - for (;;) { - const { done, value } = await reader.read(); - if (done) break; - buf += decoder.decode(value, { stream: true }); - for (;;) { - if (!statusDone) { - const nl = buf.indexOf('\n'); - if (nl === -1) break; - const status = buf.slice(0, nl); - buf = buf.slice(nl + 1); - statusDone = true; - if (status.startsWith('ERR')) { - const msg = status.slice(3).trim(); - const m = msg.match(/^(\d+)\s?(.*)$/); - throw new Error(m && m[2] ? m[2] : msg || 'request failed'); - } - continue; - } - const nl = buf.indexOf('\n'); - if (nl === -1) break; - const line = buf.slice(0, nl).trim(); - buf = buf.slice(nl + 1); - if (line) onLine(line); - } + const g = nativeBridge(); + if (g) { + await invokeNativeStream(path, body, 'lines', onLine, signal); + return; } - if (buf.trim() && statusDone) onLine(buf.trim()); - if (!statusDone) throw new Error('stream ended before status line'); + await fetchStream(path, body, 'lines', onLine, signal); } diff --git a/packages/core/test/bridge.test.ts b/packages/core/test/bridge.test.ts new file mode 100644 index 0000000..66ee281 --- /dev/null +++ b/packages/core/test/bridge.test.ts @@ -0,0 +1,184 @@ +// Bridge dual-transport tests: the native (__glz1) path and the plain +// fetch path must expose the same semantics — apiPost returns the value, +// streamChat/streamLines parse the OK/ERR line protocol, and an ERR line +// throws the backend's message on both transports. +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { apiPost, hasBackend, streamChat, streamLines } from '../src/bridge'; + +// A __glz1 double: invokes resolve with the handler's value; streams run +// the handler (which fires onDelta per chunk) and resolve with its return +// value — the same shape the real channel gives the bridge. +function nativeBridge(handlers: { + invoke?: (path: string, args: unknown) => unknown; + stream?: (path: string, args: unknown, onDelta: (t: string) => void) => string; +}) { + (window as unknown as { __glz1?: unknown }).__glz1 = { + invoke: (path: string, args: unknown) => Promise.resolve(handlers.invoke?.(path, args)), + invokeStream: (path: string, args: unknown, opts?: { onDelta?: (t: string) => void }) => { + const full = handlers.stream?.(path, args, (t) => opts?.onDelta?.(t)) ?? ''; + return Promise.resolve(full); + }, + }; +} + +describe('bridge: http transport', () => { + beforeEach(() => { + delete (window as unknown as { __glz1?: unknown }).__glz1; + }); + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('hasBackend probes /api/health', async () => { + vi.stubGlobal('fetch', vi.fn(async () => ({ ok: true }))); + expect(await hasBackend()).toBe(true); + expect(vi.mocked(fetch).mock.calls[0][0]).toBe('/api/health'); + }); + + it('apiPost throws the backend error message', async () => { + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + json: async () => ({ error: 'boom' }), + })) + ); + await expect(apiPost('/api/x', {})).rejects.toThrow('boom'); + }); + + it('streamChat fires onDelta across fetch chunks and returns the full text', async () => { + const chunks = [new TextEncoder().encode('OK\nHel'), new TextEncoder().encode('lo')]; + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: true, + body: { + getReader: () => ({ + read: async () => + chunks.length ? { done: false, value: chunks.shift()! } : { done: true, value: undefined }, + }), + }, + })) + ); + const deltas: string[] = []; + const full = await streamChat('/api/llm/chat/stream', {}, (t) => deltas.push(t)); + expect(full).toBe('Hello'); + expect(deltas.join('')).toBe('Hello'); + }); + + it('streamChat throws the ERR-line message on the http path', async () => { + const chunks = [new TextEncoder().encode('ERR 500 key bad\n')]; + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: true, + body: { + getReader: () => ({ + read: async () => + chunks.length ? { done: false, value: chunks.shift()! } : { done: true, value: undefined }, + }), + }, + })) + ); + await expect(streamChat('/api/llm/chat/stream', {}, () => {})).rejects.toThrow('key bad'); + }); + + it('streamLines fires per line even when lines split across chunks', async () => { + const chunks = [ + new TextEncoder().encode('OK\n{"a"'), + new TextEncoder().encode(':1}\n{"a":2}'), + new TextEncoder().encode('\n'), + ]; + vi.stubGlobal( + 'fetch', + vi.fn(async () => ({ + ok: true, + body: { + getReader: () => ({ + read: async () => + chunks.length ? { done: false, value: chunks.shift()! } : { done: true, value: undefined }, + }), + }, + })) + ); + const lines: string[] = []; + await streamLines('/api/translate/story', {}, (l) => lines.push(l)); + expect(lines).toEqual(['{"a":1}', '{"a":2}']); + }); +}); + +describe('bridge: native transport', () => { + beforeEach(() => { + vi.unstubAllGlobals(); + }); + afterEach(() => { + delete (window as unknown as { __glz1?: unknown }).__glz1; + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('hasBackend is true when __glz1 is present', async () => { + nativeBridge({}); + expect(await hasBackend()).toBe(true); + }); + + it('apiPost resolves the invoke value', async () => { + nativeBridge({ invoke: () => ({ pong: true }) }); + expect(await apiPost<{ pong: boolean }>('/api/kv/get', { key: 'x' })).toEqual({ pong: true }); + }); + + it('apiPost propagates backend errors as Error', async () => { + nativeBridge({ + invoke: () => { + throw new Error('not found'); + }, + }); + await expect(apiPost('/api/nope', {})).rejects.toThrow('not found'); + }); + + it('streamChat fires onDelta per chunk and returns the full text', async () => { + const deltas: string[] = []; + nativeBridge({ + stream: (_p, _a, onDelta) => { + onDelta('OK\n'); + onDelta('Hel'); + onDelta('lo'); + return 'OK\nHello'; + }, + }); + const full = await streamChat('/api/llm/chat/stream', {}, (t) => deltas.push(t)); + expect(full).toBe('Hello'); + expect(deltas.join('')).toBe('Hello'); + }); + + it('streamChat throws the ERR-line message on the native path', async () => { + nativeBridge({ + stream: (_p, _a, onDelta) => { + onDelta('ERR 429 rate limited\n'); + return 'ERR 429 rate limited\n'; + }, + }); + await expect(streamChat('/api/llm/chat/stream', {}, () => {})).rejects.toThrow('rate limited'); + }); + + it('streamLines fires per complete line on the native path', async () => { + const lines: string[] = []; + nativeBridge({ + stream: (_p, _a, onDelta) => { + onDelta('OK\n{"a":1}\n{"a":2}\n'); + return 'OK\n{"a":1}\n{"a":2}\n'; + }, + }); + await streamLines('/api/translate/story', {}, (l) => lines.push(l)); + expect(lines).toEqual(['{"a":1}', '{"a":2}']); + }); + + it('streamLines throws when the stream ends before a status line', async () => { + nativeBridge({ stream: () => 'no status here' }); + await expect(streamLines('/api/x', {}, () => {})).rejects.toThrow('status line'); + }); +}); + From 93fcb6a72057721ef2841cf9c8285f3e38a66d7a Mon Sep 17 00:00:00 2001 From: jrtxio Date: Wed, 14 Oct 2026 15:40:16 +0800 Subject: [PATCH 2/3] ci: pin glaze to the glz1-streaming branch until #63 merges The native adoption needs GLZ1 streaming, and the typed-descriptor re-exports landed on glaze main after v0.14.0. Revert to a version pin once glaze#63 merges and releases. --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34e2ae4..35c50fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: version: stable - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#v0.14.0 + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fd9182a..5df5133 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: distribution: full - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#v0.14.0 + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) @@ -240,7 +240,7 @@ jobs: distribution: full - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#v0.14.0 + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) @@ -661,7 +661,7 @@ jobs: distribution: full - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#v0.14.0 + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) @@ -831,7 +831,7 @@ jobs: - name: Install glaze and crypto run: | - raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#v0.14.0 crypto + raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming crypto - uses: actions/download-artifact@v4 with: From 640a3780dee8de2a3c5632267cb95fafb712cc14 Mon Sep 17 00:00:00 2001 From: jrtxio Date: Wed, 14 Oct 2026 16:07:51 +0800 Subject: [PATCH 3/3] ci: point the glaze pin at main now that #63 merged --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35c50fc..1ab6199 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: version: stable - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#main # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5df5133..a92efb3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -82,7 +82,7 @@ jobs: distribution: full - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#main # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) @@ -240,7 +240,7 @@ jobs: distribution: full - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#main # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) @@ -661,7 +661,7 @@ jobs: distribution: full - name: Install glaze - run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming + run: raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#main # the updater verifies manifests through the crypto package (libcrypto # FFI bundled with the Racket distribution) @@ -831,7 +831,7 @@ jobs: - name: Install glaze and crypto run: | - raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#glz1-streaming crypto + raco pkg install --auto --no-docs --skip-installed git+https://github.com/turinglambdaai/glaze.git#main crypto - uses: actions/download-artifact@v4 with: