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 @@ -108,6 +108,12 @@ export const alias = {
'@devframes/plugin-messages/cli': p('messages/src/cli.ts'),
'@devframes/plugin-messages/vite': p('messages/src/vite.ts'),
'@devframes/plugin-messages': p('messages/src/index.ts'),
'@devframes/plugin-assets/client': p('assets/src/client/index.ts'),
'@devframes/plugin-assets/node': p('assets/src/node/index.ts'),
'@devframes/plugin-assets/rpc': p('assets/src/rpc/index.ts'),
'@devframes/plugin-assets/cli': p('assets/src/cli.ts'),
'@devframes/plugin-assets/vite': p('assets/src/vite.ts'),
'@devframes/plugin-assets': p('assets/src/index.ts'),
}

// update tsconfig.base.json — CSS aliases exist for Vite resolution only;
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function pluginsItems(prefix: string) {
{ text: 'Git', link: `${prefix}/plugins/git` },
{ text: 'Terminals', link: `${prefix}/plugins/terminals` },
{ text: 'Code Server', link: `${prefix}/plugins/code-server` },
{ text: 'Assets', link: `${prefix}/plugins/assets` },
] satisfies DefaultTheme.NavItemWithLink[]
}

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

# DF0042: Static Build Disabled By The Definition

## Message

> "`{id}`" declares `capabilities.build: false` — its static export is not meaningful (writes are excluded and any live-served data won't be there).

## Cause

`createBuild` runs unconditionally when called directly, but a definition can opt out of static export via `capabilities.build: false` — typically because the devframe is inherently live (it manages real files on disk, spawns a process, etc.) and a `mode: 'build'` export would only ever produce a broken, write-less shell of the tool. `createCac` already skips registering the `build` subcommand for such a definition; this diagnostic covers the remaining path — a caller invoking `createBuild()` directly, bypassing the CLI.

## Example

```ts
// ✗ Bad — builds a devframe that opted out of static export
await createBuild(assetsDevframe) // throws DF0042

// ✓ Good — the degraded export is still useful to you
await createBuild(assetsDevframe, { force: true })
```

## Fix

- Pass `{ force: true }` to `createBuild()` if the degraded export is still useful to you.
- Otherwise, drop `capabilities.build: false` on the definition if a static export should be supported after all.

## Source

- [`packages/devframe/src/adapters/build.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/build.ts) — `createBuild()` throws this when `capabilities.build` is `false` and `force` isn't set.
90 changes: 90 additions & 0 deletions docs/plugins/assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
outline: deep
---

# Assets

Browse, preview, upload, rename, and delete the files in a directory, built as a **Vue** SPA on `@antfu/design` — a framework-neutral port of Nuxt DevTools' Assets tab.

Package: `@devframes/plugin-assets` · framework: **Vue + @antfu/design**

## What it does

Search by name and filter by type from an inline chip row, switch between a thumbnail grid (grouped by folder) and a file tree, and open a resizable right-hand details panel with a live preview (image, video, audio, font, or text), file metadata, and ready-to-copy usage snippets (`<img>`, CSS `background-image`, `@font-face`, a download link). Upload files with the toolbar button (native file picker) or by dropping them anywhere on the frame, and select multiple assets to delete them together. A live file watcher keeps every connected client's listing in sync with changes made outside the UI.

The standalone server requires devframe's trust handshake by default because it can read, write, and delete real files. Uploads, renames, deletes, and folder creation are enabled by default — pass `{ write: false }` (or `--read-only` on the standalone CLI) for a browse-only deployment.

## Standalone

```sh
pnpx @devframes/plugin-assets # manages <cwd>/public
pnpx @devframes/plugin-assets --read-only # disable upload / rename / delete / mkdir
```

## Mount into a Vite host

```ts
// vite.config.ts
import { assetsVitePlugin } from '@devframes/plugin-assets/vite'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
assetsVitePlugin(),
],
})
```

## Programmatic

`createAssetsDevframe(options)` returns a definition you can deploy through any adapter:

```ts
import { createAssetsDevframe } from '@devframes/plugin-assets'

export default createAssetsDevframe({
dir: 'static', // defaults to `<cwd>/public`
baseURL: '/', // the URL the host serves `dir` at
write: true,
uploadExtensions: ['png', 'jpg', 'svg', 'webp'], // defaults to Nuxt DevTools' own allow-list, or '*' for any
})
```

| Option | Default | Description |
|--------|---------|-------------|
| `dir` | `<cwd>/public` | Directory this devframe manages. |
| `baseURL` | `/` | URL base the host serves `dir` at — each asset's `publicPath` is `baseURL` + its path. Match a non-root deployment base (e.g. Nuxt's `app.baseURL`). |
| `write` | `true` | Enable upload, rename, delete, and folder creation from the UI. |
| `uploadExtensions` | Nuxt DevTools' allow-list | Extensions `upload` accepts, or `'*'` for any. |
| `serveStatic` | `false` | Serve the directory's bytes from this devframe itself. Left off when mounted into a host that already serves `public/`; the standalone CLI turns it on. |
| `build` | `false` | Register the `build` CLI subcommand. See [why it's off by default](#static-export) below. |

## How previews are served

Asset previews (`<img>`, `<video>`, download links) load the files by their **public URL**, and the host the plugin is mounted into serves those files — Vite, Nuxt, and most frameworks already serve their `public/` folder at `/`. The plugin never stands up its own byte-serving route; it just resolves each asset's `publicPath` as `baseURL` + the file's path. Point `baseURL` at wherever the host serves `dir` (the default `/` matches the usual `public/` convention). The standalone CLI (`pnpx @devframes/plugin-assets`) is its own host, so it flips `serveStatic` on and serves the directory under a dedicated base.

## RPC surface

All functions are namespaced `devframes:plugin:assets:*`:

| Function | Type | Notes |
|----------|------|-------|
| `list` | `query`, `snapshot: true` | Every file under the managed directory, with type, size, and last-modified time. |
| `capabilities` | `query`, `snapshot: true` | Whether write actions are enabled, and the upload allow-list — lets the UI gate itself proactively. |
| `read-image-meta` | `query` | Width, height, and orientation for an image asset. |
| `read-text` | `query` | Truncated text content, for preview. |
| `upload` | `action` | Allocates a streaming upload slot; the client pipes the file's bytes over the paired channel. |
| `rename` | `action` | Renames an asset within its folder, preserving its extension. |
| `delete` | `action` | Deletes one or more assets in a single call. |
| `mkdir` | `action` | Creates a folder, including missing parents. |
| `open-in-editor` / `reveal-in-folder` | `action` | Launch the asset in your editor, or reveal its containing folder in the OS file manager. Always registered, regardless of `write`. |

`upload` / `rename` / `delete` / `mkdir` are registered only when `write` is enabled.

## Static export

Every devframe's `build` CLI subcommand is disabled here by default (`capabilities: { build: false }`). A static export has no live host serving the files, and every write action is inherently excluded from a static dump. Rather than ship a broken, preview-less, write-less shell of the tool, the `build` command is simply not registered. Pass `{ build: true }` to `createAssetsDevframe()` (and `{ force: true }` if calling `createBuild()` directly) if that degraded export is still useful to you — the file listing itself still bakes into the static RPC dump.

## Source

[`plugins/assets`](https://github.com/devframes/devframe/tree/main/plugins/assets)
2 changes: 2 additions & 0 deletions docs/plugins/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Each plugin is built with a **different UI framework**. That is deliberate: devf
| [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. |
| [Code Server](./code-server) | Vue | Launch VS Code in the browser (code-server, `code serve-web`, or a tunnel) on demand and embed it in an auto-authenticated iframe. |
| [Assets](./assets) | Vue | Browse, preview, upload, rename, and delete the files in a managed directory. |

## One client, any framework

Expand All @@ -32,6 +33,7 @@ Most plugins publish a `bin`, so the quickest path is `pnpx`:
pnpx @devframes/plugin-inspect # the Devframe Inspector, standalone
pnpx @devframes/plugin-og # inspect Open Graph metadata and social cards
pnpx @devframes/plugin-git # the Git dashboard against the current repo
pnpx @devframes/plugin-assets # manage the files under <cwd>/public
```

Each also exports a `create…Devframe` factory (or, for the Accessibility Inspector, a ready-made definition) you can drive through any adapter — see the individual pages for the factory name, options, and host-mount snippets.
1 change: 1 addition & 0 deletions examples/next-devframe-hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@devframes/json-render": "workspace:*",
"@devframes/next": "workspace:*",
"@devframes/plugin-a11y": "workspace:*",
"@devframes/plugin-assets": "workspace:*",
"@devframes/plugin-code-server": "workspace:*",
"@devframes/plugin-git": "workspace:*",
"@devframes/plugin-inspect": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { StartedServer } from 'devframe/node'
import type { ConnectionMeta, DevframeDefinition } from 'devframe/types'
import { homedir } from 'node:os'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { defineHubRpcFunction } from '@devframes/hub'
import { createHubContext, mountDevframe } from '@devframes/hub/node'
import { toJsonRenderDockEntry } from '@devframes/json-render/hub'
Expand Down Expand Up @@ -45,6 +46,24 @@ async function loadBuiltinPlugins(): Promise<DevframeDefinition[]> {
return mods.map(mod => mod.default as DevframeDefinition)
}

/**
* Load the assets plugin and point its managed directory at this Next app's
* `public/` (`src/client/public`) — the exact directory Next serves at `/`,
* so the tab's previews resolve to real host URLs. Loaded through the same
* bundler-ignored dynamic `import()` as the other plugins (its node code and
* `import.meta.url`-based SPA-dist lookup don't survive static bundling), and
* mounted with `watch: false` so no background file watcher lingers when the
* hub is booted in a short-lived context (e.g. the example's own test).
*/
async function loadAssetsDevframe(): Promise<DevframeDefinition> {
const mod = await import(/* webpackIgnore: true */ /* turbopackIgnore: true */ '@devframes/plugin-assets')
// String path ops on the module path (not `new URL('../public', …)`, which
// Turbopack eagerly resolves as an asset import and fails on a directory) —
// `src/client/devframe/` → `src/client/public`, the dir Next serves at `/`.
const dir = join(dirname(fileURLToPath(import.meta.url)), '../public')
return (mod.createAssetsDevframe as (options: { dir: string, watch: boolean }) => DevframeDefinition)({ dir, watch: false })
}

/** URL base the a11y agent module is served under (same-origin, catch-all route). */
const A11Y_AGENT_MOUNT_BASE = '/__df-a11y-agent/'

Expand Down Expand Up @@ -190,7 +209,7 @@ export async function nextDevframeHub(

// Demo devframes alongside the dogfooded built-in plugin packages.
const devframes = options.devframes
?? [demoDevframe, demoDevframeB, ...await loadBuiltinPlugins()]
?? [demoDevframe, demoDevframeB, ...await loadBuiltinPlugins(), await loadAssetsDevframe()]

await context.messages.add({
level: 'success',
Expand Down
6 changes: 6 additions & 0 deletions examples/next-devframe-hub/src/client/public/data/notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Next hub assets

These files live in the Next app's `public/` directory (`src/client/public`),
served by Next at the site root (`/`). The **Assets** dock is mounted with its
`dir` pointed here, so previews load from Next's real URLs (`/logo.svg`,
`/images/…`) and write actions round-trip to disk.
12 changes: 12 additions & 0 deletions examples/next-devframe-hub/src/client/public/data/site.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "Devframe Assets",
"description": "Sample fixtures for the assets plugin dev server.",
"theme": {
"primary": "#3a6a45",
"surface": "#ffffff"
},
"nav": [
{ "label": "Home", "href": "/" },
{ "label": "Docs", "href": "/docs" }
]
}
4 changes: 4 additions & 0 deletions examples/next-devframe-hub/src/client/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions examples/next-devframe-hub/src/client/public/images/banner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions examples/next-devframe-hub/src/client/public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions examples/next-devframe-hub/src/client/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Allow: /
11 changes: 11 additions & 0 deletions examples/next-devframe-hub/src/client/public/styles/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:root {
--color-primary: #3a6a45;
--color-surface: #ffffff;
--radius: 0.5rem;
}

.button {
background: var(--color-primary);
border-radius: var(--radius);
color: #fff;
}
2 changes: 2 additions & 0 deletions examples/next-devframe-hub/tests/next-devframe-hub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ describe('next-devframe-hub (example)', () => {
// The dogfooded built-in plugin packages mount their own docks.
expect(dockIds).toContain('devframes_plugin_terminals')
expect(dockIds).toContain('devframes_plugin_messages')
// The assets plugin is mounted with its dir pointed at Next's public/.
expect(dockIds).toContain('devframes_plugin_assets')
})

it('lists startup and demo messages through the kit-local RPC', async () => {
Expand Down
1 change: 1 addition & 0 deletions examples/vite-devframe-hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@devframes/json-render": "workspace:*",
"@devframes/json-render-ui": "workspace:*",
"@devframes/plugin-a11y": "workspace:*",
"@devframes/plugin-assets": "workspace:*",
"@devframes/plugin-code-server": "workspace:*",
"@devframes/plugin-data-inspector": "workspace:*",
"@devframes/plugin-git": "workspace:*",
Expand Down
8 changes: 8 additions & 0 deletions examples/vite-devframe-hub/public/data/notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Vite hub assets

These files live in the Vite hub's `public/` directory, served by Vite at
the site root (`/`). The **Assets** dock manages this same directory, so:

- previews load from the host's real URLs (`/logo.svg`, `/images/…`),
- editing, renaming, deleting, and uploading all round-trip to disk,
- the listing refreshes live as files change.
12 changes: 12 additions & 0 deletions examples/vite-devframe-hub/public/data/site.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "Devframe Assets",
"description": "Sample fixtures for the assets plugin dev server.",
"theme": {
"primary": "#3a6a45",
"surface": "#ffffff"
},
"nav": [
{ "label": "Home", "href": "/" },
{ "label": "Docs", "href": "/docs" }
]
}
4 changes: 4 additions & 0 deletions examples/vite-devframe-hub/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions examples/vite-devframe-hub/public/images/banner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions examples/vite-devframe-hub/public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions examples/vite-devframe-hub/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Allow: /
11 changes: 11 additions & 0 deletions examples/vite-devframe-hub/public/styles/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:root {
--color-primary: #3a6a45;
--color-surface: #ffffff;
--radius: 0.5rem;
}

.button {
background: var(--color-primary);
border-radius: var(--radius);
color: #fff;
}
2 changes: 2 additions & 0 deletions examples/vite-devframe-hub/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mountDevframe } from '@devframes/hub/node'
import { toJsonRenderDockEntry } from '@devframes/json-render/hub'
import a11yDevframe, { a11yAgentBundlePath } from '@devframes/plugin-a11y'
import assetsDevframe from '@devframes/plugin-assets'
import codeServerDevframe from '@devframes/plugin-code-server'
import dataInspectorDevframe from '@devframes/plugin-data-inspector'
import { registerDataSource } from '@devframes/plugin-data-inspector/registry'
Expand Down Expand Up @@ -72,6 +73,7 @@ export default defineConfig({
a11yDevframe,
messagesDevframe,
ogDevframe,
assetsDevframe,
],
// 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
Loading
Loading