From b7b1176db17b52b570eeae43b2c086e33ff01813 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Tue, 31 Mar 2026 10:56:12 +0200 Subject: [PATCH 1/6] Support extra args for dev server's `--cmd` flag (PR 1680) Allow passing extra positional arguments that get appended to the -c command string. If the command contains {}, the args replace it instead (like `find -exec`). Original commit: 020219055c88c137bfa71e3a97a5fc06eec01118 --- tools/liveblocks-cli/CHANGELOG.md | 5 ++++ tools/liveblocks-cli/src/dev-server/index.ts | 30 ++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/tools/liveblocks-cli/CHANGELOG.md b/tools/liveblocks-cli/CHANGELOG.md index f6a921725fb..7b17940e21c 100644 --- a/tools/liveblocks-cli/CHANGELOG.md +++ b/tools/liveblocks-cli/CHANGELOG.md @@ -1,5 +1,10 @@ ## vNEXT (not yet released) +## v1.2.1 + +- Support passing extra arguments to `--cmd` (`-c`), appended to the command or + replacing `{}` if present + ## v1.2.0 - Add live socket inspector view diff --git a/tools/liveblocks-cli/src/dev-server/index.ts b/tools/liveblocks-cli/src/dev-server/index.ts index 7a316745930..643b2fe005f 100644 --- a/tools/liveblocks-cli/src/dev-server/index.ts +++ b/tools/liveblocks-cli/src/dev-server/index.ts @@ -111,6 +111,14 @@ function shellCmd(cmd: string): string[] { : ["sh", "-c", cmd]; } +/** + * Escapes a string for safe inclusion in a shell command. + * Wraps in single quotes, escaping any existing single quotes. + */ +function shellEscape(arg: string): string { + return "'" + arg.replace(/'/g, "'\\''") + "'"; +} + type Options = { port: string; host?: string; @@ -125,7 +133,7 @@ const dev: SubCommand = { description: "Start the local Liveblocks dev server", async run(argv) { - const { options } = parseArgs(argv, { + const { options, args } = parseArgs(argv, { port: { type: "string", short: "p", default: DEFAULT_PORT.toString() }, host: { type: "string" }, cmd: { type: "string", short: "c" }, @@ -133,7 +141,23 @@ const dev: SubCommand = { "no-check": { type: "boolean", default: false }, ci: { type: "boolean", default: false }, verbose: { type: "boolean", short: "v", default: false }, - }); + }, { allowPositionals: true }); + + if (args.length > 0 && !options.cmd) { + console.error( + red("Extra arguments are only supported with --cmd (-c)") + ); + process.exit(1); + } + + if (args.length > 0 && options.cmd) { + const escaped = args.map(shellEscape).join(" "); + if (options.cmd.includes("{}")) { + options.cmd = options.cmd.replaceAll("{}", escaped); + } else { + options.cmd += " " + escaped; + } + } if (options.help) { console.log("Usage: liveblocks dev [options]"); @@ -145,6 +169,8 @@ const dev: SubCommand = { console.log(" --host Host to bind to (default: localhost)"); console.log(" --cmd, -c Run a one-off command against a fresh server instance, then"); // prettier-ignore console.log(" shut down. Does not affect your local data in .liveblocks/."); // prettier-ignore + console.log(" Extra args are appended to the command, or replace {} if"); // prettier-ignore + console.log(" present. Use -- before args starting with -."); // prettier-ignore console.log(" --ci Start a fresh server instance on every boot, ideal for CI"); // prettier-ignore console.log(" --no-check Skip project setup check on start"); console.log(" --verbose, -v Show verbose output"); From 6b72d354f74531dc0d95fbc8cdcec87bbbc74c26 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Tue, 31 Mar 2026 10:58:16 +0200 Subject: [PATCH 2/6] Update dev derver docs with new `--cmd` flag pass-through behavior (#3291) --- docs/pages/tools/dev-server.mdx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/pages/tools/dev-server.mdx b/docs/pages/tools/dev-server.mdx index 1bbac5082ea..8c26cfbbe55 100644 --- a/docs/pages/tools/dev-server.mdx +++ b/docs/pages/tools/dev-server.mdx @@ -244,6 +244,29 @@ Test Files 1 passed (1) Liveblocks dev server shut down ``` +You can also pass extra arguments after the command. They will be appended to +the end of the command string: + +```bash +npx liveblocks dev --cmd 'vitest run' my-test.test.ts +# Runs: vitest run my-test.test.ts +``` + +If you need precise control over where the extra arguments are inserted, use +`{}` as a placeholder: + +```bash +npx liveblocks dev --cmd 'vitest run {} --reporter=verbose' my-test.test.ts +# Runs: vitest run my-test.test.ts --reporter=verbose +``` + +Use `--` before any extra arguments that start with `-`: + +```bash +npx liveblocks dev --cmd 'vitest run' -- --coverage my-test.test.ts +# Runs: vitest run --coverage my-test.test.ts +``` + We recommend: 1. `npx liveblocks dev` for manual testing and development, which retains data From d88c70d4268e24f2daa7560cc4da91810ba3b1ec Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Tue, 31 Mar 2026 08:57:41 +0000 Subject: [PATCH 3/6] Bump to 1.2.1 Original commit: 0a00acfd395aa58d79e0262de259745b0507de34 --- packages/liveblocks-server/package.json | 2 +- tools/liveblocks-cli/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/liveblocks-server/package.json b/packages/liveblocks-server/package.json index 4a684e49ccd..23051f79900 100644 --- a/packages/liveblocks-server/package.json +++ b/packages/liveblocks-server/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/server", - "version": "1.2.0", + "version": "1.2.1", "description": "Liveblocks backend server foundation.", "type": "module", "main": "./dist/index.js", diff --git a/tools/liveblocks-cli/package.json b/tools/liveblocks-cli/package.json index d7239a7f97e..96f07b21781 100644 --- a/tools/liveblocks-cli/package.json +++ b/tools/liveblocks-cli/package.json @@ -1,6 +1,6 @@ { "name": "liveblocks", - "version": "1.2.0", + "version": "1.2.1", "description": "Liveblocks command line interface", "type": "module", "bin": { From 8a05dc1363d9609b1e198dcb02c6208f194c56f4 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Tue, 31 Mar 2026 13:48:45 +0200 Subject: [PATCH 4/6] Dev server 1.3.0: feeds support, verbose toggle, permission fixes (PR 1681) * Add verbose logging toggle to dev server * Fix dev server permission handling for feeds Original commit: 5b2144704dcaaa6d4ca837c4653df3bbed3ce606 --- tools/liveblocks-cli/CHANGELOG.md | 5 +- tools/liveblocks-cli/src/dev-server/auth.ts | 7 +- .../src/dev-server/db/BunSQLiteDriver.ts | 51 ++++++------- .../liveblocks-cli/src/dev-server/db/rooms.ts | 6 +- tools/liveblocks-cli/src/dev-server/index.ts | 72 +++++++++++++++---- .../src/dev-server/lib/jwt-lite.ts | 3 +- .../src/dev-server/lib/permissions.ts | 35 +++++++++ .../src/dev-server/routes/auth.ts | 4 +- .../src/dev-server/routes/rest-api.ts | 3 +- 9 files changed, 137 insertions(+), 49 deletions(-) create mode 100644 tools/liveblocks-cli/src/dev-server/lib/permissions.ts diff --git a/tools/liveblocks-cli/CHANGELOG.md b/tools/liveblocks-cli/CHANGELOG.md index 7b17940e21c..25ee9a06a3f 100644 --- a/tools/liveblocks-cli/CHANGELOG.md +++ b/tools/liveblocks-cli/CHANGELOG.md @@ -1,7 +1,10 @@ ## vNEXT (not yet released) -## v1.2.1 +## v1.3.0 +- Add feeds support (`feeds:write` permission) +- Add verbose logging toggle +- Fix permission validation to accept all valid permission combinations - Support passing extra arguments to `--cmd` (`-c`), appended to the command or replacing `{}` if present diff --git a/tools/liveblocks-cli/src/dev-server/auth.ts b/tools/liveblocks-cli/src/dev-server/auth.ts index ced3af51095..9bb3c90af6c 100644 --- a/tools/liveblocks-cli/src/dev-server/auth.ts +++ b/tools/liveblocks-cli/src/dev-server/auth.ts @@ -15,13 +15,14 @@ * along with this program. If not, see . */ -import { nanoid, Permission } from "@liveblocks/core"; +import { nanoid } from "@liveblocks/core"; import type { CreateTicketOptions } from "@liveblocks/server"; import { ProtocolVersion } from "@liveblocks/server"; import * as Rooms from "./db/rooms"; import type { LiteAccessToken, LiteIdToken, LiteToken } from "./lib/jwt-lite"; import { verifyJwtLite } from "./lib/jwt-lite"; +import { Permission } from "./lib/permissions"; function resolvePermissions_acc( token: LiteAccessToken, @@ -150,7 +151,7 @@ export function authorizeWebSocket( // Auto-create the room if it doesn't exist yet Rooms.getOrCreateRoom(roomId, { - defaultAccesses: [Permission.Write], + defaultAccesses: [Permission.RoomWrite], }); // Public key auth always grants write access (matches production behavior) @@ -160,7 +161,7 @@ export function authorizeWebSocket( ticketData: { version, anonymousId: nanoid(), - scopes: [Permission.Write], + scopes: [Permission.RoomWrite], }, }; } diff --git a/tools/liveblocks-cli/src/dev-server/db/BunSQLiteDriver.ts b/tools/liveblocks-cli/src/dev-server/db/BunSQLiteDriver.ts index 4dfd137c5c9..87097a073af 100644 --- a/tools/liveblocks-cli/src/dev-server/db/BunSQLiteDriver.ts +++ b/tools/liveblocks-cli/src/dev-server/db/BunSQLiteDriver.ts @@ -934,9 +934,10 @@ export class BunSQLiteDriver implements IStorageDriver { get_feed(feedId: string): Feed | undefined { const row = this.db - .query( - "SELECT feed_id, jmetadata, created_at, updated_at FROM feeds WHERE feed_id = ?" - ) + .query< + FeedRow, + [string] + >("SELECT feed_id, jmetadata, created_at, updated_at FROM feeds WHERE feed_id = ?") .get(feedId); if (row === undefined || row === null) return undefined; return { @@ -949,9 +950,10 @@ export class BunSQLiteDriver implements IStorageDriver { create_feed(feed: Feed): void { const existing = this.db - .query, [string]>( - "SELECT feed_id FROM feeds WHERE feed_id = ?" - ) + .query< + Pick, + [string] + >("SELECT feed_id FROM feeds WHERE feed_id = ?") .get(feed.feedId); if (existing !== undefined && existing !== null) { throw new Error(`Feed ${feed.feedId} already exists`); @@ -970,9 +972,10 @@ export class BunSQLiteDriver implements IStorageDriver { update_feed_metadata(feedId: string, metadata: Feed["metadata"]): void { const result = this.db - .query( - "UPDATE feeds SET jmetadata = ? WHERE feed_id = ? RETURNING feed_id, jmetadata, created_at, updated_at" - ) + .query< + FeedRow, + [string, string] + >("UPDATE feeds SET jmetadata = ? WHERE feed_id = ? RETURNING feed_id, jmetadata, created_at, updated_at") .get(JSON.stringify(metadata), feedId); if (result === undefined || result === null) { throw new Error(`Feed ${feedId} not found`); @@ -980,9 +983,7 @@ export class BunSQLiteDriver implements IStorageDriver { } delete_feed(feedId: string): void { - this.db - .query("DELETE FROM feeds WHERE feed_id = ?") - .run(feedId); + this.db.query("DELETE FROM feeds WHERE feed_id = ?").run(feedId); } list_feed_messages( @@ -1074,9 +1075,10 @@ export class BunSQLiteDriver implements IStorageDriver { timestamp?: number ): FeedMessage { const existing = this.db - .query( - "SELECT feed_id, message_id, jdata, created_at, updated_at FROM feed_messages WHERE feed_id = ? AND message_id = ?" - ) + .query< + FeedMessageRow, + [string, string] + >("SELECT feed_id, message_id, jdata, created_at, updated_at FROM feed_messages WHERE feed_id = ? AND message_id = ?") .get(feedId, messageId); if (existing === undefined || existing === null) { throw new Error(`Feed message ${messageId} not found in feed ${feedId}`); @@ -1096,9 +1098,7 @@ export class BunSQLiteDriver implements IStorageDriver { .query< FeedMessageRow, [string, number, string, string, number] - >( - "UPDATE feed_messages SET jdata = ?, updated_at = ? WHERE feed_id = ? AND message_id = ? AND updated_at <= ? RETURNING feed_id, message_id, jdata, created_at, updated_at" - ) + >("UPDATE feed_messages SET jdata = ?, updated_at = ? WHERE feed_id = ? AND message_id = ? AND updated_at <= ? RETURNING feed_id, message_id, jdata, created_at, updated_at") .get( JSON.stringify(data), effectiveTimestamp, @@ -1108,12 +1108,15 @@ export class BunSQLiteDriver implements IStorageDriver { ); if (result === undefined || result === null) { const latest = this.db - .query( - "SELECT feed_id, message_id, jdata, created_at, updated_at FROM feed_messages WHERE feed_id = ? AND message_id = ?" - ) + .query< + FeedMessageRow, + [string, string] + >("SELECT feed_id, message_id, jdata, created_at, updated_at FROM feed_messages WHERE feed_id = ? AND message_id = ?") .get(feedId, messageId); if (latest === undefined || latest === null) { - throw new Error(`Feed message ${messageId} not found in feed ${feedId}`); + throw new Error( + `Feed message ${messageId} not found in feed ${feedId}` + ); } return { id: latest.message_id, @@ -1132,9 +1135,7 @@ export class BunSQLiteDriver implements IStorageDriver { delete_feed_message(feedId: string, messageId: string): void { this.db - .query( - "DELETE FROM feed_messages WHERE feed_id = ? AND message_id = ?" - ) + .query("DELETE FROM feed_messages WHERE feed_id = ? AND message_id = ?") .run(feedId, messageId); } diff --git a/tools/liveblocks-cli/src/dev-server/db/rooms.ts b/tools/liveblocks-cli/src/dev-server/db/rooms.ts index 5e376763018..1fc48451b4c 100644 --- a/tools/liveblocks-cli/src/dev-server/db/rooms.ts +++ b/tools/liveblocks-cli/src/dev-server/db/rooms.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import type { JsonObject, Permission } from "@liveblocks/core"; +import type { JsonObject } from "@liveblocks/core"; import { nanoid, WebsocketCloseCodes } from "@liveblocks/core"; import type { Millis } from "@liveblocks/server"; import { DefaultMap, Room } from "@liveblocks/server"; @@ -24,6 +24,8 @@ import { mkdirSync, mkdtempSync, rmSync } from "fs"; import { tmpdir } from "os"; import { dirname, join, resolve } from "path"; +import type { Permission } from "~/dev-server/lib/permissions"; + import { BunSQLiteDriver } from "./BunSQLiteDriver"; // --------------------------------------------------------------------------- @@ -229,7 +231,7 @@ function createDbRoom( id: roomId, internalId, organizationId, - defaultAccesses: defaultAccesses as Permission[], + defaultAccesses, usersAccesses: opts?.usersAccesses ?? {}, groupsAccesses: opts?.groupsAccesses ?? {}, metadata, diff --git a/tools/liveblocks-cli/src/dev-server/index.ts b/tools/liveblocks-cli/src/dev-server/index.ts index 643b2fe005f..c62a3fb4ad8 100644 --- a/tools/liveblocks-cli/src/dev-server/index.ts +++ b/tools/liveblocks-cli/src/dev-server/index.ts @@ -133,20 +133,22 @@ const dev: SubCommand = { description: "Start the local Liveblocks dev server", async run(argv) { - const { options, args } = parseArgs(argv, { - port: { type: "string", short: "p", default: DEFAULT_PORT.toString() }, - host: { type: "string" }, - cmd: { type: "string", short: "c" }, - help: { type: "boolean", short: "h", default: false }, - "no-check": { type: "boolean", default: false }, - ci: { type: "boolean", default: false }, - verbose: { type: "boolean", short: "v", default: false }, - }, { allowPositionals: true }); + const { options, args } = parseArgs( + argv, + { + port: { type: "string", short: "p", default: DEFAULT_PORT.toString() }, + host: { type: "string" }, + cmd: { type: "string", short: "c" }, + help: { type: "boolean", short: "h", default: false }, + "no-check": { type: "boolean", default: false }, + ci: { type: "boolean", default: false }, + verbose: { type: "boolean", short: "v", default: false }, + }, + { allowPositionals: true } + ); if (args.length > 0 && !options.cmd) { - console.error( - red("Extra arguments are only supported with --cmd (-c)") - ); + console.error(red("Extra arguments are only supported with --cmd (-c)")); process.exit(1); } @@ -170,7 +172,7 @@ const dev: SubCommand = { console.log(" --cmd, -c Run a one-off command against a fresh server instance, then"); // prettier-ignore console.log(" shut down. Does not affect your local data in .liveblocks/."); // prettier-ignore console.log(" Extra args are appended to the command, or replace {} if"); // prettier-ignore - console.log(" present. Use -- before args starting with -."); // prettier-ignore + console.log(" present. Use -- before args starting with -."); // prettier-ignore console.log(" --ci Start a fresh server instance on every boot, ideal for CI"); // prettier-ignore console.log(" --no-check Skip project setup check on start"); console.log(" --verbose, -v Show verbose output"); @@ -212,6 +214,7 @@ const dev: SubCommand = { } let server: Bun.Server; + let verbose = false; function createServer() { return Bun.serve({ @@ -279,6 +282,17 @@ const dev: SubCommand = { // Defer all other routing to ZenRouter // TODO: Maybe port this logging to ZenRouter natively const route = `${req.method} ${url.pathname}`; + + // In verbose mode, clone the request so we can read its body + let reqBody: string | undefined; + if (verbose) { + try { + reqBody = await req.clone().text(); + } catch { + // Ignore - body may not be readable + } + } + const resp = await zen.fetch(req); const status = resp.status; const colorStatus = @@ -290,6 +304,25 @@ const dev: SubCommand = { console.log(`${colorStatus} ${route}`); const warnMsg = resp.headers.get("X-LB-Warn") ?? undefined; warn(warnMsg, !resp.ok); + + if (verbose) { + if (reqBody) { + try { + const parsed = JSON.parse(reqBody); + console.log(dim(` → ${JSON.stringify(parsed)}`)); + } catch { + // Not JSON, skip + } + } + try { + const respBody = await resp.clone().text(); + const parsed = JSON.parse(respBody); + console.log(dim(` ← ${JSON.stringify(parsed)}`)); + } catch { + // Not JSON or empty, skip + } + } + return resp; }, @@ -461,7 +494,9 @@ const dev: SubCommand = { bold("!") + dim(" crash, ") + bold("c") + - dim(" clear"); + dim(" clear, ") + + bold("v") + + dim(verbose ? " verbose (on)" : " verbose"); const switchToLogs = (): void => { if (repaintTimer) { @@ -710,6 +745,15 @@ const dev: SubCommand = { originalLog(renderTabBar()); originalLog(logsLegend()); originalLog(); + } else if (ch === "v") { + verbose = !verbose; + // Repaint tab bar + legend in-place to reflect new state + process.stdout.write("\x1B[s\x1B[H\x1B[2K"); + originalLog(renderTabBar()); + process.stdout.write("\x1B[2K"); + originalLog(logsLegend()); + process.stdout.write("\x1B[u"); + console.log(dim(verbose ? "Verbose mode on" : "Verbose mode off")); } else if (ch === "p") { if (configIssues.length > 0) { const prompt = buildFixPrompt(configIssues, baseUrl); diff --git a/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts b/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts index 79745f21443..feec5352207 100644 --- a/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts +++ b/tools/liveblocks-cli/src/dev-server/lib/jwt-lite.ts @@ -16,7 +16,7 @@ */ import type { DistributiveOmit } from "@liveblocks/core"; -import { nanoid, Permission, tryParseJson } from "@liveblocks/core"; +import { nanoid, tryParseJson } from "@liveblocks/core"; import type { DecoderType } from "decoders"; import { array, @@ -31,6 +31,7 @@ import { } from "decoders"; import { userInfo } from "./decoders"; +import { Permission } from "./permissions"; const unsignedJwtHeader = object({ alg: constant("none"), diff --git a/tools/liveblocks-cli/src/dev-server/lib/permissions.ts b/tools/liveblocks-cli/src/dev-server/lib/permissions.ts new file mode 100644 index 00000000000..bad31899ff5 --- /dev/null +++ b/tools/liveblocks-cli/src/dev-server/lib/permissions.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) Liveblocks Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Permission values accepted by the dev server. This must stay in sync with the + * backend's canonical Permission definition in shared/common/src/security/client-auth.ts. + * NOTE: Do not import from @liveblocks/core — the published version may lag behind. + * TODO: Find a way to DRY this up with the backend's Permission definition. + */ +export const Permission = { + RoomRead: "room:read", + RoomWrite: "room:write", + CommentsWrite: "comments:write", + FeedsWrite: "feeds:write", + /** @deprecated Accepted but ignored. Presence is always writable. */ + RoomPresenceWrite: "room:presence:write", + /** @deprecated Accepted but ignored. Read access is implied by room:read. */ + CommentsRead: "comments:read", +} as const; + +export type Permission = (typeof Permission)[keyof typeof Permission]; diff --git a/tools/liveblocks-cli/src/dev-server/routes/auth.ts b/tools/liveblocks-cli/src/dev-server/routes/auth.ts index 42827c16dca..789c7f21815 100644 --- a/tools/liveblocks-cli/src/dev-server/routes/auth.ts +++ b/tools/liveblocks-cli/src/dev-server/routes/auth.ts @@ -15,14 +15,14 @@ * along with this program. If not, see . */ -import { Permission } from "@liveblocks/core"; import { ZenRouter } from "@liveblocks/zenrouter"; import { array, enum_, object, optional, record, string } from "decoders"; import { authorizeSecretKey } from "~/dev-server/lib/auth"; import { userInfo } from "~/dev-server/lib/decoders"; import { createJwtLite } from "~/dev-server/lib/jwt-lite"; -// Valid permission values (from @liveblocks/core Permission enum) +import { Permission } from "~/dev-server/lib/permissions"; + const permission = enum_(Permission); export const zen = new ZenRouter({ diff --git a/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts b/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts index 7ca0452a1ee..3688601cc5d 100644 --- a/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts +++ b/tools/liveblocks-cli/src/dev-server/routes/rest-api.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import type { JsonObject, Permission, PlainLsonObject } from "@liveblocks/core"; +import type { JsonObject, PlainLsonObject } from "@liveblocks/core"; import { QueryParser } from "@liveblocks/query-parser"; import type { Guid, Logger, YDocId } from "@liveblocks/server"; import { @@ -42,6 +42,7 @@ import * as Y from "yjs"; import type { DbRoom, RoomFilters } from "~/dev-server/db/rooms"; import * as Rooms from "~/dev-server/db/rooms"; import { authorizeSecretKey } from "~/dev-server/lib/auth"; +import type { Permission } from "~/dev-server/lib/permissions"; import { yDocToJson } from "~/dev-server/lib/ydoc"; import { DUMMY, NOT_IMPLEMENTED } from "~/dev-server/responses"; From b58aad79251318db8fbe661120ec0fc7d0717610 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Tue, 31 Mar 2026 11:51:35 +0000 Subject: [PATCH 5/6] Bump to 1.3.0 Original commit: 902eb5067660e66edd1ec33b3a9b417904ee5a48 --- packages/liveblocks-server/package.json | 2 +- tools/liveblocks-cli/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/liveblocks-server/package.json b/packages/liveblocks-server/package.json index 23051f79900..a04ee119451 100644 --- a/packages/liveblocks-server/package.json +++ b/packages/liveblocks-server/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/server", - "version": "1.2.1", + "version": "1.3.0", "description": "Liveblocks backend server foundation.", "type": "module", "main": "./dist/index.js", diff --git a/tools/liveblocks-cli/package.json b/tools/liveblocks-cli/package.json index 96f07b21781..b967fc6b014 100644 --- a/tools/liveblocks-cli/package.json +++ b/tools/liveblocks-cli/package.json @@ -1,6 +1,6 @@ { "name": "liveblocks", - "version": "1.2.1", + "version": "1.3.0", "description": "Liveblocks command line interface", "type": "module", "bin": { From 44939f48c12a51002ff8eed6a403f0d2386d3a2a Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Tue, 31 Mar 2026 13:54:56 +0200 Subject: [PATCH 6/6] Lints (PR 1682) Original commit: fee6f54da023c58595072002a156503129825589 --- tools/liveblocks-cli/src/dev-server/index.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tools/liveblocks-cli/src/dev-server/index.ts b/tools/liveblocks-cli/src/dev-server/index.ts index c62a3fb4ad8..014594927b2 100644 --- a/tools/liveblocks-cli/src/dev-server/index.ts +++ b/tools/liveblocks-cli/src/dev-server/index.ts @@ -17,6 +17,7 @@ import { Promise_withResolvers, + tryParseJson, WebsocketCloseCodes as CloseCode, } from "@liveblocks/core"; import type { Millis, Room, SessionKey, Ticket } from "@liveblocks/server"; @@ -307,19 +308,15 @@ const dev: SubCommand = { if (verbose) { if (reqBody) { - try { - const parsed = JSON.parse(reqBody); + const parsed = tryParseJson(reqBody); + if (parsed !== undefined) { console.log(dim(` → ${JSON.stringify(parsed)}`)); - } catch { - // Not JSON, skip } } - try { - const respBody = await resp.clone().text(); - const parsed = JSON.parse(respBody); - console.log(dim(` ← ${JSON.stringify(parsed)}`)); - } catch { - // Not JSON or empty, skip + const respBody = await resp.clone().text(); + const parsedResp = tryParseJson(respBody); + if (parsedResp !== undefined) { + console.log(dim(` ← ${JSON.stringify(parsedResp)}`)); } }