devframe 0.9.0-beta.4 → 0.9.0-beta.6

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.
Files changed (65) hide show
  1. package/dist/{_shared-CGPVmXUM.mjs → _shared-CKZPvSrN.mjs} +2 -1
  2. package/dist/{_shared-Bm8p4-fD.d.mts → _shared-QkI6ljJS.d.mts} +2 -2
  3. package/dist/adapters/build.d.mts +10 -12
  4. package/dist/adapters/build.mjs +23 -27
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +3 -4
  7. package/dist/adapters/dev.d.mts +19 -5
  8. package/dist/adapters/dev.mjs +2 -2
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +4 -3
  11. package/dist/adapters/initiate.mjs +1 -1
  12. package/dist/adapters/mcp.d.mts +1 -1
  13. package/dist/adapters/mcp.mjs +1 -1
  14. package/dist/cli/main.mjs +2 -2
  15. package/dist/client/index.d.mts +1 -1
  16. package/dist/client/index.mjs +2 -1175
  17. package/dist/constants.d.mts +1 -2
  18. package/dist/constants.mjs +1 -2
  19. package/dist/{context-DV4uWieM.d.mts → context-CQkRTbAc.d.mts} +1 -1
  20. package/dist/{context-b6iSomtv.mjs → context-D0HD0F7C.mjs} +19 -7
  21. package/dist/{context-CMSryXs-.mjs → context-D0jErmlx.mjs} +1 -1
  22. package/dist/{dev-4xjiynUD.mjs → dev-QEuT8SOF.mjs} +15 -9
  23. package/dist/{devframe-5oblos7I.d.mts → devframe-Cfx6doi4.d.mts} +41 -64
  24. package/dist/{diagnostics-C7IGWppM.mjs → diagnostics-DEuH3P5O.mjs} +32 -0
  25. package/dist/{host-agent-BBkKT27Q.mjs → host-agent-C5HjzfBz.mjs} +41 -3
  26. package/dist/{host-h3-CzCZwtuV.mjs → host-h3-Crkzm42q.mjs} +2 -2
  27. package/dist/{http-Z0w9baEe.mjs → http-CVwj7b4g.mjs} +2 -2
  28. package/dist/{index-BzDl02lt.d.mts → index-MYi1SZVL.d.mts} +2 -2
  29. package/dist/index.d.mts +3 -2
  30. package/dist/{instance-registry-Du_hbx-j.mjs → instance-registry-M2p-z6xE.mjs} +1 -1
  31. package/dist/{instance-shell-B0TwOxCl.mjs → instance-shell-DJ8s-MFw.mjs} +5 -5
  32. package/dist/{instance-shell-B-BSVp37.d.mts → instance-shell-D_UA0Idz.d.mts} +2 -2
  33. package/dist/internal/index.d.mts +72 -10
  34. package/dist/internal/index.mjs +7 -7
  35. package/dist/node/auth.d.mts +2 -2
  36. package/dist/node/auth.mjs +1 -1
  37. package/dist/node/hub-internals.d.mts +2 -2
  38. package/dist/node/hub-internals.mjs +2 -2
  39. package/dist/node/index.d.mts +1 -1
  40. package/dist/node/index.mjs +2 -2
  41. package/dist/recipes/common-rpc-functions.d.mts +1 -1
  42. package/dist/recipes/interactive-auth.d.mts +2 -2
  43. package/dist/recipes/interactive-auth.mjs +2 -2
  44. package/dist/remote-assets-DclsYyoL.d.mts +106 -0
  45. package/dist/rpc/transports/ws-bun.d.mts +1 -1
  46. package/dist/{rpc-core-B4Qm66Qt.d.mts → rpc-core-C8JQ3p7A.d.mts} +2 -2
  47. package/dist/{rpc-core-Cva-0pw0.mjs → rpc-core-IhcWl_xn.mjs} +1 -1
  48. package/dist/state-Cc1IeSmQ.mjs +1291 -0
  49. package/dist/storage-BVuipbGS.mjs +137 -0
  50. package/dist/types/index.d.mts +3 -2
  51. package/dist/utils/events.d.mts +1 -1
  52. package/dist/utils/open.d.mts +2 -10
  53. package/dist/utils/open.mjs +56 -1
  54. package/dist/utils/remote-assets.d.mts +15 -0
  55. package/dist/utils/remote-assets.mjs +309 -0
  56. package/dist/utils/serve-static.d.mts +13 -5
  57. package/dist/utils/serve-static.mjs +103 -9
  58. package/dist/utils/shared-state.d.mts +1 -1
  59. package/dist/utils/streaming-channel.d.mts +1 -1
  60. package/package.json +2 -3
  61. package/skills/devframe/SKILL.md +4 -5
  62. package/dist/open-Dz4V4irZ.mjs +0 -547
  63. package/dist/state-BeUHDDjk.mjs +0 -108
  64. package/dist/storage-CaUVrBrG.mjs +0 -1122
  65. package/skills/devframe/templates/spa-devframe.ts +0 -38
@@ -0,0 +1,137 @@
1
+ import { t as diagnostics } from "./diagnostics-DEuH3P5O.mjs";
2
+ import { createSharedState } from "devframe/utils/shared-state";
3
+ import fs from "node:fs";
4
+ import { dirname } from "pathe";
5
+ import process from "node:process";
6
+ //#region ../../node_modules/.pnpm/perfect-debounce@2.1.0/node_modules/perfect-debounce/dist/index.mjs
7
+ const DEBOUNCE_DEFAULTS = { trailing: true };
8
+ /**
9
+ Debounce functions
10
+ @param fn - Promise-returning/async function to debounce.
11
+ @param wait - Milliseconds to wait before calling `fn`. Default value is 25ms
12
+ @returns A function that delays calling `fn` until after `wait` milliseconds have elapsed since the last time it was called.
13
+ @example
14
+ ```
15
+ import { debounce } from 'perfect-debounce';
16
+ const expensiveCall = async input => input;
17
+ const debouncedFn = debounce(expensiveCall, 200);
18
+ for (const number of [1, 2, 3]) {
19
+ console.log(await debouncedFn(number));
20
+ }
21
+ //=> 1
22
+ //=> 2
23
+ //=> 3
24
+ ```
25
+ */
26
+ function debounce(fn, wait = 25, options = {}) {
27
+ options = {
28
+ ...DEBOUNCE_DEFAULTS,
29
+ ...options
30
+ };
31
+ if (!Number.isFinite(wait)) throw new TypeError("Expected `wait` to be a finite number");
32
+ let leadingValue;
33
+ let timeout;
34
+ let resolveList = [];
35
+ let currentPromise;
36
+ let trailingArgs;
37
+ const applyFn = (_this, args) => {
38
+ currentPromise = _applyPromised(fn, _this, args);
39
+ currentPromise.finally(() => {
40
+ currentPromise = null;
41
+ if (options.trailing && trailingArgs && !timeout) {
42
+ const promise = applyFn(_this, trailingArgs);
43
+ trailingArgs = null;
44
+ return promise;
45
+ }
46
+ });
47
+ return currentPromise;
48
+ };
49
+ const debounced = function(...args) {
50
+ if (options.trailing) trailingArgs = args;
51
+ if (currentPromise) return currentPromise;
52
+ return new Promise((resolve) => {
53
+ const shouldCallNow = !timeout && options.leading;
54
+ clearTimeout(timeout);
55
+ timeout = setTimeout(() => {
56
+ timeout = null;
57
+ const promise = options.leading ? leadingValue : applyFn(this, args);
58
+ trailingArgs = null;
59
+ for (const _resolve of resolveList) _resolve(promise);
60
+ resolveList = [];
61
+ }, wait);
62
+ if (shouldCallNow) {
63
+ leadingValue = applyFn(this, args);
64
+ resolve(leadingValue);
65
+ } else resolveList.push(resolve);
66
+ });
67
+ };
68
+ const _clearTimeout = (timer) => {
69
+ if (timer) {
70
+ clearTimeout(timer);
71
+ timeout = null;
72
+ }
73
+ };
74
+ debounced.isPending = () => !!timeout;
75
+ debounced.cancel = () => {
76
+ _clearTimeout(timeout);
77
+ resolveList = [];
78
+ trailingArgs = null;
79
+ };
80
+ debounced.flush = () => {
81
+ _clearTimeout(timeout);
82
+ if (!trailingArgs || currentPromise) return;
83
+ const args = trailingArgs;
84
+ trailingArgs = null;
85
+ return applyFn(this, args);
86
+ };
87
+ return debounced;
88
+ }
89
+ async function _applyPromised(fn, _this, args) {
90
+ return await fn.apply(_this, args);
91
+ }
92
+ //#endregion
93
+ //#region src/node/storage.ts
94
+ function safeJsonParse(text) {
95
+ return JSON.parse(text, (key, value) => {
96
+ if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) return void 0;
97
+ return value;
98
+ });
99
+ }
100
+ function createStorage(options) {
101
+ const { mergeInitialValue = (initialValue, savedValue) => ({
102
+ ...initialValue,
103
+ ...savedValue
104
+ }), debounce: debounceTime = 100 } = options;
105
+ let initialValue = options.initialValue;
106
+ if (fs.existsSync(options.filepath)) try {
107
+ const savedValue = safeJsonParse(fs.readFileSync(options.filepath, "utf-8"));
108
+ initialValue = mergeInitialValue ? mergeInitialValue(options.initialValue, savedValue) : savedValue;
109
+ } catch (error) {
110
+ diagnostics.DF0012({
111
+ filepath: options.filepath,
112
+ cause: error
113
+ }, { method: "warn" });
114
+ initialValue = options.initialValue;
115
+ }
116
+ const state = createSharedState({
117
+ initialValue,
118
+ enablePatches: false
119
+ });
120
+ state.on("updated", debounce((newState) => {
121
+ try {
122
+ const dir = dirname(options.filepath);
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ const tmp = `${options.filepath}.${process.pid}.tmp`;
125
+ fs.writeFileSync(tmp, `${JSON.stringify(newState, null, 2)}\n`);
126
+ fs.renameSync(tmp, options.filepath);
127
+ } catch (error) {
128
+ diagnostics.DF0035({
129
+ filepath: options.filepath,
130
+ cause: error
131
+ }, { method: "error" });
132
+ }
133
+ }, debounceTime));
134
+ return state;
135
+ }
136
+ //#endregion
137
+ export { createStorage as t };
@@ -1,4 +1,5 @@
1
- import { $ as RpcStreamingHost, A as DevframeSettings, At as AgentToolProviderHandle, B as DevframeHost, C as DevframeServiceId, Ct as AgentManifest, D as DevframeScopedNodeContext, Dt as AgentTool, E as DevframeServicesRegistry, Et as AgentResourceInput, F as ScopedRpcFn, Ft as EventsMap, G as DevframeDiagnosticsLogger, H as DevframeDefineDiagnosticsOptions, I as ScopedServerFunctions, J as RpcFunctionsHost, K as DevframeNodeRpcSession, L as ScopedSharedStates, M as DevframeSettingsStore, Mt as DevframeAgentHostEvents, N as ScopedBroadcastOptions, Nt as EventEmitter, O as DevframeScopedNodeRpc, Ot as AgentToolInput, P as ScopedClientFunctions, Pt as EventUnsubscribe, Q as RpcStreamingChannelOptions, R as SettingsForNamespace, S as DevframeNodeContext, St as AgentHandle, T as DevframeServicesHost, Tt as AgentResourceContent, U as DevframeDiagnosticsDefinition, V as DevframeStorageScope, W as DevframeDiagnosticsHost, X as RpcSharedStateHost, Y as RpcSharedStateGetOptions, Z as RpcStreamingChannel, _ as ConnectionMeta, a as DevframeDockDefaults, b as DevframeCapabilities, c as DevframeSetupInfo, d as DevframeWsOptions, et as DevframeRpcClientFunctions, f as McpRouteOptions, g as Thenable, h as PartialWithoutId, i as DevframeDeploymentKind, j as DevframeSettingsRegistry, jt as DevframeAgentHost, k as DevframeScopedStreamingHost, kt as AgentToolProvider, l as DevframeSpaOptions, m as EntriesToObject, n as DevframeCliOptions, nt as DevframeRpcSharedStates, o as DevframeDuplicationStrategy, q as RpcBroadcastOptions, r as DevframeDefinition, s as DevframeRuntime, t as DevframeBrowserContext, tt as DevframeRpcServerFunctions, u as DevframeSseOptions, v as ConnectionMetaSse, w as DevframeServiceOf, wt as AgentResource, x as DevframeConnectionConfigsRegistry, y as ConnectionMetaWebsocket, z as DevframeViewHost } from "../devframe-5oblos7I.mjs";
1
+ import { A as ScopedServerFunctions, B as RpcBroadcastOptions, C as DevframeScopedStreamingHost, Ct as AgentToolProviderHandle, D as ScopedBroadcastOptions, Dt as EventUnsubscribe, E as DevframeSettingsStore, Et as EventEmitter, F as DevframeStorageScope, G as RpcStreamingChannelOptions, H as RpcSharedStateGetOptions, I as DevframeDefineDiagnosticsOptions, J as DevframeRpcServerFunctions, K as RpcStreamingHost, L as DevframeDiagnosticsHost, M as SettingsForNamespace, N as DevframeViewHost, O as ScopedClientFunctions, Ot as EventsMap, P as DevframeHost, R as DevframeDiagnosticsLogger, S as DevframeScopedNodeRpc, St as AgentToolProvider, T as DevframeSettingsRegistry, Tt as DevframeAgentHostEvents, U as RpcSharedStateHost, V as RpcFunctionsHost, W as RpcStreamingChannel, Y as DevframeRpcSharedStates, _ as DevframeServiceId, _t as AgentResource, a as DevframeDuplicationStrategy, b as DevframeServicesRegistry, bt as AgentTool, c as DevframeWsOptions, d as ConnectionMeta, f as ConnectionMetaSse, g as DevframeNodeContext, gt as AgentManifest, h as DevframeConnectionConfigsRegistry, ht as AgentHandle, i as DevframeDockDefaults, j as ScopedSharedStates, k as ScopedRpcFn, l as McpRouteOptions, m as DevframeCapabilities, n as DevframeDefinition, o as DevframeSetupInfo, p as ConnectionMetaWebsocket, q as DevframeRpcClientFunctions, r as DevframeDeploymentKind, s as DevframeSseOptions, t as DevframeCliOptions, v as DevframeServiceOf, vt as AgentResourceContent, w as DevframeSettings, wt as DevframeAgentHost, x as DevframeScopedNodeContext, xt as AgentToolInput, y as DevframeServicesHost, yt as AgentResourceInput, z as DevframeNodeRpcSession } from "../devframe-Cfx6doi4.mjs";
2
2
  import { g as RpcFunctionAgentOptions } from "../types-Djq7CBXh.mjs";
3
3
  import { d as DevframeRpcConnection, f as DevframeRpcConnectionRequest, p as DevframeRpcTransportKind, u as DevframeNodeRpcSessionMeta } from "../ws-server-D1d3QM9f.mjs";
4
- export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, AgentToolProvider, AgentToolProviderHandle, ConnectionMeta, ConnectionMetaSse, ConnectionMetaWebsocket, DevframeAgentHost, DevframeAgentHostEvents, DevframeBrowserContext, DevframeCapabilities, DevframeCliOptions, DevframeConnectionConfigsRegistry, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsDefinition, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeDockDefaults, DevframeDuplicationStrategy, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, type DevframeRpcConnection, type DevframeRpcConnectionRequest, DevframeRpcServerFunctions, DevframeRpcSharedStates, type DevframeRpcTransportKind, DevframeRuntime, DevframeScopedNodeContext, DevframeScopedNodeRpc, DevframeScopedStreamingHost, DevframeServiceId, DevframeServiceOf, DevframeServicesHost, DevframeServicesRegistry, DevframeSettings, DevframeSettingsRegistry, DevframeSettingsStore, DevframeSetupInfo, DevframeSpaOptions, DevframeSseOptions, DevframeStorageScope, DevframeViewHost, DevframeWsOptions, EntriesToObject, EventEmitter, EventUnsubscribe, EventsMap, McpRouteOptions, PartialWithoutId, RpcBroadcastOptions, type RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, ScopedBroadcastOptions, ScopedClientFunctions, ScopedRpcFn, ScopedServerFunctions, ScopedSharedStates, SettingsForNamespace, Thenable };
4
+ import { a as StaticAssetsSource, i as RemoteAssetsStore, n as RemoteAssetsProvider, r as RemoteAssetsProviderCustom, t as RemoteAssets } from "../remote-assets-DclsYyoL.mjs";
5
+ export { AgentHandle, AgentManifest, AgentResource, AgentResourceContent, AgentResourceInput, AgentTool, AgentToolInput, AgentToolProvider, AgentToolProviderHandle, ConnectionMeta, ConnectionMetaSse, ConnectionMetaWebsocket, DevframeAgentHost, DevframeAgentHostEvents, DevframeCapabilities, DevframeCliOptions, DevframeConnectionConfigsRegistry, DevframeDefineDiagnosticsOptions, DevframeDefinition, DevframeDeploymentKind, DevframeDiagnosticsHost, DevframeDiagnosticsLogger, DevframeDockDefaults, DevframeDuplicationStrategy, DevframeHost, DevframeNodeContext, DevframeNodeRpcSession, type DevframeNodeRpcSessionMeta, DevframeRpcClientFunctions, type DevframeRpcConnection, type DevframeRpcConnectionRequest, DevframeRpcServerFunctions, DevframeRpcSharedStates, type DevframeRpcTransportKind, DevframeScopedNodeContext, DevframeScopedNodeRpc, DevframeScopedStreamingHost, DevframeServiceId, DevframeServiceOf, DevframeServicesHost, DevframeServicesRegistry, DevframeSettings, DevframeSettingsRegistry, DevframeSettingsStore, DevframeSetupInfo, DevframeSseOptions, DevframeStorageScope, DevframeViewHost, DevframeWsOptions, EventEmitter, EventUnsubscribe, EventsMap, McpRouteOptions, RemoteAssets, RemoteAssetsProvider, RemoteAssetsProviderCustom, RemoteAssetsStore, RpcBroadcastOptions, type RpcFunctionAgentOptions, RpcFunctionsHost, RpcSharedStateGetOptions, RpcSharedStateHost, RpcStreamingChannel, RpcStreamingChannelOptions, RpcStreamingHost, ScopedBroadcastOptions, ScopedClientFunctions, ScopedRpcFn, ScopedServerFunctions, ScopedSharedStates, SettingsForNamespace, StaticAssetsSource };
@@ -1,4 +1,4 @@
1
- import { Ft as EventsMap, Nt as EventEmitter } from "../devframe-5oblos7I.mjs";
1
+ import { Et as EventEmitter, Ot as EventsMap } from "../devframe-Cfx6doi4.mjs";
2
2
  //#region src/utils/events.d.ts
3
3
  /**
4
4
  * Create event emitter.
@@ -1,16 +1,8 @@
1
1
  //#region src/utils/open.d.ts
2
- interface OpenOptions {
3
- /**
4
- * Resolve only after the launched app exits.
5
- *
6
- * @default false
7
- */
8
- wait?: boolean;
9
- }
10
2
  /**
11
3
  * Open a URL, file, or other target in its default OS handler
12
4
  * (browser for URLs, Finder/Explorer for paths, etc.).
13
5
  */
14
- declare function open(target: string, options?: OpenOptions): Promise<void>;
6
+ declare function open(target: string): Promise<void>;
15
7
  //#endregion
16
- export { OpenOptions, open };
8
+ export { open };
@@ -1,2 +1,57 @@
1
- import { t as open } from "../open-Dz4V4irZ.mjs";
1
+ import fs from "node:fs";
2
+ import process from "node:process";
3
+ import { spawn } from "node:child_process";
4
+ //#region src/utils/open.ts
5
+ /**
6
+ * Launches `command` detached from the current process and resolves once
7
+ * the OS has accepted the spawn (not once the launched app exits) — the
8
+ * same "fire and forget" behavior `open`'s default (`wait: false`) gave us.
9
+ */
10
+ function spawnDetached(command, args) {
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn(command, args, {
13
+ detached: true,
14
+ stdio: "ignore",
15
+ windowsHide: true
16
+ });
17
+ child.once("error", reject);
18
+ child.once("spawn", () => {
19
+ child.unref();
20
+ resolve();
21
+ });
22
+ });
23
+ }
24
+ function isWsl() {
25
+ if (process.platform !== "linux") return false;
26
+ try {
27
+ return fs.readFileSync("/proc/version", "utf-8").toLowerCase().includes("microsoft");
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Open a URL, file, or other target in its default OS handler
34
+ * (browser for URLs, Finder/Explorer for paths, etc.).
35
+ */
36
+ async function open(target) {
37
+ if (process.platform === "darwin") return spawnDetached("open", [target]);
38
+ if (process.platform === "win32") return spawnDetached("cmd", [
39
+ "/c",
40
+ "start",
41
+ "\"\"",
42
+ target
43
+ ]);
44
+ if (isWsl()) try {
45
+ return await spawnDetached("wslview", [target]);
46
+ } catch {
47
+ return spawnDetached("cmd.exe", [
48
+ "/c",
49
+ "start",
50
+ "\"\"",
51
+ target
52
+ ]);
53
+ }
54
+ return spawnDetached("xdg-open", [target]);
55
+ }
56
+ //#endregion
2
57
  export { open };
@@ -0,0 +1,15 @@
1
+ import { a as StaticAssetsSource, i as RemoteAssetsStore } from "../remote-assets-DclsYyoL.mjs";
2
+ //#region src/utils/remote-assets.d.ts
3
+ /**
4
+ * Normalize a {@link StaticAssetsSource} into something servable: a local
5
+ * directory (strings pass through; a remote source short-circuits to a
6
+ * locally installed copy of its package when present) or a caching
7
+ * {@link RemoteAssetsStore} back-proxy. Remote caches live under
8
+ * `<projectStorageDir>/.remote-assets/<package>@<version>/`.
9
+ *
10
+ * A remote source's `package`/`version` are validated first (`DF0065`) — both
11
+ * are interpolated into CDN URLs and the cache path.
12
+ */
13
+ declare function resolveStaticAssetsSource(source: StaticAssetsSource, projectStorageDir: string): string | RemoteAssetsStore;
14
+ //#endregion
15
+ export { resolveStaticAssetsSource };
@@ -0,0 +1,309 @@
1
+ import { t as diagnostics } from "../diagnostics-DEuH3P5O.mjs";
2
+ import { createRequire } from "node:module";
3
+ import { createReadStream, existsSync } from "node:fs";
4
+ import { Buffer } from "node:buffer";
5
+ import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
6
+ import { Readable } from "node:stream";
7
+ import { lookup } from "mrmime";
8
+ import { dirname, extname, join, normalize, sep } from "pathe";
9
+ //#region src/utils/remote-assets.ts
10
+ const MANIFEST_FILENAME = ".manifest.json";
11
+ /** Flatten a jsDelivr (`name`/`files`) or unpkg (`path`/`files`) file tree. */
12
+ function flattenTree(nodes, style) {
13
+ const out = [];
14
+ const walk = (list, prefix) => {
15
+ for (const node of list) if (style === "path") if (node.type === "file") out.push((node.path ?? "").replace(/^\//, ""));
16
+ else walk(node.files ?? [], "");
17
+ else if (node.type === "file") out.push(prefix + (node.name ?? ""));
18
+ else if (node.files) walk(node.files, `${prefix}${node.name}/`);
19
+ };
20
+ walk(nodes, "");
21
+ return out;
22
+ }
23
+ const providers = {
24
+ jsdelivr: {
25
+ fileUrl: (pkg, version, filePath) => `https://cdn.jsdelivr.net/npm/${pkg}@${version}/${filePath}`,
26
+ listFiles: async (pkg, version, fetchImpl) => {
27
+ const res = await fetchImpl(`https://data.jsdelivr.com/v1/packages/npm/${pkg}@${version}`);
28
+ if (!res.ok) throw new Error(`HTTP ${res.status} from data.jsdelivr.com`);
29
+ return flattenTree((await res.json()).files ?? [], "name");
30
+ }
31
+ },
32
+ unpkg: {
33
+ fileUrl: (pkg, version, filePath) => `https://unpkg.com/${pkg}@${version}/${filePath}`,
34
+ listFiles: async (pkg, version, fetchImpl) => {
35
+ const res = await fetchImpl(`https://unpkg.com/${pkg}@${version}/?meta`);
36
+ if (!res.ok) throw new Error(`HTTP ${res.status} from unpkg.com`);
37
+ return flattenTree([await res.json()], "path");
38
+ }
39
+ }
40
+ };
41
+ function resolveProvider(assets) {
42
+ const p = assets.provider ?? "jsdelivr";
43
+ return typeof p === "string" ? {
44
+ provider: providers[p],
45
+ name: p
46
+ } : {
47
+ provider: p,
48
+ name: "custom"
49
+ };
50
+ }
51
+ /**
52
+ * Resolve a locally installed copy of `assets.package` from
53
+ * `assets.resolveFrom`'s dependency graph and return its assets directory,
54
+ * or `undefined` when the package (or directory) is absent. A different
55
+ * installed version warns (`DF0062`); a different major throws (`DF0061`).
56
+ */
57
+ function resolveInstalled(assets) {
58
+ if (!assets.resolveFrom) return void 0;
59
+ let pkgJsonPath;
60
+ let installed;
61
+ try {
62
+ const requireFrom = createRequire(assets.resolveFrom);
63
+ pkgJsonPath = requireFrom.resolve(`${assets.package}/package.json`);
64
+ installed = requireFrom(`${assets.package}/package.json`).version;
65
+ } catch {
66
+ return;
67
+ }
68
+ if (typeof installed !== "string") return void 0;
69
+ if (installed !== assets.version) {
70
+ const major = (v) => v.trim().split(".")[0] ?? v;
71
+ if (major(installed) !== major(assets.version)) throw diagnostics.DF0061({
72
+ package: assets.package,
73
+ required: assets.version,
74
+ installed
75
+ });
76
+ diagnostics.DF0062({
77
+ package: assets.package,
78
+ required: assets.version,
79
+ installed
80
+ });
81
+ }
82
+ const dir = join(dirname(pkgJsonPath), assets.path ?? "dist");
83
+ return existsSync(dir) ? dir : void 0;
84
+ }
85
+ function contentTypeFor(filePath) {
86
+ const type = lookup(filePath);
87
+ if (!type) return "application/octet-stream";
88
+ return type === "text/html" ? "text/html; charset=utf-8" : type;
89
+ }
90
+ /** Clean a request path into a safe package-relative POSIX path, or `null` if it escapes root. */
91
+ function cleanRequestPath(urlPath) {
92
+ let cleaned;
93
+ try {
94
+ cleaned = decodeURIComponent(urlPath || "/");
95
+ } catch {
96
+ return null;
97
+ }
98
+ cleaned = cleaned.replace(/[?#].*$/, "").replace(/^\/+|\/+$/g, "");
99
+ const normalized = normalize(cleaned);
100
+ if (normalized === ".." || normalized.startsWith(`..${sep}`) || normalized.startsWith("/")) return null;
101
+ return normalized === "." ? "" : normalized;
102
+ }
103
+ /** Candidate files for a request, in order: direct hit, index, `.html`, SPA fallback. */
104
+ function candidatePaths(prefix, cleaned) {
105
+ const candidates = [];
106
+ if (cleaned) candidates.push(prefix + cleaned);
107
+ candidates.push(`${prefix}${cleaned ? `${cleaned}/` : ""}index.html`);
108
+ if (cleaned && !extname(cleaned)) candidates.push(`${prefix + cleaned}.html`);
109
+ if (!/\.[a-z0-9]+$/i.test(cleaned) && !candidates.includes(`${prefix}index.html`)) candidates.push(`${prefix}index.html`);
110
+ return candidates;
111
+ }
112
+ function createStore(assets, cacheDir) {
113
+ const normalized = {
114
+ ...assets,
115
+ path: assets.path ?? "dist"
116
+ };
117
+ const { provider, name: providerName } = resolveProvider(assets);
118
+ const fetchImpl = assets.fetch ?? globalThis.fetch;
119
+ const prefix = `${normalized.path}/`;
120
+ let manifestPromise;
121
+ let manifestReported = false;
122
+ async function loadManifest() {
123
+ const manifestFile = join(cacheDir, MANIFEST_FILENAME);
124
+ try {
125
+ return new Set(JSON.parse(await readFile(manifestFile, "utf8")));
126
+ } catch {}
127
+ if (assets.offline || !provider.listFiles) return null;
128
+ try {
129
+ const files = await provider.listFiles(normalized.package, normalized.version, fetchImpl);
130
+ await mkdir(cacheDir, { recursive: true });
131
+ await writeFile(manifestFile, JSON.stringify(files), "utf8").catch(() => {});
132
+ return new Set(files);
133
+ } catch (error) {
134
+ if (!manifestReported) {
135
+ manifestReported = true;
136
+ diagnostics.DF0059({
137
+ package: normalized.package,
138
+ version: normalized.version,
139
+ provider: providerName,
140
+ reason: errText(error),
141
+ cause: error
142
+ });
143
+ }
144
+ return null;
145
+ }
146
+ }
147
+ async function serveCached(filePath) {
148
+ const abs = join(cacheDir, filePath);
149
+ let size;
150
+ try {
151
+ const s = await stat(abs);
152
+ if (!s.isFile()) return null;
153
+ size = s.size;
154
+ } catch {
155
+ return null;
156
+ }
157
+ return new Response(Readable.toWeb(createReadStream(abs)), { headers: {
158
+ "Content-Type": contentTypeFor(filePath),
159
+ "Content-Length": String(size),
160
+ "Cache-Control": "no-store"
161
+ } });
162
+ }
163
+ /** Persist `body` to the cache at `filePath` (tmp + rename); failures warn (`DF0063`). */
164
+ async function persist(filePath, body) {
165
+ const target = join(cacheDir, filePath);
166
+ const tmp = `${target}.${Math.random().toString(36).slice(2)}.tmp`;
167
+ try {
168
+ await mkdir(dirname(target), { recursive: true });
169
+ await writeFile(tmp, Buffer.from(await new Response(body).arrayBuffer()));
170
+ await rename(tmp, target);
171
+ } catch (error) {
172
+ await rm(tmp, { force: true }).catch(() => {});
173
+ diagnostics.DF0063({
174
+ filepath: target,
175
+ reason: errText(error),
176
+ cause: error
177
+ });
178
+ }
179
+ }
180
+ /** Fetch `filePath` through the provider: `null` on 404, a `Response` on 200, throws (`DF0060`) otherwise. */
181
+ async function serveRemote(filePath) {
182
+ const url = provider.fileUrl(normalized.package, normalized.version, filePath);
183
+ let res;
184
+ try {
185
+ res = await fetchImpl(url);
186
+ } catch (error) {
187
+ throw diagnostics.DF0060({
188
+ url,
189
+ package: normalized.package,
190
+ reason: errText(error),
191
+ cause: error
192
+ });
193
+ }
194
+ if (res.status === 404) {
195
+ await res.body?.cancel().catch(() => {});
196
+ return null;
197
+ }
198
+ if (!res.ok || !res.body) {
199
+ await res.body?.cancel().catch(() => {});
200
+ throw diagnostics.DF0060({
201
+ url,
202
+ package: normalized.package,
203
+ reason: `HTTP ${res.status}`
204
+ });
205
+ }
206
+ const [toClient, toCache] = res.body.tee();
207
+ persist(filePath, toCache);
208
+ const length = res.headers.get("content-length");
209
+ return new Response(toClient, { headers: {
210
+ "Content-Type": contentTypeFor(filePath),
211
+ "Cache-Control": "no-store",
212
+ ...length ? { "Content-Length": length } : {}
213
+ } });
214
+ }
215
+ async function serve(urlPath) {
216
+ const cleaned = cleanRequestPath(urlPath);
217
+ if (cleaned === null) return null;
218
+ const candidates = candidatePaths(prefix, cleaned);
219
+ manifestPromise ??= loadManifest();
220
+ const manifest = await manifestPromise;
221
+ if (manifest) {
222
+ const filePath = candidates.find((c) => manifest.has(c));
223
+ if (!filePath) return null;
224
+ return await serveCached(filePath) ?? (assets.offline ? Promise.reject(diagnostics.DF0060({
225
+ url: filePath,
226
+ package: normalized.package,
227
+ reason: "offline: true and the file is not in the cache"
228
+ })) : serveRemote(filePath));
229
+ }
230
+ for (const candidate of candidates) {
231
+ const cached = await serveCached(candidate);
232
+ if (cached) return cached;
233
+ }
234
+ if (assets.offline) return null;
235
+ for (const candidate of candidates) {
236
+ const remote = await serveRemote(candidate);
237
+ if (remote) return remote;
238
+ }
239
+ return null;
240
+ }
241
+ async function materialize(targetDir) {
242
+ const fail = (reason, cause) => {
243
+ throw diagnostics.DF0064({
244
+ package: normalized.package,
245
+ version: normalized.version,
246
+ reason,
247
+ cause
248
+ });
249
+ };
250
+ if (!provider.listFiles) fail("the configured provider has no file listing (`listFiles`)");
251
+ let files;
252
+ try {
253
+ files = await provider.listFiles(normalized.package, normalized.version, fetchImpl);
254
+ } catch (error) {
255
+ return fail(errText(error), error);
256
+ }
257
+ for (const filePath of files.filter((f) => f.startsWith(prefix))) {
258
+ const target = join(targetDir, filePath.slice(prefix.length));
259
+ const url = provider.fileUrl(normalized.package, normalized.version, filePath);
260
+ let res;
261
+ try {
262
+ res = await fetchImpl(url);
263
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
264
+ } catch (error) {
265
+ return fail(`failed to download ${filePath}: ${errText(error)}`, error);
266
+ }
267
+ await mkdir(dirname(target), { recursive: true });
268
+ await writeFile(target, Buffer.from(await res.arrayBuffer()));
269
+ }
270
+ }
271
+ return {
272
+ assets: normalized,
273
+ serve,
274
+ materialize
275
+ };
276
+ }
277
+ function errText(error) {
278
+ return error instanceof Error ? error.message : String(error);
279
+ }
280
+ const PACKAGE_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
281
+ const VERSION_RE = /^\d+\.\d+\.\d+(?:-[a-z0-9-]+(?:\.[a-z0-9-]+)*)?(?:\+[a-z0-9-]+(?:\.[a-z0-9-]+)*)?$/i;
282
+ /** Reject a {@link RemoteAssets} with an unsafe package name or version (`DF0065`). */
283
+ function assertValidRemoteAssets(assets) {
284
+ if (assets.package.length > 214 || !PACKAGE_NAME_RE.test(assets.package)) throw diagnostics.DF0065({
285
+ field: "package",
286
+ value: assets.package
287
+ });
288
+ if (!VERSION_RE.test(assets.version)) throw diagnostics.DF0065({
289
+ field: "version",
290
+ value: assets.version
291
+ });
292
+ }
293
+ /**
294
+ * Normalize a {@link StaticAssetsSource} into something servable: a local
295
+ * directory (strings pass through; a remote source short-circuits to a
296
+ * locally installed copy of its package when present) or a caching
297
+ * {@link RemoteAssetsStore} back-proxy. Remote caches live under
298
+ * `<projectStorageDir>/.remote-assets/<package>@<version>/`.
299
+ *
300
+ * A remote source's `package`/`version` are validated first (`DF0065`) — both
301
+ * are interpolated into CDN URLs and the cache path.
302
+ */
303
+ function resolveStaticAssetsSource(source, projectStorageDir) {
304
+ if (typeof source === "string") return source;
305
+ assertValidRemoteAssets(source);
306
+ return resolveInstalled(source) ?? createStore(source, join(projectStorageDir, ".remote-assets", `${source.package.replace(/\//g, "+")}@${source.version}`));
307
+ }
308
+ //#endregion
309
+ export { resolveStaticAssetsSource };
@@ -1,6 +1,12 @@
1
+ import { i as RemoteAssetsStore } from "../remote-assets-DclsYyoL.mjs";
1
2
  import { IncomingMessage, ServerResponse } from "node:http";
2
3
  import { EventHandler, H3 } from "h3";
3
4
  //#region src/utils/serve-static.d.ts
5
+ /**
6
+ * What the static-serving engine accepts: a local directory, or a resolved
7
+ * {@link RemoteAssetsStore} back-proxy (from `devframe/utils/remote-assets`).
8
+ */
9
+ type ServableAssets = string | RemoteAssetsStore;
4
10
  interface ServeStaticOptions {
5
11
  /** Default: `['index.html']`. */
6
12
  indexNames?: string[];
@@ -8,7 +14,9 @@ interface ServeStaticOptions {
8
14
  single?: boolean;
9
15
  }
10
16
  /**
11
- * h3 event handler that serves files from `dir` with SPA fallback.
17
+ * h3 event handler that serves files from `source` with SPA fallback — a
18
+ * local directory, or a {@link RemoteAssetsStore} whose files stream through
19
+ * the CDN back-proxy into the local cache.
12
20
  *
13
21
  * Drop-in replacement for `fromNodeMiddleware(sirv(dir, { dev: true, single: true }))`
14
22
  * when the surrounding server is an h3 app — no `Cache-Control` beyond
@@ -16,7 +24,7 @@ interface ServeStaticOptions {
16
24
  * file extension falls back to `<dir>/index.html` so client-side routing
17
25
  * works.
18
26
  */
19
- declare function serveStaticHandler(dir: string, options?: ServeStaticOptions): EventHandler;
27
+ declare function serveStaticHandler(source: ServableAssets, options?: ServeStaticOptions): EventHandler;
20
28
  /**
21
29
  * Mount {@link serveStaticHandler} on an h3 app at `base`.
22
30
  *
@@ -24,7 +32,7 @@ declare function serveStaticHandler(dir: string, options?: ServeStaticOptions):
24
32
  * from `event.url.pathname`, so the file resolver sees paths relative to
25
33
  * `dir`.
26
34
  */
27
- declare function mountStaticHandler(app: H3, base: string, dir: string, options?: ServeStaticOptions): void;
35
+ declare function mountStaticHandler(app: H3, base: string, source: ServableAssets, options?: ServeStaticOptions): void;
28
36
  /**
29
37
  * Connect/Express-style Node middleware variant of {@link serveStaticHandler}.
30
38
  *
@@ -32,6 +40,6 @@ declare function mountStaticHandler(app: H3, base: string, dir: string, options?
32
40
  * Connect stack — avoids forcing the host package to depend on h3 just to
33
41
  * adapt an event handler back into Node middleware.
34
42
  */
35
- declare function serveStaticNodeMiddleware(dir: string, options?: ServeStaticOptions): (req: IncomingMessage, res: ServerResponse, next?: (err?: Error) => void) => void;
43
+ declare function serveStaticNodeMiddleware(source: ServableAssets, options?: ServeStaticOptions): (req: IncomingMessage, res: ServerResponse, next?: (err?: Error) => void) => void;
36
44
  //#endregion
37
- export { ServeStaticOptions, mountStaticHandler, serveStaticHandler, serveStaticNodeMiddleware };
45
+ export { ServableAssets, ServeStaticOptions, mountStaticHandler, serveStaticHandler, serveStaticNodeMiddleware };