@omg-dev/vite-plugin 0.4.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@omg-dev/vite-plugin",
3
+ "version": "0.4.24",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./src/index.ts",
8
+ "default": "./dist/index.mjs"
9
+ }
10
+ },
11
+ "bin": {
12
+ "vibes-build": "./src/build.ts"
13
+ },
14
+ "scripts": {
15
+ "build": "vp pack src/index.ts --no-fail-on-warn"
16
+ },
17
+ "dependencies": {
18
+ "@omg-dev/server": "0.4.24",
19
+ "@omg-dev/schema": "0.4.24",
20
+ "ws": "^8.18.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/ws": "^8.5.13"
24
+ },
25
+ "peerDependencies": {
26
+ "vite": "*"
27
+ },
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/BennyKok/vibes.git"
32
+ },
33
+ "homepage": "https://docs.omg.dev",
34
+ "files": [
35
+ "dist",
36
+ "src"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public",
40
+ "registry": "https://registry.npmjs.org/"
41
+ }
42
+ }
@@ -0,0 +1,118 @@
1
+ import { afterEach, describe, expect, it } from "vitest"
2
+ import { fetchAuthUpstream, relayAuthUpstream } from "./auth-bridge.ts"
3
+
4
+ // Minimal ServerResponse stand-in capturing what the bridge writes.
5
+ function mockRes() {
6
+ const headers: Record<string, string> = {}
7
+ const state = { statusCode: 0, body: null as Buffer | null, headers }
8
+ const res = {
9
+ get statusCode() { return state.statusCode },
10
+ set statusCode(v: number) { state.statusCode = v },
11
+ setHeader(key: string, value: string) { headers[key.toLowerCase()] = String(value) },
12
+ end(body?: Buffer) { state.body = body ?? Buffer.alloc(0) },
13
+ }
14
+ return { res: res as unknown as Parameters<typeof relayAuthUpstream>[0], state }
15
+ }
16
+
17
+ describe("relayAuthUpstream", () => {
18
+ it("strips upstream framing + cookie headers and recomputes content-length", async () => {
19
+ // Mirrors the production failure: Cloudflare zstd-compressed the 200
20
+ // token response, fetch() decompressed it, and the old relay copied the
21
+ // stale framing headers onto the plaintext body — CF 520'd the desync.
22
+ const body = JSON.stringify({ token: "x".repeat(400), expiresAt: "2026-01-01T00:00:00Z" })
23
+ const upstream = new Response(body, {
24
+ status: 200,
25
+ headers: {
26
+ "Content-Type": "application/json;charset=utf-8",
27
+ "Content-Encoding": "zstd",
28
+ "Transfer-Encoding": "chunked",
29
+ "Set-Cookie": "evil=1",
30
+ Connection: "keep-alive",
31
+ "Access-Control-Allow-Origin": "https://omg.dev",
32
+ },
33
+ })
34
+ // Response normally re-derives content-length; force the stale value the
35
+ // way fetch() surfaces it after transparent decompression.
36
+ upstream.headers.set("Content-Length", "460")
37
+
38
+ const { res, state } = mockRes()
39
+ await relayAuthUpstream(res, upstream)
40
+
41
+ expect(state.statusCode).toBe(200)
42
+ expect(state.body?.toString()).toBe(body)
43
+ expect(state.headers["content-encoding"]).toBeUndefined()
44
+ expect(state.headers["transfer-encoding"]).toBeUndefined()
45
+ expect(state.headers["set-cookie"]).toBeUndefined()
46
+ expect(state.headers["connection"]).toBeUndefined()
47
+ expect(state.headers["content-length"]).toBe(String(Buffer.byteLength(body)))
48
+ expect(state.headers["content-type"]).toBe("application/json;charset=utf-8")
49
+ expect(state.headers["access-control-allow-origin"]).toBe("https://omg.dev")
50
+ })
51
+
52
+ it("relays error statuses verbatim", async () => {
53
+ const upstream = new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 })
54
+ const { res, state } = mockRes()
55
+ await relayAuthUpstream(res, upstream)
56
+ expect(state.statusCode).toBe(401)
57
+ expect(state.body?.toString()).toContain("unauthorized")
58
+ })
59
+ })
60
+
61
+ describe("fetchAuthUpstream", () => {
62
+ const originalFetch = globalThis.fetch
63
+ const originalProxy = process.env.OMG_AI_URL
64
+ const originalAuthUrl = process.env.VIBES_AUTH_URL
65
+
66
+ afterEach(() => {
67
+ globalThis.fetch = originalFetch
68
+ if (originalProxy === undefined) delete process.env.OMG_AI_URL
69
+ else process.env.OMG_AI_URL = originalProxy
70
+ if (originalAuthUrl === undefined) delete process.env.VIBES_AUTH_URL
71
+ else process.env.VIBES_AUTH_URL = originalAuthUrl
72
+ })
73
+
74
+ it("uses the host-side proxy when it advertises the auth route", async () => {
75
+ process.env.OMG_AI_URL = "http://169.254.0.1:9090"
76
+ const calls: string[] = []
77
+ globalThis.fetch = (async (input: RequestInfo | URL) => {
78
+ calls.push(String(input))
79
+ return new Response("{}", { status: 200, headers: { "X-Vibes-Auth-Proxy": "1" } })
80
+ }) as typeof fetch
81
+
82
+ const res = await fetchAuthUpstream("token", { method: "POST" })
83
+ expect(res.status).toBe(200)
84
+ expect(calls).toEqual(["http://169.254.0.1:9090/auth/token"])
85
+ })
86
+
87
+ it("falls back to direct when the proxy lacks the marker (old orchestrator)", async () => {
88
+ process.env.OMG_AI_URL = "http://169.254.0.1:9090"
89
+ delete process.env.VIBES_AUTH_URL
90
+ const calls: string[] = []
91
+ globalThis.fetch = (async (input: RequestInfo | URL) => {
92
+ calls.push(String(input))
93
+ if (calls.length === 1) return new Response("not the auth route", { status: 404 })
94
+ return new Response("{}", { status: 200 })
95
+ }) as typeof fetch
96
+
97
+ const res = await fetchAuthUpstream("session", { method: "GET" }, "?a=1")
98
+ expect(res.status).toBe(200)
99
+ expect(calls).toEqual([
100
+ "http://169.254.0.1:9090/auth/get-session?a=1",
101
+ "https://auth.omg.dev/api/auth/get-session?a=1",
102
+ ])
103
+ })
104
+
105
+ it("goes direct when no proxy env is present", async () => {
106
+ delete process.env.OMG_AI_URL
107
+ process.env.VIBES_AUTH_URL = "https://auth.example.test/"
108
+ const calls: string[] = []
109
+ globalThis.fetch = (async (input: RequestInfo | URL) => {
110
+ calls.push(String(input))
111
+ return new Response("{}", { status: 401 })
112
+ }) as typeof fetch
113
+
114
+ const res = await fetchAuthUpstream("token", { method: "POST" })
115
+ expect(res.status).toBe(401)
116
+ expect(calls).toEqual(["https://auth.example.test/token"])
117
+ })
118
+ })
@@ -0,0 +1,73 @@
1
+ // Preview auth bridge upstream helpers — shared by the /__vibes/auth/token
2
+ // and /__vibes/auth/session dev-server middlewares in index.ts.
3
+
4
+ import type { ServerResponse } from "node:http"
5
+
6
+ // Auth-state headers the preview bridges must never relay downstream:
7
+ // - set-cookie: the bridges are read-only for auth state (sign-in/out go
8
+ // direct to auth.omg.dev through the better-auth client).
9
+ // - content-encoding / content-length / transfer-encoding / connection /
10
+ // keep-alive: fetch() has already decompressed and de-chunked the body, so
11
+ // upstream's framing headers describe bytes we are NOT sending. Relaying
12
+ // them verbatim produced protocol-desynced responses (a `content-encoding:
13
+ // zstd` header on a plaintext body) that Cloudflare's edge killed with a
14
+ // 520 — which made token minting hang for every signed-in preview user
15
+ // while signed-out 401s (too small for compression) sailed through.
16
+ export const AUTH_RELAY_SKIP_HEADERS = new Set([
17
+ "set-cookie",
18
+ "content-encoding",
19
+ "content-length",
20
+ "transfer-encoding",
21
+ "connection",
22
+ "keep-alive",
23
+ ])
24
+
25
+ /** Relay an upstream auth response with sane framing for the buffered body. */
26
+ export async function relayAuthUpstream(res: ServerResponse, upstream: Response): Promise<void> {
27
+ const body = Buffer.from(await upstream.arrayBuffer())
28
+ res.statusCode = upstream.status
29
+ upstream.headers.forEach((value, key) => {
30
+ if (AUTH_RELAY_SKIP_HEADERS.has(key.toLowerCase())) return
31
+ res.setHeader(key, value)
32
+ })
33
+ res.setHeader("Content-Length", String(body.byteLength))
34
+ res.end(body)
35
+ }
36
+
37
+ /**
38
+ * Fetch the auth upstream for the preview bridges.
39
+ *
40
+ * Prefers the host-side per-sandbox proxy (OMG_AI_URL, the same listener the
41
+ * LLM proxy uses at 169.254.0.1:9090) when it advertises the auth route via
42
+ * the `x-vibes-auth-proxy` marker: in-VM Bun fetch to the Cloudflare-proxied
43
+ * auth host is the AAAA-first/no-IPv6-route hang class, while the host-side
44
+ * Go client dials dual-stack. Falls back to a direct fetch when the proxy is
45
+ * absent (local dev outside a sandbox) or predates the auth route.
46
+ */
47
+ export async function fetchAuthUpstream(
48
+ kind: "token" | "session",
49
+ init: RequestInit,
50
+ qs = "",
51
+ ): Promise<Response> {
52
+ const proxyBase = (process.env.OMG_AI_URL ?? "").trim().replace(/\/+$/, "")
53
+ if (proxyBase) {
54
+ const proxyUrl = kind === "token"
55
+ ? `${proxyBase}/auth/token`
56
+ : `${proxyBase}/auth/get-session${qs}`
57
+ try {
58
+ const viaProxy = await fetch(proxyUrl, init)
59
+ if (viaProxy.headers.get("x-vibes-auth-proxy")) return viaProxy
60
+ // No marker: an older orchestrator's catch-all LLM route answered.
61
+ // Discard and go direct.
62
+ await viaProxy.arrayBuffer().catch(() => {})
63
+ } catch {
64
+ // Host proxy unreachable — go direct.
65
+ }
66
+ }
67
+ const authUrl = (process.env.VIBES_AUTH_URL || process.env.VITE_AUTH_URL || "https://auth.omg.dev")
68
+ .replace(/\/+$/, "")
69
+ const directUrl = kind === "token"
70
+ ? `${authUrl}/token`
71
+ : `${authUrl}/api/auth/get-session${qs}`
72
+ return fetch(directUrl, init)
73
+ }
@@ -0,0 +1,94 @@
1
+ // Verifies the build-time injection of the published-app omg badge
2
+ // (@omg-dev/sdk/brand/auto) into the app entry — default ON, opt-out via
3
+ // vibes({ brandBadge: false }), build-only, app-entry-only.
4
+
5
+ import { describe, expect, test } from "bun:test"
6
+ import vibes from "./index.ts"
7
+
8
+ const ROOT = "/app"
9
+ const ENTRY = `${ROOT}/src/main.tsx`
10
+ const BRAND_IMPORT = `import "@omg-dev/sdk/brand/auto";`
11
+
12
+ function ctx() {
13
+ const warnings: string[] = []
14
+ return {
15
+ warnings,
16
+ resolve: async (src: string) => ({ id: src }),
17
+ warn: (msg: string) => warnings.push(msg),
18
+ }
19
+ }
20
+
21
+ async function runTransform(
22
+ plugin: ReturnType<typeof vibes>,
23
+ code: string,
24
+ id: string,
25
+ context: ReturnType<typeof ctx>,
26
+ ) {
27
+ const fn = plugin.transform as (this: unknown, code: string, id: string) => Promise<unknown>
28
+ return fn.call(context, code, id) as Promise<{ code: string } | null>
29
+ }
30
+
31
+ function buildPlugin(opts?: Parameters<typeof vibes>[0]) {
32
+ const plugin = vibes({ root: ROOT, pwa: false, feedback: false, ...opts })
33
+ const configResolved = plugin.configResolved as (cfg: unknown) => void
34
+ configResolved({ command: "build", root: ROOT })
35
+ return plugin
36
+ }
37
+
38
+ describe("brand badge injection", () => {
39
+ test("default ON: appends the auto-mount import to the app entry", async () => {
40
+ const plugin = buildPlugin()
41
+ const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
42
+ expect(out?.code).toContain(BRAND_IMPORT)
43
+ })
44
+
45
+ test("opt-out via brandBadge:false omits the import", async () => {
46
+ const plugin = buildPlugin({ brandBadge: false })
47
+ const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
48
+ expect(out).toBeNull()
49
+ })
50
+
51
+ test("injected exactly once across multiple entry transforms", async () => {
52
+ const plugin = buildPlugin()
53
+ const first = await runTransform(plugin, "export default 1", ENTRY, ctx())
54
+ const second = await runTransform(plugin, "export default 2", ENTRY, ctx())
55
+ expect(first?.code).toContain(BRAND_IMPORT)
56
+ expect(second).toBeNull()
57
+ })
58
+
59
+ test("non-entry modules are untouched", async () => {
60
+ const plugin = buildPlugin()
61
+ const out = await runTransform(plugin, "export const x = 1", `${ROOT}/src/util.ts`, ctx())
62
+ expect(out).toBeNull()
63
+ })
64
+
65
+ test("warns and skips when the SDK subpath cannot be resolved", async () => {
66
+ const plugin = buildPlugin()
67
+ const context = ctx()
68
+ context.resolve = async () => null
69
+ const out = await runTransform(plugin, "export default 1", ENTRY, context)
70
+ expect(out).toBeNull()
71
+ expect(context.warnings.some((w) => w.includes("brand badge"))).toBe(true)
72
+ })
73
+
74
+ test("badge ON bundles feedback + remix: no standalone imports injected", async () => {
75
+ // brand badge default ON, with feedback + pwa also on. The badge carries
76
+ // feedback (button-less) and the "Make it mine" CTA itself, so the
77
+ // standalone feedback/auto + remix-cta imports must NOT be appended.
78
+ const plugin = vibes({ root: ROOT })
79
+ const configResolved = plugin.configResolved as (cfg: unknown) => void
80
+ configResolved({ command: "build", root: ROOT })
81
+ const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
82
+ expect(out?.code).toContain(BRAND_IMPORT)
83
+ expect(out?.code).not.toContain(`import "@omg-dev/sdk/feedback/auto";`)
84
+ expect(out?.code).not.toContain(`import "@omg-dev/pwa/remix-cta";`)
85
+ })
86
+
87
+ test("serve mode never injects the badge", async () => {
88
+ const plugin = vibes({ root: ROOT })
89
+ const configResolved = plugin.configResolved as (cfg: unknown) => void
90
+ configResolved({ command: "serve", root: ROOT })
91
+ const out = await runTransform(plugin, "export default 1", ENTRY, ctx())
92
+ expect(out).toBeNull()
93
+ })
94
+ })