Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ export const alias = {
'@devframes/plugin-inspect/cli': p('inspect/src/cli.ts'),
'@devframes/plugin-inspect/vite': p('inspect/src/vite.ts'),
'@devframes/plugin-inspect': p('inspect/src/index.ts'),
'@devframes/plugin-og/client': p('og/src/client/index.ts'),
'@devframes/plugin-og/node': p('og/src/node/index.ts'),
'@devframes/plugin-og/rpc': p('og/src/rpc/index.ts'),
'@devframes/plugin-og/cli': p('og/src/cli.ts'),
'@devframes/plugin-og/vite': p('og/src/vite.ts'),
'@devframes/plugin-og': p('og/src/index.ts'),
'@devframes/plugin-a11y/client': p('a11y/src/client/index.ts'),
'@devframes/plugin-a11y/node': p('a11y/src/node/index.ts'),
'@devframes/plugin-a11y/cli': p('a11y/src/cli.ts'),
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ function pluginsItems(prefix: string) {
{ text: 'Overview', link: `${prefix}/plugins/` },
{ text: 'Data Inspector', link: `${prefix}/plugins/data-inspector` },
{ text: 'Devframe Inspector', link: `${prefix}/plugins/inspect` },
{ text: 'Open Graph Viewer', link: `${prefix}/plugins/og` },
{ text: 'Accessibility Inspector', link: `${prefix}/plugins/a11y` },
{ text: 'Git', link: `${prefix}/plugins/git` },
{ text: 'Terminals', link: `${prefix}/plugins/terminals` },
Expand Down
2 changes: 2 additions & 0 deletions docs/plugins/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Each plugin is built with a **different UI framework**. That is deliberate: devf
|--------|--------------|--------------|
| [Data Inspector](./data-inspector) | Vue | Query live server-side objects with jora — sources contributed by plugins, hosts, data files, or attached processes. |
| [Devframe Inspector](./inspect) | Vue | Browse the RPC registry, invoke read-only queries, watch shared state update live, and explore the agent surface. |
| [Open Graph Viewer](./og) | Vue | Inspect Open Graph and Twitter metadata and compare social-card previews. |
| [Accessibility Inspector](./a11y) | Solid | Run axe-core against a host app, list WCAG violations, and highlight the offending element in the page on hover. |
| [Git](./git) | React (Next.js) | A repository dashboard — status, a commit graph, branches, and diffs, with optional staging and committing. |
| [Terminals](./terminals) | Svelte | Stream read-only command output and run fully interactive PTY shells in the browser. |
Expand All @@ -29,6 +30,7 @@ Most plugins publish a `bin`, so the quickest path is `npx`:

```sh
npx @devframes/plugin-inspect # the Devframe Inspector, standalone
npx @devframes/plugin-og # inspect Open Graph metadata and social cards
npx @devframes/plugin-git # the Git dashboard against the current repo
```

Expand Down
29 changes: 29 additions & 0 deletions docs/plugins/og.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
outline: deep
---

# Open Graph Viewer

Inspect a page's resolved Open Graph and Twitter metadata and compare its social cards across common platforms.

## Run Standalone

```sh
npx @devframes/plugin-og
```

Enter any HTTP or HTTPS page reachable from the devframe process. The viewer resolves relative image and icon URLs against the fetched document and identifies missing metadata with a ready-to-use Nuxt `useSeoMeta()` snippet.

## Create A Definition

```ts
import { createOgDevframe } from '@devframes/plugin-og'

export default createOgDevframe({
defaultUrl: 'http://localhost:3000/about',
})
```

`defaultUrl` supplies the initial target and makes `devframe build` bake that page into the static RPC dump. The resulting report keeps its assets relative and runs from any deployment path.

The standalone server requires devframe's trust handshake by default because it can request developer-supplied URLs. Set `auth: false` only for an isolated local environment.
1 change: 1 addition & 0 deletions examples/minimal-vite-devframe-hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@devframes/plugin-git": "workspace:*",
"@devframes/plugin-inspect": "workspace:*",
"@devframes/plugin-messages": "workspace:*",
"@devframes/plugin-og": "workspace:*",
"@devframes/plugin-terminals": "workspace:*",
"colorjs.io": "catalog:frontend",
"devframe": "workspace:*"
Expand Down
2 changes: 2 additions & 0 deletions examples/minimal-vite-devframe-hub/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { registerDataSource } from '@devframes/plugin-data-inspector/registry'
import gitDevframe from '@devframes/plugin-git'
import inspectDevframe from '@devframes/plugin-inspect'
import messagesDevframe from '@devframes/plugin-messages'
import ogDevframe from '@devframes/plugin-og'
import terminalsDevframe from '@devframes/plugin-terminals'
import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'
Expand Down Expand Up @@ -61,6 +62,7 @@ export default defineConfig({
dataInspectorDevframe,
a11yDevframe,
messagesDevframe,
ogDevframe,
],
// Attach the a11y inspector's in-page agent as its dock's client script.
// The hub client runtime (booted in src/client/main.ts) imports it into
Expand Down
2 changes: 2 additions & 0 deletions plugins/og/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.nuxt
src/spa/.nuxt
9 changes: 9 additions & 0 deletions plugins/og/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# `@devframes/plugin-og`

Inspect Open Graph and Twitter metadata for any reachable page, then compare its social preview across Twitter, Facebook, LinkedIn, and Telegram.

```sh
npx @devframes/plugin-og
```

The package exports `createOgDevframe()` for custom definitions and `ogVitePlugin()` from `@devframes/plugin-og/vite` for Vite hosts. Pass `defaultUrl` to bake a shareable report with the devframe build adapter.
12 changes: 12 additions & 0 deletions plugins/og/bin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node
import process from 'node:process'
import { createOgCli } from './dist/cli.mjs'

async function main() {
await createOgCli().parse()
}

main().catch((error) => {
console.error(error)
process.exit(1)
})
79 changes: 79 additions & 0 deletions plugins/og/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
{
"name": "@devframes/plugin-og",
"type": "module",
"version": "0.7.5",
"description": "Devframe plugin for inspecting Open Graph metadata and previewing social cards for any URL.",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"homepage": "https://github.com/devframes/devframe#readme",
"repository": {
"directory": "plugins/og",
"type": "git",
"url": "git+https://github.com/devframes/devframe.git"
},
"bugs": "https://github.com/devframes/devframe/issues",
"keywords": [
"devframe",
"devframe-plugin",
"devtools",
"open-graph",
"social-preview"
],
"sideEffects": false,
"exports": {
".": "./dist/index.mjs",
"./client": "./dist/client/index.mjs",
"./cli": "./dist/cli.mjs",
"./node": "./dist/node/index.mjs",
"./rpc": "./dist/rpc/index.mjs",
"./vite": "./dist/vite.mjs",
"./package.json": "./package.json"
},
"types": "./dist/index.d.mts",
"bin": {
"devframe-og": "./bin.mjs"
},
"files": [
"bin.mjs",
"dist"
],
"scripts": {
"build": "tsdown && vite build --config src/spa/vite.config.ts",
"dev": "vite --config src/spa/vite.config.ts --host 0.0.0.0",
"watch": "tsdown --watch",
"cli:build": "node bin.mjs build --out-dir dist/static",
"prepack": "pnpm build",
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"peerDependencies": {
"devframe": "workspace:*",
"vite": "^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"vite": {
"optional": true
}
},
"dependencies": {
"cac": "catalog:deps",
"nostics": "catalog:deps",
"parse5": "catalog:deps",
"valibot": "catalog:deps"
},
"devDependencies": {
"@antfu/design": "catalog:frontend",
"@iconify-json/ph": "catalog:frontend",
"@types/node": "catalog:types",
"@vitejs/plugin-vue": "catalog:build",
"devframe": "workspace:*",
"get-port-please": "catalog:deps",
"h3": "catalog:deps",
"tsdown": "catalog:build",
"unocss": "catalog:frontend",
"vite": "catalog:build",
"vitest": "catalog:testing",
"vue": "catalog:frontend",
"ws": "catalog:deps"
}
}
7 changes: 7 additions & 0 deletions plugins/og/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { CacHandle } from 'devframe/adapters/cac'
import { createCac } from 'devframe/adapters/cac'
import ogDevframe from './index'

export function createOgCli(): CacHandle {
return createCac(ogDevframe)
}
9 changes: 9 additions & 0 deletions plugins/og/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { DevframeRpcClientOptions } from 'devframe/client'
import { connectDevframe } from 'devframe/client'

export type { OgHeadTag, OgHeadTagName, OgResolveInput, OgSnapshot } from '../types'
export type { DevframeConnectionStatus, DevframeRpcClient } from 'devframe/client'

export function connectOg(options?: DevframeRpcClientOptions) {
return connectDevframe(options)
}
27 changes: 27 additions & 0 deletions plugins/og/src/diagnostics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { defineDiagnostics } from 'nostics'

export const diagnostics = defineDiagnostics({
docsBase: 'https://devfra.me/errors',
codes: {
DP_OG_0001: {
why: (p: { url: string }) => `Cannot inspect "${p.url}" because it is not a valid HTTP or HTTPS URL.`,
fix: 'Enter an absolute URL, such as `http://localhost:3000/about`.',
},
DP_OG_0002: {
why: (p: { url: string, reason: string }) => `Could not fetch Open Graph metadata from "${p.url}": ${p.reason}`,
fix: 'Check that the target server is running and reachable from the devframe process.',
},
DP_OG_0003: {
why: (p: { url: string, status: number }) => `The target "${p.url}" returned HTTP ${p.status}.`,
fix: 'Inspect a route that returns a successful HTML response.',
},
DP_OG_0004: {
why: (p: { url: string, size: number }) => `The HTML response from "${p.url}" is ${p.size} bytes, which exceeds the 2 MB inspection limit.`,
fix: 'Inspect a smaller HTML document or reduce the response payload.',
},
DP_OG_0005: {
why: 'A static Open Graph report requires a default URL to inspect.',
fix: 'Pass `defaultUrl` to `createOgDevframe()` before running the build adapter.',
},
},
})
60 changes: 60 additions & 0 deletions plugins/og/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { DevframeDefinition } from 'devframe/types'
import type { OgFetch } from './types'
import { existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { defineDevframe } from 'devframe/types'
import pkg from '../package.json' with { type: 'json' }
import { setupOg } from './node/index'

const DEFAULT_ID = 'devframes_plugin_og'
const distDir = fileURLToPath(new URL('../dist/spa', import.meta.url))

export interface OgDevframeOptions {
id?: string
name?: string
icon?: string
basePath?: string
port?: number
/** Require the trust handshake. Enabled by default for the network-capable backend. */
auth?: boolean
/** URL inspected when the UI does not supply one and baked by `build`. */
defaultUrl?: string
/** Override the request implementation, primarily for custom transports and tests. */
fetch?: OgFetch
}

/**
* Create the standalone Open Graph viewer definition.
*
* @experimental This plugin is experimental and may change without a major
* version bump until it stabilizes.
*/
export function createOgDevframe(options: OgDevframeOptions = {}): DevframeDefinition {
const id = options.id ?? DEFAULT_ID
return defineDevframe({
id,
name: options.name ?? 'Open Graph',
version: pkg.version,
packageName: pkg.name,
homepage: pkg.homepage,
description: pkg.description,
icon: options.icon ?? 'ph:image-square-duotone',
basePath: options.basePath,
cli: {
command: id,
port: options.port ?? 9016,
distDir: existsSync(distDir) ? distDir : undefined,
auth: options.auth ?? true,
},
spa: { loader: 'query' },
dock: { category: '~builtin' },
setup(ctx) {
setupOg(ctx, { defaultUrl: options.defaultUrl, fetch: options.fetch })
},
})
}

const ogDevframe: DevframeDefinition = createOgDevframe()

export default ogDevframe
export type { OgFetch, OgHeadTag, OgHeadTagName, OgResolveInput, OgSnapshot } from './types'
10 changes: 10 additions & 0 deletions plugins/og/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { DevframeNodeContext } from 'devframe/types'
import type { ResolveMetadataOptions } from '../rpc/functions/resolve-metadata'
import { createResolveMetadataRpc, resolveMetadata } from '../rpc/functions/resolve-metadata'

export function setupOg(ctx: DevframeNodeContext, options: ResolveMetadataOptions = {}): void {
const hasOptions = options.defaultUrl !== undefined || options.fetch !== undefined
ctx.rpc.register(hasOptions ? createResolveMetadataRpc(options) : resolveMetadata)
}

export { fetchOgMetadata, parseOgMetadata } from './metadata'
Loading
Loading