@devframes/plugin-assets 0.9.8 → 0.9.10
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/README.md +4 -4
- package/bin.mjs +1 -1
- package/dist/{cli.d.mts → node/cli.d.mts} +1 -1
- package/dist/{cli.mjs → node/cli.mjs} +2 -2
- package/dist/node/index.d.mts +68 -85
- package/dist/node/index.mjs +2 -3
- package/dist/{rpc → node/rpc}/index.d.mts +14 -14
- package/dist/{rpc → node/rpc}/index.mjs +1 -1
- package/dist/node/setup.d.mts +102 -0
- package/dist/node/setup.mjs +3 -0
- package/dist/{src-CPYq9rfy.mjs → node-CYRI6r4z.mjs} +13 -5
- package/dist/{rpc-BOraEthN.mjs → rpc-DG9tV_c_.mjs} +107 -34
- package/dist/{node-Cit_U9yi.mjs → setup-BIpnpKP0.mjs} +12 -6
- package/dist/{types-BrE0klRv.d.mts → types-Bspm5Ylo.d.mts} +3 -3
- package/package.json +18 -17
- package/dist/client/index.d.mts +0 -6
- package/dist/client/index.mjs +0 -7
- package/dist/index.d.mts +0 -79
- package/dist/index.mjs +0 -2
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# `@devframes/plugin-assets`
|
|
2
2
|
|
|
3
|
-
Browse, preview, upload, rename, and delete the files in a directory
|
|
3
|
+
Browse, preview, upload, rename, and delete the files in a directory: a framework-neutral port of Nuxt DevTools' Assets tab, built as a **Vue** SPA on `@antfu/design`.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
pnpx @devframes/plugin-assets
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Manages `<cwd>/public` by default. The package exports `createAssetsDevframe()` for custom definitions
|
|
9
|
+
Manages `<cwd>/public` by default. The package exports `createAssetsDevframe()` for custom definitions; mount it into a Vite host with `devframeVite()` from `@devframes/vite/single`. Pass `{ dir }` to manage a different directory, `{ baseURL }` to match where the host framework serves it, or `{ write: false }` (`--read-only` on the standalone CLI) for a browse-only deployment.
|
|
10
10
|
|
|
11
|
-
Asset previews load files by their public URL, served by the host framework the devframe is mounted into (Vite/Nuxt/etc. already serve `public/` at `/`)
|
|
11
|
+
Asset previews load files by their public URL, served by the host framework the devframe is mounted into (Vite/Nuxt/etc. already serve `public/` at `/`), so the devframe doesn't serve the bytes itself. The standalone CLI serves the directory for you.
|
|
12
12
|
|
|
13
|
-
The standalone server requires devframe's trust handshake by default because it can read, write, and delete real files. The `build` CLI subcommand is disabled by default
|
|
13
|
+
The standalone server requires devframe's trust handshake by default because it can read, write, and delete real files. The `build` CLI subcommand is disabled by default; see the docs page for why.
|
package/bin.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as createAssetsDevframe } from "
|
|
1
|
+
import { t as createAssetsDevframe } from "../node-CYRI6r4z.mjs";
|
|
2
2
|
import { createCac } from "devframe/adapters/cac";
|
|
3
|
-
//#region src/cli.ts
|
|
3
|
+
//#region src/node/cli.ts
|
|
4
4
|
function createAssetsCli() {
|
|
5
5
|
return createCac(createAssetsDevframe({ serveStatic: true }));
|
|
6
6
|
}
|
package/dist/node/index.d.mts
CHANGED
|
@@ -1,96 +1,79 @@
|
|
|
1
|
-
import { n as AssetInfo, r as AssetType } from "../types-
|
|
2
|
-
import {
|
|
3
|
-
//#region src/node/
|
|
4
|
-
interface
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
write: boolean;
|
|
9
|
-
/** Extensions `upload` accepts, or `'*'` to accept any. */
|
|
10
|
-
uploadExtensions: readonly string[] | '*';
|
|
1
|
+
import { a as DEFAULT_ALLOWED_UPLOAD_EXTENSIONS, i as CodeSnippet, n as AssetInfo, r as AssetType, t as AssetImageMeta } from "../types-Bspm5Ylo.mjs";
|
|
2
|
+
import { DevframeDefinition } from "devframe";
|
|
3
|
+
//#region src/node/index.d.ts
|
|
4
|
+
interface AssetsDevframeOptions {
|
|
5
|
+
id?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
icon?: string;
|
|
11
8
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Directory this devframe manages. Defaults to `<cwd>/public`, the
|
|
10
|
+
* conventional static-asset folder for Nuxt, Vite, and most static-site
|
|
11
|
+
* frameworks. Pass an absolute path, or one relative to the devframe's
|
|
12
|
+
* own `cwd`.
|
|
15
13
|
*/
|
|
16
|
-
|
|
17
|
-
/** The upload streaming channel, created once in `setupAssets` when `write` is enabled. */
|
|
18
|
-
uploadChannel?: RpcStreamingChannel<Uint8Array>;
|
|
19
|
-
}
|
|
20
|
-
interface AssetsContext extends AssetsConfig {
|
|
21
|
-
/** Resolve a root-relative path to an absolute one, rejecting escapes. */
|
|
22
|
-
resolvePath: (relativePath: string) => string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Record the managed directory and write policy for a context. Called from
|
|
26
|
-
* the devframe's `setup` before any RPC handler runs.
|
|
27
|
-
*/
|
|
28
|
-
declare function configureAssets(ctx: DevframeNodeContext, config: AssetsConfig): void;
|
|
29
|
-
/**
|
|
30
|
-
* Per-`DevframeNodeContext` assets state. Each RPC function pulls its
|
|
31
|
-
* managed directory and path resolver from here instead of re-reading
|
|
32
|
-
* `configureAssets`'s config on every call.
|
|
33
|
-
*/
|
|
34
|
-
declare function getAssetsContext(ctx: DevframeNodeContext): AssetsContext;
|
|
35
|
-
//#endregion
|
|
36
|
-
//#region src/node/paths.d.ts
|
|
37
|
-
/**
|
|
38
|
-
* Resolve a client-supplied, root-relative path against the managed
|
|
39
|
-
* directory, rejecting anything that would escape it (`..` traversal, a
|
|
40
|
-
* rogue absolute path, etc.). Every RPC handler that touches the
|
|
41
|
-
* filesystem goes through this — never trust a path from the wire.
|
|
42
|
-
*/
|
|
43
|
-
declare function resolveAssetPath(root: string, relativePath: string): string;
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/node/scanner.d.ts
|
|
46
|
-
/** Classifies a path by extension, driving preview and icon choice. */
|
|
47
|
-
declare function guessAssetType(path: string): AssetType;
|
|
48
|
-
/** Recursively lists every file under `dir`, sorted alphabetically by path. */
|
|
49
|
-
declare function scanAssets(dir: string, baseURL: string, includeFsPath?: boolean): Promise<AssetInfo[]>;
|
|
50
|
-
//#endregion
|
|
51
|
-
//#region src/node/index.d.ts
|
|
52
|
-
interface SetupAssetsOptions {
|
|
53
|
-
/** Directory this devframe manages. */
|
|
54
|
-
dir: string;
|
|
55
|
-
/** Whether upload / rename / delete / mkdir are registered. */
|
|
56
|
-
write: boolean;
|
|
57
|
-
/** Extensions `upload` accepts, or `'*'` to accept any. */
|
|
58
|
-
uploadExtensions: readonly string[] | '*';
|
|
14
|
+
dir?: string;
|
|
59
15
|
/**
|
|
60
|
-
* URL base the managed directory
|
|
61
|
-
* `publicPath
|
|
16
|
+
* URL base the **host** serves the managed directory at, used to build
|
|
17
|
+
* each asset's `publicPath`. Defaults to `/`; Vite, Nuxt, and most
|
|
18
|
+
* frameworks serve their `public/` folder at the site root. Set this to
|
|
19
|
+
* match a non-root deployment base (e.g. Nuxt's `app.baseURL`).
|
|
62
20
|
*/
|
|
63
|
-
baseURL
|
|
21
|
+
baseURL?: string;
|
|
64
22
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* already serves `public/` at {@link baseURL}
|
|
68
|
-
*
|
|
23
|
+
* Serve the managed directory's bytes from this devframe itself.
|
|
24
|
+
* Defaults to `false`: when mounted into a host (Vite/Nuxt/…) that host
|
|
25
|
+
* already serves `public/` at {@link baseURL}, so the plugin references
|
|
26
|
+
* those URLs instead of standing up its own route. The standalone CLI
|
|
27
|
+
* sets this to `true` (it is its own host), serving the directory under
|
|
28
|
+
* a dedicated base so it doesn't collide with the SPA at `/`.
|
|
69
29
|
*/
|
|
70
|
-
serveStatic
|
|
71
|
-
|
|
72
|
-
|
|
30
|
+
serveStatic?: boolean;
|
|
31
|
+
basePath?: string;
|
|
32
|
+
distDir?: string;
|
|
33
|
+
/** Preferred standalone CLI port (default 9015). */
|
|
34
|
+
port?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Enable upload, rename, delete, and folder creation from the UI.
|
|
37
|
+
* Enabled by default; set `false` (or pass `--read-only` on the
|
|
38
|
+
* standalone CLI) for a browse-only deployment.
|
|
39
|
+
*/
|
|
40
|
+
write?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Extensions `upload` accepts, or `'*'` to accept any file. Defaults to
|
|
43
|
+
* the same allow-list Nuxt DevTools ships.
|
|
44
|
+
*/
|
|
45
|
+
uploadExtensions?: readonly string[] | '*';
|
|
46
|
+
/**
|
|
47
|
+
* Watch the managed directory in dev mode and push live listing updates.
|
|
48
|
+
* Enabled by default. Set `false` to skip the file watcher, useful when a
|
|
49
|
+
* host mounts the devframe in a short-lived or repeatedly-recreated
|
|
50
|
+
* context (tests, some SSR dev servers) where a background watcher isn't
|
|
51
|
+
* wanted.
|
|
52
|
+
*/
|
|
53
|
+
watch?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Require the trust handshake on the standalone server. Enabled by
|
|
56
|
+
* default, because this devframe can read, write, and delete real files.
|
|
57
|
+
*/
|
|
58
|
+
auth?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Register the `build` CLI subcommand. Disabled by default: a static
|
|
61
|
+
* export can only ever list file metadata from a baked snapshot; there
|
|
62
|
+
* is no live host serving the files, and every write action is inherently
|
|
63
|
+
* excluded from a static dump, so the command would produce a broken,
|
|
64
|
+
* write-less shell of the tool. Opt back in if that degraded export is
|
|
65
|
+
* still useful to you.
|
|
66
|
+
*/
|
|
67
|
+
build?: boolean;
|
|
73
68
|
}
|
|
74
69
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
70
|
+
* Create the assets manager devframe, a framework-neutral port of Nuxt
|
|
71
|
+
* DevTools' Assets tab. Mount it into any host via devframe's adapters, or
|
|
72
|
+
* run it standalone with the bundled CLI (`devframe-assets`).
|
|
78
73
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* already serves its `public/` dir at {@link SetupAssetsOptions.baseURL}.
|
|
82
|
-
* The plugin only serves them itself when `serveStatic` is set (the
|
|
83
|
-
* standalone CLI, which is its own host).
|
|
84
|
-
*
|
|
85
|
-
* Called from the definition's `setup(ctx)` and reusable by host adapters
|
|
86
|
-
* that wire their own context.
|
|
87
|
-
*/
|
|
88
|
-
declare function setupAssets(ctx: DevframeNodeContext, options: SetupAssetsOptions): Promise<void>;
|
|
89
|
-
/**
|
|
90
|
-
* Stop the live file watcher started by {@link setupAssets}. Test harnesses
|
|
91
|
-
* wrap their dev-server `close()` with this — see `test/_utils.ts` — so a
|
|
92
|
-
* leaked chokidar watcher doesn't keep the process alive.
|
|
74
|
+
* @experimental This plugin is experimental and may change without a major
|
|
75
|
+
* version bump until it stabilizes.
|
|
93
76
|
*/
|
|
94
|
-
declare function
|
|
77
|
+
declare function createAssetsDevframe(options?: AssetsDevframeOptions): DevframeDefinition;
|
|
95
78
|
//#endregion
|
|
96
|
-
export {
|
|
79
|
+
export { type AssetImageMeta, type AssetInfo, type AssetType, AssetsDevframeOptions, type CodeSnippet, DEFAULT_ALLOWED_UPLOAD_EXTENSIONS, createAssetsDevframe, createAssetsDevframe as default };
|
package/dist/node/index.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import { n as
|
|
2
|
-
|
|
3
|
-
export { configureAssets, disposeAssetsWatcher, getAssetsContext, guessAssetType, resolveAssetPath, scanAssets, setupAssets };
|
|
1
|
+
import { n as DEFAULT_ALLOWED_UPLOAD_EXTENSIONS, t as createAssetsDevframe } from "../node-CYRI6r4z.mjs";
|
|
2
|
+
export { DEFAULT_ALLOWED_UPLOAD_EXTENSIONS, createAssetsDevframe, createAssetsDevframe as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DevframeNodeContext } from "devframe";
|
|
2
2
|
import { RpcDefinitionsToFunctions } from "devframe/rpc";
|
|
3
|
-
//#region src/rpc/functions/capabilities.d.ts
|
|
3
|
+
//#region src/node/rpc/functions/capabilities.d.ts
|
|
4
4
|
interface AssetsCapabilities {
|
|
5
5
|
write: boolean;
|
|
6
6
|
uploadExtensions: readonly string[] | '*';
|
|
@@ -8,7 +8,7 @@ interface AssetsCapabilities {
|
|
|
8
8
|
/**
|
|
9
9
|
* Lets the UI know upfront whether upload / rename / delete / mkdir are
|
|
10
10
|
* available, so it can hide or disable those affordances proactively
|
|
11
|
-
* instead of letting the user hit a "method not found" error
|
|
11
|
+
* instead of letting the user hit a "method not found" error, the same
|
|
12
12
|
* `canWrite`-gating idea the git plugin's `GitStatus.canWrite` follows.
|
|
13
13
|
*/
|
|
14
14
|
declare const capabilities: {
|
|
@@ -48,7 +48,7 @@ declare const capabilities: {
|
|
|
48
48
|
}>>> | undefined;
|
|
49
49
|
};
|
|
50
50
|
//#endregion
|
|
51
|
-
//#region src/rpc/functions/delete.d.ts
|
|
51
|
+
//#region src/node/rpc/functions/delete.d.ts
|
|
52
52
|
/** One request covers both single- and multi-select delete. */
|
|
53
53
|
declare const deleteAssets: {
|
|
54
54
|
name: "devframes:plugin:assets:delete";
|
|
@@ -94,7 +94,7 @@ declare const deleteAssets: {
|
|
|
94
94
|
}>>> | undefined;
|
|
95
95
|
};
|
|
96
96
|
//#endregion
|
|
97
|
-
//#region src/rpc/functions/list.d.ts
|
|
97
|
+
//#region src/node/rpc/functions/list.d.ts
|
|
98
98
|
declare const assetInfoSchema: import("devframe/utils/simple-schema").SimpleSchema<{
|
|
99
99
|
path: string;
|
|
100
100
|
type: "image" | "font" | "video" | "audio" | "text" | "other";
|
|
@@ -175,7 +175,7 @@ declare const list: {
|
|
|
175
175
|
}[]>>> | undefined;
|
|
176
176
|
};
|
|
177
177
|
//#endregion
|
|
178
|
-
//#region src/rpc/functions/mkdir.d.ts
|
|
178
|
+
//#region src/node/rpc/functions/mkdir.d.ts
|
|
179
179
|
declare const mkdir: {
|
|
180
180
|
name: "devframes:plugin:assets:mkdir";
|
|
181
181
|
type?: "action" | undefined;
|
|
@@ -206,7 +206,7 @@ declare const mkdir: {
|
|
|
206
206
|
}], import("devframe/rpc").Thenable<void>>> | undefined;
|
|
207
207
|
};
|
|
208
208
|
//#endregion
|
|
209
|
-
//#region src/rpc/functions/read-image-meta.d.ts
|
|
209
|
+
//#region src/node/rpc/functions/read-image-meta.d.ts
|
|
210
210
|
declare const readImageMeta: {
|
|
211
211
|
name: "devframes:plugin:assets:read-image-meta";
|
|
212
212
|
type?: "query" | undefined;
|
|
@@ -251,7 +251,7 @@ declare const readImageMeta: {
|
|
|
251
251
|
} | null>>> | undefined;
|
|
252
252
|
};
|
|
253
253
|
//#endregion
|
|
254
|
-
//#region src/rpc/functions/read-text.d.ts
|
|
254
|
+
//#region src/node/rpc/functions/read-text.d.ts
|
|
255
255
|
declare const readText: {
|
|
256
256
|
name: "devframes:plugin:assets:read-text";
|
|
257
257
|
type?: "query" | undefined;
|
|
@@ -268,12 +268,12 @@ declare const readText: {
|
|
|
268
268
|
__promise?: import("devframe/rpc").Thenable<import("devframe/rpc").RpcFunctionSetupResult<[string, number | undefined], import("devframe/rpc").Thenable<string | null>>> | undefined;
|
|
269
269
|
};
|
|
270
270
|
//#endregion
|
|
271
|
-
//#region src/rpc/functions/rename.d.ts
|
|
271
|
+
//#region src/node/rpc/functions/rename.d.ts
|
|
272
272
|
interface RenameArgs {
|
|
273
273
|
/** Root-relative path of the asset to rename. */
|
|
274
274
|
path: string;
|
|
275
275
|
/**
|
|
276
|
-
* New base name, without an extension
|
|
276
|
+
* New base name, without an extension; the original extension is
|
|
277
277
|
* always preserved (mirrors Nuxt DevTools' rename dialog exactly). Any
|
|
278
278
|
* extension included here is treated as part of the base name, not
|
|
279
279
|
* stripped.
|
|
@@ -366,12 +366,12 @@ declare const rename: {
|
|
|
366
366
|
}>>> | undefined;
|
|
367
367
|
};
|
|
368
368
|
//#endregion
|
|
369
|
-
//#region src/rpc/functions/upload.d.ts
|
|
369
|
+
//#region src/node/rpc/functions/upload.d.ts
|
|
370
370
|
/** Streaming channel name, namespaced like every other RPC name in this plugin. */
|
|
371
371
|
declare const UPLOAD_CHANNEL = "devframes:plugin:assets:upload";
|
|
372
372
|
/**
|
|
373
373
|
* Allocates an upload and returns a streaming id. Actual bytes flow over
|
|
374
|
-
* the `upload` channel opened in `setupAssets
|
|
374
|
+
* the `upload` channel opened in `setupAssets`; see the client-side
|
|
375
375
|
* `useUpload` hook for the matching `rpc.streaming.upload()` call.
|
|
376
376
|
*/
|
|
377
377
|
declare const upload: {
|
|
@@ -418,8 +418,8 @@ declare const upload: {
|
|
|
418
418
|
}>>> | undefined;
|
|
419
419
|
};
|
|
420
420
|
//#endregion
|
|
421
|
-
//#region src/rpc/index.d.ts
|
|
422
|
-
/** Read-only RPC
|
|
421
|
+
//#region src/node/rpc/index.d.ts
|
|
422
|
+
/** Read-only RPC, always registered. */
|
|
423
423
|
declare const readFunctions: readonly [{
|
|
424
424
|
name: "devframes:plugin:assets:list";
|
|
425
425
|
type?: "query" | undefined;
|
|
@@ -575,7 +575,7 @@ declare const readFunctions: readonly [{
|
|
|
575
575
|
uploadExtensions: string[] | "*";
|
|
576
576
|
}>>> | undefined;
|
|
577
577
|
}];
|
|
578
|
-
/** Mutating RPC
|
|
578
|
+
/** Mutating RPC, registered only when write actions are enabled. */
|
|
579
579
|
declare const writeFunctions: readonly [{
|
|
580
580
|
name: "devframes:plugin:assets:upload";
|
|
581
581
|
type?: "action" | undefined;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as readText, c as assetInfoSchema, f as deleteAssets, h as upload, i as rename, l as list, m as UPLOAD_CHANNEL, n as serverFunctions, o as readImageMeta, p as capabilities, r as writeFunctions, s as mkdir, t as readFunctions } from "
|
|
1
|
+
import { a as readText, c as assetInfoSchema, f as deleteAssets, h as upload, i as rename, l as list, m as UPLOAD_CHANNEL, n as serverFunctions, o as readImageMeta, p as capabilities, r as writeFunctions, s as mkdir, t as readFunctions } from "../../rpc-DG9tV_c_.mjs";
|
|
2
2
|
export { UPLOAD_CHANNEL, assetInfoSchema, capabilities, deleteAssets, list, mkdir, readFunctions, readImageMeta, readText, rename, serverFunctions, upload, writeFunctions };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { n as AssetInfo, r as AssetType } from "../types-Bspm5Ylo.mjs";
|
|
2
|
+
import { DevframeNodeContext, RpcStreamingChannel } from "devframe";
|
|
3
|
+
//#region src/node/context.d.ts
|
|
4
|
+
interface AssetsConfig {
|
|
5
|
+
/** Directory this devframe manages. */
|
|
6
|
+
dir: string;
|
|
7
|
+
/** Whether upload / rename / delete / mkdir are registered. */
|
|
8
|
+
write: boolean;
|
|
9
|
+
/** Extensions `upload` accepts, or `'*'` to accept any. */
|
|
10
|
+
uploadExtensions: readonly string[] | '*';
|
|
11
|
+
/**
|
|
12
|
+
* URL base the managed directory is served at. Normally the host
|
|
13
|
+
* (Vite / Nuxt / …) serves `public/` at `/`, so asset `publicPath`s are
|
|
14
|
+
* `joinURL(baseURL, path)` and the plugin serves nothing itself.
|
|
15
|
+
*/
|
|
16
|
+
baseURL: string;
|
|
17
|
+
/** The upload streaming channel, created once in `setupAssets` when `write` is enabled. */
|
|
18
|
+
uploadChannel?: RpcStreamingChannel<Uint8Array>;
|
|
19
|
+
}
|
|
20
|
+
interface AssetsContext extends AssetsConfig {
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a root-relative path to an absolute one, rejecting lexical
|
|
23
|
+
* escapes. Symlink-aware containment for reads and mutations lives in
|
|
24
|
+
* `node/paths` (`resolveAssetReadPath` / `assertAssetMutationPath`), which
|
|
25
|
+
* the RPC handlers call directly with {@link AssetsContext.dir}.
|
|
26
|
+
*/
|
|
27
|
+
resolvePath: (relativePath: string) => string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Record the managed directory and write policy for a context. Called from
|
|
31
|
+
* the devframe's `setup` before any RPC handler runs.
|
|
32
|
+
*/
|
|
33
|
+
declare function configureAssets(ctx: DevframeNodeContext, config: AssetsConfig): void;
|
|
34
|
+
/**
|
|
35
|
+
* Per-`DevframeNodeContext` assets state. Each RPC function pulls its
|
|
36
|
+
* managed directory and path resolver from here instead of re-reading
|
|
37
|
+
* `configureAssets`'s config on every call.
|
|
38
|
+
*/
|
|
39
|
+
declare function getAssetsContext(ctx: DevframeNodeContext): AssetsContext;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/node/paths.d.ts
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a client-supplied, root-relative path against the managed
|
|
44
|
+
* directory, rejecting anything that would escape it lexically (`..`
|
|
45
|
+
* traversal, a rogue absolute path). The first guard every RPC handler runs;
|
|
46
|
+
* symlink-aware containment is layered on by {@link resolveAssetReadPath}
|
|
47
|
+
* (reads) and {@link assertAssetMutationPath} (mutations).
|
|
48
|
+
*/
|
|
49
|
+
declare function resolveAssetPath(root: string, relativePath: string): string;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/node/scanner.d.ts
|
|
52
|
+
/** Classifies a path by extension, driving preview and icon choice. */
|
|
53
|
+
declare function guessAssetType(path: string): AssetType;
|
|
54
|
+
/** Recursively lists every file under `dir`, sorted alphabetically by path. */
|
|
55
|
+
declare function scanAssets(dir: string, baseURL: string, includeFsPath?: boolean): Promise<AssetInfo[]>;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/node/setup.d.ts
|
|
58
|
+
interface SetupAssetsOptions {
|
|
59
|
+
/** Directory this devframe manages. */
|
|
60
|
+
dir: string;
|
|
61
|
+
/** Whether upload / rename / delete / mkdir are registered. */
|
|
62
|
+
write: boolean;
|
|
63
|
+
/** Extensions `upload` accepts, or `'*'` to accept any. */
|
|
64
|
+
uploadExtensions: readonly string[] | '*';
|
|
65
|
+
/**
|
|
66
|
+
* URL base the managed directory is served at, the base every asset's
|
|
67
|
+
* `publicPath` is resolved against.
|
|
68
|
+
*/
|
|
69
|
+
baseURL: string;
|
|
70
|
+
/**
|
|
71
|
+
* Whether this devframe should serve the managed directory's bytes
|
|
72
|
+
* itself. Left `false` when mounted into a host (Vite / Nuxt / …) that
|
|
73
|
+
* already serves `public/` at {@link baseURL}; set `true` only for the
|
|
74
|
+
* standalone CLI, which is its own host.
|
|
75
|
+
*/
|
|
76
|
+
serveStatic: boolean;
|
|
77
|
+
/** Start the live file watcher (dev mode only). */
|
|
78
|
+
watch: boolean;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Register the assets RPC surface on a devframe node context: ensures the
|
|
82
|
+
* managed directory exists, registers the read RPCs (always) and write
|
|
83
|
+
* RPCs (when enabled), and starts a live file watcher in dev mode.
|
|
84
|
+
*
|
|
85
|
+
* Raw asset bytes (for `<img>`/`<video>`/download previews) are served by
|
|
86
|
+
* the **host** the plugin is attached to: Vite, Nuxt, or any framework
|
|
87
|
+
* already serves its `public/` dir at {@link SetupAssetsOptions.baseURL}.
|
|
88
|
+
* The plugin only serves them itself when `serveStatic` is set (the
|
|
89
|
+
* standalone CLI, which is its own host).
|
|
90
|
+
*
|
|
91
|
+
* Called from the definition's `setup(ctx)` and reusable by host adapters
|
|
92
|
+
* that wire their own context.
|
|
93
|
+
*/
|
|
94
|
+
declare function setupAssets(ctx: DevframeNodeContext, options: SetupAssetsOptions): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Stop the live file watcher started by {@link setupAssets}. Test harnesses
|
|
97
|
+
* wrap their dev-server `close()` with this (see `test/_utils.ts`), so a
|
|
98
|
+
* leaked chokidar watcher doesn't keep the process alive.
|
|
99
|
+
*/
|
|
100
|
+
declare function disposeAssetsWatcher(ctx: DevframeNodeContext): Promise<void>;
|
|
101
|
+
//#endregion
|
|
102
|
+
export { SetupAssetsOptions, configureAssets, disposeAssetsWatcher, getAssetsContext, guessAssetType, resolveAssetPath, scanAssets, setupAssets };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as setupAssets, t as disposeAssetsWatcher } from "../setup-BIpnpKP0.mjs";
|
|
2
|
+
import { _ as getAssetsContext, d as scanAssets, g as configureAssets, u as guessAssetType, v as resolveAssetPath } from "../rpc-DG9tV_c_.mjs";
|
|
3
|
+
export { configureAssets, disposeAssetsWatcher, getAssetsContext, guessAssetType, resolveAssetPath, scanAssets, setupAssets };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { n as setupAssets } from "./
|
|
1
|
+
import { n as setupAssets } from "./setup-BIpnpKP0.mjs";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { defineDevframe } from "devframe";
|
|
4
4
|
import { resolve } from "pathe";
|
|
5
5
|
//#region package.json
|
|
6
6
|
var name = "@devframes/plugin-assets";
|
|
7
|
-
var version = "0.9.
|
|
7
|
+
var version = "0.9.10";
|
|
8
8
|
var description = "Devframe plugin for browsing, previewing, uploading, and managing static assets in a directory.";
|
|
9
9
|
var homepage = "https://github.com/devframes/devframe#readme";
|
|
10
10
|
//#endregion
|
|
11
|
-
//#region src/types.ts
|
|
11
|
+
//#region src/node/types.ts
|
|
12
12
|
/** Mirrors Nuxt DevTools' own default upload allow-list. */
|
|
13
13
|
const DEFAULT_ALLOWED_UPLOAD_EXTENSIONS = [
|
|
14
14
|
"png",
|
|
@@ -32,14 +32,14 @@ const DEFAULT_ALLOWED_UPLOAD_EXTENSIONS = [
|
|
|
32
32
|
"eot"
|
|
33
33
|
];
|
|
34
34
|
//#endregion
|
|
35
|
-
//#region src/index.ts
|
|
35
|
+
//#region src/node/index.ts
|
|
36
36
|
const remoteAssets = {
|
|
37
37
|
package: `${name}--assets`,
|
|
38
38
|
version
|
|
39
39
|
};
|
|
40
40
|
const DEFAULT_ID = "devframes_plugin_assets";
|
|
41
41
|
/**
|
|
42
|
-
* Create the assets manager devframe
|
|
42
|
+
* Create the assets manager devframe, a framework-neutral port of Nuxt
|
|
43
43
|
* DevTools' Assets tab. Mount it into any host via devframe's adapters, or
|
|
44
44
|
* run it standalone with the bundled CLI (`devframe-assets`).
|
|
45
45
|
*
|
|
@@ -74,6 +74,14 @@ function createAssetsDevframe(options = {}) {
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
dock: { category: "~builtin" },
|
|
77
|
+
/**
|
|
78
|
+
* Both wire services are declared, not imperatively installed: devframe
|
|
79
|
+
* constructs them (deep-merging options across every declarer) before
|
|
80
|
+
* setup runs. `service-open` gets the managed dir as an allowed root so
|
|
81
|
+
* out-of-workspace dirs open; the client hits it directly with the
|
|
82
|
+
* dev-only absolute `fsPath`. `service-shiki` backs server-highlighted
|
|
83
|
+
* text previews, with a plain `<pre>` fallback when it isn't advertised.
|
|
84
|
+
*/
|
|
77
85
|
services: [{
|
|
78
86
|
package: "@devframes/service-open",
|
|
79
87
|
options: { roots: [dir] }
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { dirname, extname, join, resolve } from "pathe";
|
|
1
|
+
import { dirname, extname, join, normalize, resolve } from "pathe";
|
|
2
2
|
import { createWriteStream } from "node:fs";
|
|
3
3
|
import fsp from "node:fs/promises";
|
|
4
|
+
import { defineDiagnostics } from "devframe/utils/nostics";
|
|
4
5
|
import { createDefineWrapperWithContext } from "devframe/rpc";
|
|
5
6
|
import { s } from "devframe/utils/simple-schema";
|
|
6
|
-
import { defineDiagnostics } from "devframe/utils/nostics";
|
|
7
7
|
import { glob } from "tinyglobby";
|
|
8
8
|
import { joinURL } from "ufo";
|
|
9
9
|
import { imageMeta } from "image-meta";
|
|
10
|
-
//#region src/diagnostics.ts
|
|
10
|
+
//#region src/node/diagnostics.ts
|
|
11
|
+
/**
|
|
12
|
+
* Uses the plugin's own `DP_ASSETS_` prefix per the built-in plugin
|
|
13
|
+
* convention, keeping it collision-free with devframe core (`DF`) and the
|
|
14
|
+
* hub (`DF8xxx`).
|
|
15
|
+
*/
|
|
11
16
|
const diagnostics = defineDiagnostics({
|
|
12
17
|
docsBase: "https://devfra.me/errors",
|
|
13
18
|
codes: {
|
|
@@ -25,7 +30,7 @@ const diagnostics = defineDiagnostics({
|
|
|
25
30
|
},
|
|
26
31
|
DP_ASSETS_0004: {
|
|
27
32
|
why: (p) => `No asset found at "${p.path}".`,
|
|
28
|
-
fix: "Refresh the asset list
|
|
33
|
+
fix: "Refresh the asset list; it may have been moved or deleted already."
|
|
29
34
|
},
|
|
30
35
|
DP_ASSETS_0005: {
|
|
31
36
|
why: (p) => `Cannot create folder "${p.path}" because a file already exists there.`,
|
|
@@ -37,25 +42,67 @@ const diagnostics = defineDiagnostics({
|
|
|
37
42
|
},
|
|
38
43
|
DP_ASSETS_0007: {
|
|
39
44
|
why: "The upload streaming channel is unavailable because this devframe was set up with `write: false`.",
|
|
40
|
-
fix: "This indicates an internal registration bug
|
|
45
|
+
fix: "This indicates an internal registration bug, since `upload` should never be reachable without `write: true`. Please report it."
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
});
|
|
44
49
|
//#endregion
|
|
45
50
|
//#region src/node/paths.ts
|
|
51
|
+
/** realpath, pathe-normalized, or `null` when the path doesn't exist. */
|
|
52
|
+
async function realpath(path) {
|
|
53
|
+
try {
|
|
54
|
+
return normalize(await fsp.realpath(path));
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
46
59
|
/**
|
|
47
60
|
* Resolve a client-supplied, root-relative path against the managed
|
|
48
|
-
* directory, rejecting anything that would escape it (`..`
|
|
49
|
-
* rogue absolute path
|
|
50
|
-
*
|
|
61
|
+
* directory, rejecting anything that would escape it lexically (`..`
|
|
62
|
+
* traversal, a rogue absolute path). The first guard every RPC handler runs;
|
|
63
|
+
* symlink-aware containment is layered on by {@link resolveAssetReadPath}
|
|
64
|
+
* (reads) and {@link assertAssetMutationPath} (mutations).
|
|
51
65
|
*/
|
|
52
66
|
function resolveAssetPath(root, relativePath) {
|
|
53
|
-
const cleaned = relativePath.replace(/^[/\\]+/, "");
|
|
54
67
|
const normalizedRoot = resolve(root);
|
|
55
|
-
const absolute = resolve(normalizedRoot,
|
|
68
|
+
const absolute = resolve(normalizedRoot, relativePath.replace(/^[/\\]+/, ""));
|
|
56
69
|
if (absolute !== normalizedRoot && !absolute.startsWith(`${normalizedRoot}/`)) throw diagnostics.DP_ASSETS_0001({ path: relativePath });
|
|
57
70
|
return absolute;
|
|
58
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a path for a **read**, allowing a symlink only when its canonical
|
|
74
|
+
* target stays inside the canonical managed root. A target resolving outside
|
|
75
|
+
* throws `DP_ASSETS_0001`; a missing target is left for the caller's own read
|
|
76
|
+
* to fail.
|
|
77
|
+
*/
|
|
78
|
+
async function resolveAssetReadPath(root, relativePath) {
|
|
79
|
+
const absolute = resolveAssetPath(root, relativePath);
|
|
80
|
+
const real = await realpath(absolute);
|
|
81
|
+
const canonRoot = await realpath(root) ?? resolve(root);
|
|
82
|
+
if (real && real !== canonRoot && !real.startsWith(`${canonRoot}/`)) throw diagnostics.DP_ASSETS_0001({ path: relativePath });
|
|
83
|
+
return absolute;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a path for a **mutation**, rejecting every pre-existing symlink
|
|
87
|
+
* among the path components from the managed root down to the target
|
|
88
|
+
* (including in-root symlinks) so a mutation can never follow a symlink out
|
|
89
|
+
* of, or around, the root. Only existing components are inspected, so it is
|
|
90
|
+
* safe for not-yet-created upload/mkdir targets; call it again after
|
|
91
|
+
* creating directories and right before the I/O. This closes deterministic,
|
|
92
|
+
* pre-existing symlink escapes, not concurrent component-swap races.
|
|
93
|
+
*/
|
|
94
|
+
async function assertAssetMutationPath(root, relativePath) {
|
|
95
|
+
const lexRoot = resolve(root);
|
|
96
|
+
const absolute = resolveAssetPath(root, relativePath);
|
|
97
|
+
let current = await realpath(root) ?? lexRoot;
|
|
98
|
+
for (const segment of absolute.slice(lexRoot.length).split("/").filter(Boolean)) {
|
|
99
|
+
current += `/${segment}`;
|
|
100
|
+
const stat = await fsp.lstat(current).catch(() => null);
|
|
101
|
+
if (!stat) break;
|
|
102
|
+
if (stat.isSymbolicLink()) throw diagnostics.DP_ASSETS_0001({ path: relativePath });
|
|
103
|
+
}
|
|
104
|
+
return absolute;
|
|
105
|
+
}
|
|
59
106
|
//#endregion
|
|
60
107
|
//#region src/node/context.ts
|
|
61
108
|
const configs = /* @__PURE__ */ new WeakMap();
|
|
@@ -90,7 +137,7 @@ function getAssetsContext(ctx) {
|
|
|
90
137
|
return built;
|
|
91
138
|
}
|
|
92
139
|
//#endregion
|
|
93
|
-
//#region src/rpc/functions/upload.ts
|
|
140
|
+
//#region src/node/rpc/functions/upload.ts
|
|
94
141
|
const defineAssetsRpc$7 = createDefineWrapperWithContext();
|
|
95
142
|
/** Streaming channel name, namespaced like every other RPC name in this plugin. */
|
|
96
143
|
const UPLOAD_CHANNEL = "devframes:plugin:assets:upload";
|
|
@@ -101,7 +148,7 @@ function isExtensionAllowed(path, allowed) {
|
|
|
101
148
|
}
|
|
102
149
|
/**
|
|
103
150
|
* Allocates an upload and returns a streaming id. Actual bytes flow over
|
|
104
|
-
* the `upload` channel opened in `setupAssets
|
|
151
|
+
* the `upload` channel opened in `setupAssets`; see the client-side
|
|
105
152
|
* `useUpload` hook for the matching `rpc.streaming.upload()` call.
|
|
106
153
|
*/
|
|
107
154
|
const upload = defineAssetsRpc$7({
|
|
@@ -118,14 +165,17 @@ const upload = defineAssetsRpc$7({
|
|
|
118
165
|
},
|
|
119
166
|
setup: (ctx) => {
|
|
120
167
|
const assets = getAssetsContext(ctx);
|
|
121
|
-
return {
|
|
168
|
+
return {
|
|
169
|
+
/** See `list.ts` for why the async handler is cast. */
|
|
170
|
+
handler: (async ({ path }) => {
|
|
122
171
|
if (!isExtensionAllowed(path, assets.uploadExtensions)) throw diagnostics.DP_ASSETS_0002({
|
|
123
172
|
path,
|
|
124
173
|
extension: extname(path).slice(1).toLowerCase() || "(none)",
|
|
125
174
|
allowed: assets.uploadExtensions === "*" ? [] : assets.uploadExtensions
|
|
126
175
|
});
|
|
127
|
-
const absolute = assets.
|
|
176
|
+
const absolute = await assertAssetMutationPath(assets.dir, path);
|
|
128
177
|
await fsp.mkdir(dirname(absolute), { recursive: true });
|
|
178
|
+
await assertAssetMutationPath(assets.dir, path);
|
|
129
179
|
const channel = assets.uploadChannel;
|
|
130
180
|
if (!channel) throw diagnostics.DP_ASSETS_0007();
|
|
131
181
|
const reader = channel.openInbound();
|
|
@@ -147,7 +197,7 @@ const upload = defineAssetsRpc$7({
|
|
|
147
197
|
/**
|
|
148
198
|
* Lets the UI know upfront whether upload / rename / delete / mkdir are
|
|
149
199
|
* available, so it can hide or disable those affordances proactively
|
|
150
|
-
* instead of letting the user hit a "method not found" error
|
|
200
|
+
* instead of letting the user hit a "method not found" error, the same
|
|
151
201
|
* `canWrite`-gating idea the git plugin's `GitStatus.canWrite` follows.
|
|
152
202
|
*/
|
|
153
203
|
const capabilities = createDefineWrapperWithContext()({
|
|
@@ -189,10 +239,12 @@ const deleteAssets = createDefineWrapperWithContext()({
|
|
|
189
239
|
},
|
|
190
240
|
setup: (ctx) => {
|
|
191
241
|
const assets = getAssetsContext(ctx);
|
|
192
|
-
return {
|
|
242
|
+
return {
|
|
243
|
+
/** See `list.ts` for why the async handler is cast. */
|
|
244
|
+
handler: (async ({ paths }) => {
|
|
193
245
|
const deleted = [];
|
|
194
246
|
for (const path of paths) {
|
|
195
|
-
const absolute = assets.
|
|
247
|
+
const absolute = await assertAssetMutationPath(assets.dir, path);
|
|
196
248
|
try {
|
|
197
249
|
await fsp.unlink(absolute);
|
|
198
250
|
deleted.push(path);
|
|
@@ -245,18 +297,21 @@ async function scanAssets(dir, baseURL, includeFsPath = false) {
|
|
|
245
297
|
const files = await glob(["**/*"], {
|
|
246
298
|
cwd: dir,
|
|
247
299
|
onlyFiles: true,
|
|
248
|
-
dot: false
|
|
300
|
+
dot: false,
|
|
301
|
+
followSymbolicLinks: false
|
|
249
302
|
});
|
|
250
303
|
return (await Promise.all(files.map(async (relPath) => {
|
|
251
304
|
try {
|
|
252
|
-
|
|
305
|
+
const stat = await fsp.lstat(join(dir, relPath));
|
|
306
|
+
if (stat.isSymbolicLink()) return void 0;
|
|
307
|
+
return statToAssetInfo(dir, baseURL, relPath, stat, includeFsPath);
|
|
253
308
|
} catch {
|
|
254
309
|
return;
|
|
255
310
|
}
|
|
256
311
|
}))).filter((info) => info !== void 0).sort((a, b) => a.path.localeCompare(b.path));
|
|
257
312
|
}
|
|
258
313
|
//#endregion
|
|
259
|
-
//#region src/rpc/functions/list.ts
|
|
314
|
+
//#region src/node/rpc/functions/list.ts
|
|
260
315
|
const defineAssetsRpc$4 = createDefineWrapperWithContext();
|
|
261
316
|
const assetInfoSchema = s.object({
|
|
262
317
|
path: s.string(),
|
|
@@ -288,7 +343,14 @@ const list = defineAssetsRpc$4({
|
|
|
288
343
|
},
|
|
289
344
|
setup: (ctx) => {
|
|
290
345
|
const assets = getAssetsContext(ctx);
|
|
291
|
-
return {
|
|
346
|
+
return {
|
|
347
|
+
/**
|
|
348
|
+
* The RPC runtime awaits handlers before validating `returns`; its
|
|
349
|
+
* public setup type currently models schema-backed returns as
|
|
350
|
+
* synchronous.
|
|
351
|
+
* `fsPath` is dev-only, never baked into a static build's dump.
|
|
352
|
+
*/
|
|
353
|
+
handler: (async () => scanAssets(assets.dir, assets.baseURL, ctx.mode === "dev")) };
|
|
292
354
|
}
|
|
293
355
|
});
|
|
294
356
|
const mkdir = createDefineWrapperWithContext()({
|
|
@@ -305,11 +367,14 @@ const mkdir = createDefineWrapperWithContext()({
|
|
|
305
367
|
},
|
|
306
368
|
setup: (ctx) => {
|
|
307
369
|
const assets = getAssetsContext(ctx);
|
|
308
|
-
return {
|
|
309
|
-
|
|
370
|
+
return {
|
|
371
|
+
/** See `list.ts` for why the async handler is cast. */
|
|
372
|
+
handler: (async ({ path }) => {
|
|
373
|
+
const absolute = await assertAssetMutationPath(assets.dir, path);
|
|
310
374
|
const stat = await fsp.stat(absolute).catch(() => void 0);
|
|
311
375
|
if (stat && !stat.isDirectory()) throw diagnostics.DP_ASSETS_0005({ path });
|
|
312
376
|
await fsp.mkdir(absolute, { recursive: true });
|
|
377
|
+
await assertAssetMutationPath(assets.dir, path);
|
|
313
378
|
}) };
|
|
314
379
|
}
|
|
315
380
|
});
|
|
@@ -331,9 +396,11 @@ const readImageMeta = createDefineWrapperWithContext()({
|
|
|
331
396
|
},
|
|
332
397
|
setup: (ctx) => {
|
|
333
398
|
const assets = getAssetsContext(ctx);
|
|
334
|
-
return {
|
|
399
|
+
return {
|
|
400
|
+
/** See `list.ts` for why the async handler is cast. */
|
|
401
|
+
handler: (async (path) => {
|
|
335
402
|
try {
|
|
336
|
-
const buffer = await fsp.readFile(assets.
|
|
403
|
+
const buffer = await fsp.readFile(await resolveAssetReadPath(assets.dir, path));
|
|
337
404
|
const meta = imageMeta(buffer);
|
|
338
405
|
return {
|
|
339
406
|
width: meta.width,
|
|
@@ -347,7 +414,7 @@ const readImageMeta = createDefineWrapperWithContext()({
|
|
|
347
414
|
}
|
|
348
415
|
});
|
|
349
416
|
//#endregion
|
|
350
|
-
//#region src/rpc/functions/read-text.ts
|
|
417
|
+
//#region src/node/rpc/functions/read-text.ts
|
|
351
418
|
const defineAssetsRpc$1 = createDefineWrapperWithContext();
|
|
352
419
|
const DEFAULT_LIMIT = 5e3;
|
|
353
420
|
const readText = defineAssetsRpc$1({
|
|
@@ -364,9 +431,11 @@ const readText = defineAssetsRpc$1({
|
|
|
364
431
|
},
|
|
365
432
|
setup: (ctx) => {
|
|
366
433
|
const assets = getAssetsContext(ctx);
|
|
367
|
-
return {
|
|
434
|
+
return {
|
|
435
|
+
/** See `list.ts` for why the async handler is cast. */
|
|
436
|
+
handler: (async (path, limit = DEFAULT_LIMIT) => {
|
|
368
437
|
try {
|
|
369
|
-
return (await fsp.readFile(assets.
|
|
438
|
+
return (await fsp.readFile(await resolveAssetReadPath(assets.dir, path), "utf-8")).slice(0, limit);
|
|
370
439
|
} catch {
|
|
371
440
|
return null;
|
|
372
441
|
}
|
|
@@ -390,20 +459,24 @@ const rename = createDefineWrapperWithContext()({
|
|
|
390
459
|
},
|
|
391
460
|
setup: (ctx) => {
|
|
392
461
|
const assets = getAssetsContext(ctx);
|
|
393
|
-
return {
|
|
462
|
+
return {
|
|
463
|
+
/** See `list.ts` for why the async handler is cast. */
|
|
464
|
+
handler: (async ({ path, newName }) => {
|
|
394
465
|
const trimmed = newName.trim();
|
|
395
466
|
if (!trimmed || /[/\\]/.test(trimmed)) throw diagnostics.DP_ASSETS_0006({ name: newName });
|
|
396
467
|
const folder = dirname(path);
|
|
397
468
|
const nextName = `${trimmed}${extname(path)}`;
|
|
398
469
|
const nextRelPath = folder === "." ? nextName : `${folder}/${nextName}`;
|
|
399
|
-
const from = assets.
|
|
400
|
-
const to = assets.
|
|
470
|
+
const from = await assertAssetMutationPath(assets.dir, path);
|
|
471
|
+
const to = await assertAssetMutationPath(assets.dir, nextRelPath);
|
|
401
472
|
if (from === to) {
|
|
402
473
|
const stat = await fsp.lstat(from);
|
|
403
474
|
return statToAssetInfo(assets.dir, assets.baseURL, path, stat, true);
|
|
404
475
|
}
|
|
405
476
|
if (await fsp.access(to).then(() => true).catch(() => false)) throw diagnostics.DP_ASSETS_0003({ path: nextRelPath });
|
|
406
477
|
await fsp.mkdir(dirname(to), { recursive: true });
|
|
478
|
+
await assertAssetMutationPath(assets.dir, path);
|
|
479
|
+
await assertAssetMutationPath(assets.dir, nextRelPath);
|
|
407
480
|
try {
|
|
408
481
|
await fsp.rename(from, to);
|
|
409
482
|
} catch (error) {
|
|
@@ -416,15 +489,15 @@ const rename = createDefineWrapperWithContext()({
|
|
|
416
489
|
}
|
|
417
490
|
});
|
|
418
491
|
//#endregion
|
|
419
|
-
//#region src/rpc/index.ts
|
|
420
|
-
/** Read-only RPC
|
|
492
|
+
//#region src/node/rpc/index.ts
|
|
493
|
+
/** Read-only RPC, always registered. */
|
|
421
494
|
const readFunctions = [
|
|
422
495
|
list,
|
|
423
496
|
readImageMeta,
|
|
424
497
|
readText,
|
|
425
498
|
capabilities
|
|
426
499
|
];
|
|
427
|
-
/** Mutating RPC
|
|
500
|
+
/** Mutating RPC, registered only when write actions are enabled. */
|
|
428
501
|
const writeFunctions = [
|
|
429
502
|
upload,
|
|
430
503
|
rename,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { g as configureAssets, m as UPLOAD_CHANNEL, r as writeFunctions, t as readFunctions } from "./rpc-
|
|
1
|
+
import { g as configureAssets, m as UPLOAD_CHANNEL, r as writeFunctions, t as readFunctions } from "./rpc-DG9tV_c_.mjs";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import fsp from "node:fs/promises";
|
|
5
5
|
import { watch } from "chokidar";
|
|
6
6
|
import { debounce } from "perfect-debounce";
|
|
7
|
-
//#region src/constants.ts
|
|
7
|
+
//#region src/node/constants.ts
|
|
8
8
|
/** Broadcast whenever a file is added, removed, or changed under the managed directory. */
|
|
9
9
|
const CHANGED_EVENT = "devframes:plugin:assets:changed";
|
|
10
10
|
/** Preferred standalone CLI port. */
|
|
@@ -15,7 +15,7 @@ const DEFAULT_PORT = 9015;
|
|
|
15
15
|
* Watch the managed directory and broadcast {@link CHANGED_EVENT} (debounced)
|
|
16
16
|
* whenever a file is added, removed, or changed, so connected UIs refresh
|
|
17
17
|
* their listing live. Returns a disposer; call it when the devframe shuts
|
|
18
|
-
* down (tests in particular
|
|
18
|
+
* down (tests in particular, since a leaked watcher keeps the process alive).
|
|
19
19
|
*/
|
|
20
20
|
function watchAssetsDir(ctx, dir) {
|
|
21
21
|
const notify = debounce(async () => {
|
|
@@ -26,6 +26,12 @@ function watchAssetsDir(ctx, dir) {
|
|
|
26
26
|
}, 300);
|
|
27
27
|
const watcher = watch(dir, {
|
|
28
28
|
ignoreInitial: true,
|
|
29
|
+
/**
|
|
30
|
+
* Directories can nest arbitrarily deep (icon sets, generated builds
|
|
31
|
+
* dropped into the managed dir, …); chokidar's default depth is
|
|
32
|
+
* unlimited, but pin a generous cap so a runaway symlink loop can't
|
|
33
|
+
* spin the watcher forever.
|
|
34
|
+
*/
|
|
29
35
|
depth: 32
|
|
30
36
|
});
|
|
31
37
|
watcher.on("add", () => void notify()).on("unlink", () => void notify()).on("addDir", () => void notify()).on("unlinkDir", () => void notify()).on("change", () => void notify());
|
|
@@ -35,7 +41,7 @@ function watchAssetsDir(ctx, dir) {
|
|
|
35
41
|
};
|
|
36
42
|
}
|
|
37
43
|
//#endregion
|
|
38
|
-
//#region src/node/
|
|
44
|
+
//#region src/node/setup.ts
|
|
39
45
|
const watchers = /* @__PURE__ */ new WeakMap();
|
|
40
46
|
/**
|
|
41
47
|
* Register the assets RPC surface on a devframe node context: ensures the
|
|
@@ -43,7 +49,7 @@ const watchers = /* @__PURE__ */ new WeakMap();
|
|
|
43
49
|
* RPCs (when enabled), and starts a live file watcher in dev mode.
|
|
44
50
|
*
|
|
45
51
|
* Raw asset bytes (for `<img>`/`<video>`/download previews) are served by
|
|
46
|
-
* the **host** the plugin is attached to
|
|
52
|
+
* the **host** the plugin is attached to: Vite, Nuxt, or any framework
|
|
47
53
|
* already serves its `public/` dir at {@link SetupAssetsOptions.baseURL}.
|
|
48
54
|
* The plugin only serves them itself when `serveStatic` is set (the
|
|
49
55
|
* standalone CLI, which is its own host).
|
|
@@ -65,7 +71,7 @@ async function setupAssets(ctx, options) {
|
|
|
65
71
|
}
|
|
66
72
|
/**
|
|
67
73
|
* Stop the live file watcher started by {@link setupAssets}. Test harnesses
|
|
68
|
-
* wrap their dev-server `close()` with this
|
|
74
|
+
* wrap their dev-server `close()` with this (see `test/_utils.ts`), so a
|
|
69
75
|
* leaked chokidar watcher doesn't keep the process alive.
|
|
70
76
|
*/
|
|
71
77
|
async function disposeAssetsWatcher(ctx) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
1
|
+
//#region src/node/types.d.ts
|
|
2
2
|
/** Broad category guessed from a file's extension, driving preview/icon choice. */
|
|
3
3
|
type AssetType = 'image' | 'font' | 'video' | 'audio' | 'text' | 'other';
|
|
4
4
|
/** One file under the managed directory. */
|
|
@@ -9,10 +9,10 @@ interface AssetInfo {
|
|
|
9
9
|
/** URL the browser can fetch the raw bytes from (live adapters only). */
|
|
10
10
|
publicPath: string;
|
|
11
11
|
size: number;
|
|
12
|
-
/** `mtimeMs`
|
|
12
|
+
/** `mtimeMs` in milliseconds since epoch. */
|
|
13
13
|
mtime: number;
|
|
14
14
|
/**
|
|
15
|
-
* Absolute filesystem path
|
|
15
|
+
* Absolute filesystem path, populated in `dev` mode only (never baked
|
|
16
16
|
* into a static build's dump), so the client can hand it to the
|
|
17
17
|
* `@devframes/service-open` wire service for open-in-editor / reveal.
|
|
18
18
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devframes/plugin-assets",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.10",
|
|
5
5
|
"description": "Devframe plugin for browsing, previewing, uploading, and managing static assets in a directory.",
|
|
6
6
|
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,14 +21,13 @@
|
|
|
21
21
|
],
|
|
22
22
|
"sideEffects": false,
|
|
23
23
|
"exports": {
|
|
24
|
-
".": "./dist/index.mjs",
|
|
25
|
-
"./
|
|
26
|
-
"./
|
|
27
|
-
"./
|
|
28
|
-
"./rpc": "./dist/rpc/index.mjs",
|
|
24
|
+
".": "./dist/node/index.mjs",
|
|
25
|
+
"./cli": "./dist/node/cli.mjs",
|
|
26
|
+
"./node": "./dist/node/setup.mjs",
|
|
27
|
+
"./rpc": "./dist/node/rpc/index.mjs",
|
|
29
28
|
"./package.json": "./package.json"
|
|
30
29
|
},
|
|
31
|
-
"types": "./dist/index.d.mts",
|
|
30
|
+
"types": "./dist/node/index.d.mts",
|
|
32
31
|
"bin": {
|
|
33
32
|
"devframe-assets": "./bin.mjs"
|
|
34
33
|
},
|
|
@@ -37,9 +36,9 @@
|
|
|
37
36
|
"dist"
|
|
38
37
|
],
|
|
39
38
|
"peerDependencies": {
|
|
40
|
-
"@devframes/plugin-assets--assets": "0.9.
|
|
41
|
-
"@devframes/service-shiki": "0.9.
|
|
42
|
-
"devframe": "0.9.
|
|
39
|
+
"@devframes/plugin-assets--assets": "0.9.10",
|
|
40
|
+
"@devframes/service-shiki": "0.9.10",
|
|
41
|
+
"devframe": "0.9.10",
|
|
43
42
|
"vite": "^7.0.0 || ^8.0.0"
|
|
44
43
|
},
|
|
45
44
|
"peerDependenciesMeta": {
|
|
@@ -54,7 +53,7 @@
|
|
|
54
53
|
}
|
|
55
54
|
},
|
|
56
55
|
"dependencies": {
|
|
57
|
-
"@devframes/service-open": "0.9.
|
|
56
|
+
"@devframes/service-open": "0.9.10",
|
|
58
57
|
"cac": "^7.0.0",
|
|
59
58
|
"chokidar": "^5.0.0",
|
|
60
59
|
"image-meta": "^0.2.2",
|
|
@@ -65,9 +64,9 @@
|
|
|
65
64
|
},
|
|
66
65
|
"devDependencies": {
|
|
67
66
|
"@antfu/design": "^0.4.0",
|
|
68
|
-
"@devframes/plugin-assets--assets": "0.9.
|
|
69
|
-
"@devframes/service-shiki": "0.9.
|
|
70
|
-
"@devframes/vite": "0.9.
|
|
67
|
+
"@devframes/plugin-assets--assets": "0.9.10",
|
|
68
|
+
"@devframes/service-shiki": "0.9.10",
|
|
69
|
+
"@devframes/vite": "0.9.10",
|
|
71
70
|
"@iconify-json/ph": "^1.2.2",
|
|
72
71
|
"@storybook/addon-a11y": "^10.5.10",
|
|
73
72
|
"@storybook/addon-docs": "^10.5.10",
|
|
@@ -75,7 +74,7 @@
|
|
|
75
74
|
"@types/node": "^26.4.0",
|
|
76
75
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
77
76
|
"colorjs.io": "^0.7.1",
|
|
78
|
-
"devframe": "0.9.
|
|
77
|
+
"devframe": "0.9.10",
|
|
79
78
|
"floating-vue": "^5.2.2",
|
|
80
79
|
"h3": "^2.0.1-rc.29",
|
|
81
80
|
"reka-ui": "^2.10.4",
|
|
@@ -87,9 +86,11 @@
|
|
|
87
86
|
"vue": "^3.5.42"
|
|
88
87
|
},
|
|
89
88
|
"scripts": {
|
|
90
|
-
"build": "tsdown && vite build --config
|
|
91
|
-
"
|
|
89
|
+
"build": "tsdown && vite build --config app/vite.config.ts",
|
|
90
|
+
"build:app": "vite build --config app/vite.config.ts",
|
|
92
91
|
"watch": "tsdown --watch",
|
|
92
|
+
"dev": "vite --config app/vite.config.ts --host",
|
|
93
|
+
"play": "pnpm run build && node playgrounds/server.mjs",
|
|
93
94
|
"storybook": "storybook dev -p 6013 --host 0.0.0.0",
|
|
94
95
|
"storybook:build": "storybook build",
|
|
95
96
|
"test": "vitest run",
|
package/dist/client/index.d.mts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { i as CodeSnippet, n as AssetInfo, r as AssetType, t as AssetImageMeta } from "../types-BrE0klRv.mjs";
|
|
2
|
-
import { DevframeRpcClientOptions } from "devframe/client";
|
|
3
|
-
//#region src/client/index.d.ts
|
|
4
|
-
declare function connectAssets(options?: DevframeRpcClientOptions): Promise<import("devframe/client").DevframeRpcClient>;
|
|
5
|
-
//#endregion
|
|
6
|
-
export { type AssetImageMeta, type AssetInfo, type AssetType, type CodeSnippet, connectAssets };
|
package/dist/client/index.mjs
DELETED
package/dist/index.d.mts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { a as DEFAULT_ALLOWED_UPLOAD_EXTENSIONS, i as CodeSnippet, n as AssetInfo, r as AssetType, t as AssetImageMeta } from "./types-BrE0klRv.mjs";
|
|
2
|
-
import { DevframeDefinition } from "devframe";
|
|
3
|
-
//#region src/index.d.ts
|
|
4
|
-
interface AssetsDevframeOptions {
|
|
5
|
-
id?: string;
|
|
6
|
-
name?: string;
|
|
7
|
-
icon?: string;
|
|
8
|
-
/**
|
|
9
|
-
* Directory this devframe manages. Defaults to `<cwd>/public` — the
|
|
10
|
-
* conventional static-asset folder for Nuxt, Vite, and most static-site
|
|
11
|
-
* frameworks. Pass an absolute path, or one relative to the devframe's
|
|
12
|
-
* own `cwd`.
|
|
13
|
-
*/
|
|
14
|
-
dir?: string;
|
|
15
|
-
/**
|
|
16
|
-
* URL base the **host** serves the managed directory at, used to build
|
|
17
|
-
* each asset's `publicPath`. Defaults to `/` — Vite, Nuxt, and most
|
|
18
|
-
* frameworks serve their `public/` folder at the site root. Set this to
|
|
19
|
-
* match a non-root deployment base (e.g. Nuxt's `app.baseURL`).
|
|
20
|
-
*/
|
|
21
|
-
baseURL?: string;
|
|
22
|
-
/**
|
|
23
|
-
* Serve the managed directory's bytes from this devframe itself.
|
|
24
|
-
* Defaults to `false`: when mounted into a host (Vite/Nuxt/…) that host
|
|
25
|
-
* already serves `public/` at {@link baseURL}, so the plugin references
|
|
26
|
-
* those URLs instead of standing up its own route. The standalone CLI
|
|
27
|
-
* sets this to `true` (it is its own host), serving the directory under
|
|
28
|
-
* a dedicated base so it doesn't collide with the SPA at `/`.
|
|
29
|
-
*/
|
|
30
|
-
serveStatic?: boolean;
|
|
31
|
-
basePath?: string;
|
|
32
|
-
distDir?: string;
|
|
33
|
-
/** Preferred standalone CLI port (default 9015). */
|
|
34
|
-
port?: number;
|
|
35
|
-
/**
|
|
36
|
-
* Enable upload, rename, delete, and folder creation from the UI.
|
|
37
|
-
* Enabled by default — set `false` (or pass `--read-only` on the
|
|
38
|
-
* standalone CLI) for a browse-only deployment.
|
|
39
|
-
*/
|
|
40
|
-
write?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* Extensions `upload` accepts, or `'*'` to accept any file. Defaults to
|
|
43
|
-
* the same allow-list Nuxt DevTools ships.
|
|
44
|
-
*/
|
|
45
|
-
uploadExtensions?: readonly string[] | '*';
|
|
46
|
-
/**
|
|
47
|
-
* Watch the managed directory in dev mode and push live listing updates.
|
|
48
|
-
* Enabled by default. Set `false` to skip the file watcher — useful when a
|
|
49
|
-
* host mounts the devframe in a short-lived or repeatedly-recreated
|
|
50
|
-
* context (tests, some SSR dev servers) where a background watcher isn't
|
|
51
|
-
* wanted.
|
|
52
|
-
*/
|
|
53
|
-
watch?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
* Require the trust handshake on the standalone server. Enabled by
|
|
56
|
-
* default — this devframe can read, write, and delete real files.
|
|
57
|
-
*/
|
|
58
|
-
auth?: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Register the `build` CLI subcommand. Disabled by default: a static
|
|
61
|
-
* export can only ever list file metadata from a baked snapshot — there
|
|
62
|
-
* is no live host serving the files, and every write action is inherently
|
|
63
|
-
* excluded from a static dump — so the command would produce a broken,
|
|
64
|
-
* write-less shell of the tool. Opt back in if that degraded export is
|
|
65
|
-
* still useful to you.
|
|
66
|
-
*/
|
|
67
|
-
build?: boolean;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Create the assets manager devframe — a framework-neutral port of Nuxt
|
|
71
|
-
* DevTools' Assets tab. Mount it into any host via devframe's adapters, or
|
|
72
|
-
* run it standalone with the bundled CLI (`devframe-assets`).
|
|
73
|
-
*
|
|
74
|
-
* @experimental This plugin is experimental and may change without a major
|
|
75
|
-
* version bump until it stabilizes.
|
|
76
|
-
*/
|
|
77
|
-
declare function createAssetsDevframe(options?: AssetsDevframeOptions): DevframeDefinition;
|
|
78
|
-
//#endregion
|
|
79
|
-
export { type AssetImageMeta, type AssetInfo, type AssetType, AssetsDevframeOptions, type CodeSnippet, DEFAULT_ALLOWED_UPLOAD_EXTENSIONS, createAssetsDevframe, createAssetsDevframe as default };
|
package/dist/index.mjs
DELETED