@yugoo-design/dsh-design-studio-dsh 0.1.2 → 0.2.2
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 +438 -3
- package/lib/authoring/panel-components.json +10790 -0
- package/lib/authoring/studio-creators/material.json +1 -0
- package/lib/authoring-agent-vc786tS0.js +648 -0
- package/lib/authoring-agent.d.ts +24 -0
- package/lib/authoring-agent.js +2 -0
- package/lib/authoring-agent.types-0S0xnlDW.d.ts +87 -0
- package/lib/authoring-entry.d.ts +20 -0
- package/lib/authoring-entry.js +77 -0
- package/lib/authoring-host-m8-GmXIx.js +348 -0
- package/lib/authoring-host.d.ts +5 -0
- package/lib/authoring-host.js +2 -0
- package/lib/authoring-host.types-BZt9l0xK.d.ts +77 -0
- package/lib/authoring-reference-store.d.ts +39 -0
- package/lib/authoring-reference-store.js +385 -0
- package/lib/authoring-service-WClXJGik.js +347 -0
- package/lib/authoring-service.d.ts +121 -0
- package/lib/authoring-service.js +2 -0
- package/lib/authoring-session.d.ts +5 -0
- package/lib/authoring-session.js +204 -0
- package/lib/authoring-session.types-C6HosIWU.d.ts +58 -0
- package/lib/authoring-sources-BzRuEuub.d.ts +17 -0
- package/lib/authoring-sources.d.ts +2 -0
- package/lib/authoring-sources.js +95 -0
- package/lib/authoring-workbench.d.ts +15 -0
- package/lib/authoring-workbench.js +782 -0
- package/lib/authoring.page-rpc-CYYWsmxc.d.ts +30 -0
- package/lib/authoring.page-rpc.d.ts +2 -0
- package/lib/authoring.page-rpc.js +55 -0
- package/lib/authoring.remote-client.d.ts +2557 -0
- package/lib/authoring.remote-client.js +435 -0
- package/lib/client-adapter-BmIzQcMT.d.ts +65 -0
- package/lib/client-adapter.d.ts +2 -64
- package/lib/client.js +1 -2
- package/lib/css-assets/dsh-design-studio-page/9178281a0695f79ff8bff8eca529231b67f896dc1c12c8dbaaad24b32dd3fc07.eot +0 -0
- package/lib/css-assets/dsh-design-studio-page/b366faf39f6bcc48f10b10f2860e5ebe57091cb2a15beeed8a0977e24560bbba.svg +224 -0
- package/lib/css-assets/dsh-design-studio-page/manifest.json +20 -0
- package/lib/index.js +4 -105
- package/lib/kit-files-DKBlBOG8.js +159 -0
- package/lib/page-client.js +4 -23849
- package/lib/page-path-BkPiqrUH.d.ts +25 -0
- package/lib/page-path.d.ts +2 -2
- package/lib/page-path.js +70 -2
- package/lib/page-rpc-DXbf4seT.d.ts +24 -0
- package/lib/page-rpc.d.ts +2 -15
- package/lib/page-rpc.js +9 -4
- package/lib/page.d.ts +5 -3
- package/lib/page.js +219 -2
- package/lib/remote-method-ab_lHwWN.js +16 -0
- package/lib/typert.host.d.ts +827 -2
- package/lib/typert.host.js +2 -1
- package/lib/typert.remote-client-CWz-anTr.d.ts +37 -0
- package/lib/typert.remote-client.d.ts +1 -36
- package/licenses/dsh-distribution.json +8644 -149
- package/package.json +73 -10
- package/lib/page-path-wn8e_fid.d.ts +0 -14
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { a as StudioPreparedLocator } from "./authoring-host.types-BZt9l0xK.js";
|
|
2
|
+
//#region src/page-path.d.ts
|
|
3
|
+
/** Base path owned by the Studio page route. */
|
|
4
|
+
declare const STUDIO_PAGE_BASE = "/design-studio/";
|
|
5
|
+
/** Slash-less route that redirects to the base path. */
|
|
6
|
+
declare const STUDIO_PAGE_PATH = "/design-studio";
|
|
7
|
+
/** Bundle file name served below the base path. */
|
|
8
|
+
declare const STUDIO_PAGE_BUNDLE = "page-client.js";
|
|
9
|
+
interface StudioAuthoringLocation {
|
|
10
|
+
readonly presentation: 'content-only' | 'full';
|
|
11
|
+
readonly initialInstance?: Pick<StudioPreparedLocator, 'id' | 'expectedRevision' | 'bundle'>;
|
|
12
|
+
readonly preparedLocator?: StudioPreparedLocator;
|
|
13
|
+
}
|
|
14
|
+
/** A locator for one parent-owned S2 receipt, never a save or identity capability. */
|
|
15
|
+
declare function studioPreparedUrl(agentId: string, locator: StudioPreparedLocator): string;
|
|
16
|
+
/** Parse exact navigation intent; the live Host revalidates its parent and candidate. */
|
|
17
|
+
declare function studioAuthoringLocation(href?: string | undefined): StudioAuthoringLocation;
|
|
18
|
+
/** Read only an explicit Session locator; the client codec and Host lookup validate it. */
|
|
19
|
+
declare function studioAuthoringSessionLocator(href?: string | undefined): string | undefined;
|
|
20
|
+
/** Recognize only the official Desktop application authority, not a redirect hint. */
|
|
21
|
+
declare function isDshDesktopLocation(href?: string | undefined): boolean;
|
|
22
|
+
/** Desktop returns through the known application root; no caller-controlled redirect. */
|
|
23
|
+
declare function studioConversationReturnPath(href?: string | undefined): '/' | undefined;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { isDshDesktopLocation as a, studioConversationReturnPath as c, StudioAuthoringLocation as i, studioPreparedUrl as l, STUDIO_PAGE_BUNDLE as n, studioAuthoringLocation as o, STUDIO_PAGE_PATH as r, studioAuthoringSessionLocator as s, STUDIO_PAGE_BASE as t };
|
package/lib/page-path.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as studioConversationReturnPath, i as
|
|
2
|
-
export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, isDshDesktopLocation, studioConversationReturnPath };
|
|
1
|
+
import { a as isDshDesktopLocation, c as studioConversationReturnPath, i as StudioAuthoringLocation, l as studioPreparedUrl, n as STUDIO_PAGE_BUNDLE, o as studioAuthoringLocation, r as STUDIO_PAGE_PATH, s as studioAuthoringSessionLocator, t as STUDIO_PAGE_BASE } from "./page-path-BkPiqrUH.js";
|
|
2
|
+
export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, StudioAuthoringLocation, isDshDesktopLocation, studioAuthoringLocation, studioAuthoringSessionLocator, studioConversationReturnPath, studioPreparedUrl };
|
package/lib/page-path.js
CHANGED
|
@@ -1,11 +1,79 @@
|
|
|
1
1
|
//#region src/page-path.ts
|
|
2
|
-
/** Browser-safe path constants shared by the host route and the launcher. */
|
|
3
2
|
/** Base path owned by the Studio page route. */
|
|
4
3
|
const STUDIO_PAGE_BASE = "/design-studio/";
|
|
5
4
|
/** Slash-less route that redirects to the base path. */
|
|
6
5
|
const STUDIO_PAGE_PATH = "/design-studio";
|
|
7
6
|
/** Bundle file name served below the base path. */
|
|
8
7
|
const STUDIO_PAGE_BUNDLE = "page-client.js";
|
|
8
|
+
/** A locator for one parent-owned S2 receipt, never a save or identity capability. */
|
|
9
|
+
function studioPreparedUrl(agentId, locator) {
|
|
10
|
+
const { requestId, digest, ...target } = locator;
|
|
11
|
+
const query = new URLSearchParams({
|
|
12
|
+
agentId,
|
|
13
|
+
presentation: "full",
|
|
14
|
+
studioTarget: JSON.stringify(target),
|
|
15
|
+
studioRequest: requestId,
|
|
16
|
+
studioDigest: digest
|
|
17
|
+
});
|
|
18
|
+
return `${STUDIO_PAGE_BASE}?${query}`;
|
|
19
|
+
}
|
|
20
|
+
/** Parse exact navigation intent; the live Host revalidates its parent and candidate. */
|
|
21
|
+
function studioAuthoringLocation(href = globalThis.location?.href) {
|
|
22
|
+
if (href === void 0) return { presentation: "full" };
|
|
23
|
+
const url = new URL(href);
|
|
24
|
+
const one = (name) => {
|
|
25
|
+
const values = url.searchParams.getAll(name);
|
|
26
|
+
if (values.length > 1) throw new Error(`Studio requires one ${name} locator`);
|
|
27
|
+
return values[0];
|
|
28
|
+
};
|
|
29
|
+
const presentation = one("presentation") ?? "full";
|
|
30
|
+
if (presentation !== "content-only" && presentation !== "full") throw new Error("Unknown Studio presentation");
|
|
31
|
+
const raw = one("studioTarget"), requestId = one("studioRequest"), digest = one("studioDigest");
|
|
32
|
+
if (raw === void 0 && requestId === void 0 && digest === void 0) return { presentation };
|
|
33
|
+
if (!studioAuthoringSessionLocator(href)) throw new Error("Studio candidate navigation requires an explicit native Session");
|
|
34
|
+
const validIdentity = (value) => typeof value === "string" && value.length > 0 && !/[\u0000-\u001f\u007f]/u.test(value);
|
|
35
|
+
const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
36
|
+
if (raw === void 0 || raw.length > 8192) throw new Error("Studio target is missing or too large");
|
|
37
|
+
const value = JSON.parse(raw);
|
|
38
|
+
if (!object(value) || Object.keys(value).some((key) => ![
|
|
39
|
+
"id",
|
|
40
|
+
"expectedRevision",
|
|
41
|
+
"bundle"
|
|
42
|
+
].includes(key)) || !validIdentity(value.id) || !Number.isSafeInteger(value.expectedRevision) || Number(value.expectedRevision) < 0) throw new Error("Invalid Studio target");
|
|
43
|
+
const pin = value.bundle;
|
|
44
|
+
if (pin !== void 0 && (!object(pin) || Object.keys(pin).some((key) => !["name", "version"].includes(key)) || !validIdentity(pin.name) || !validIdentity(pin.version))) throw new Error("Invalid Studio Bundle pin");
|
|
45
|
+
const initialInstance = {
|
|
46
|
+
id: value.id,
|
|
47
|
+
expectedRevision: value.expectedRevision,
|
|
48
|
+
...pin === void 0 ? {} : { bundle: {
|
|
49
|
+
name: pin.name,
|
|
50
|
+
version: pin.version
|
|
51
|
+
} }
|
|
52
|
+
};
|
|
53
|
+
if (requestId === void 0 && digest === void 0) return {
|
|
54
|
+
presentation,
|
|
55
|
+
initialInstance
|
|
56
|
+
};
|
|
57
|
+
if (!validIdentity(requestId) || requestId.length > 128 || digest === void 0 || !/^[a-f0-9]{64}$/u.test(digest)) throw new Error("Invalid Studio candidate locator");
|
|
58
|
+
return {
|
|
59
|
+
presentation,
|
|
60
|
+
initialInstance,
|
|
61
|
+
preparedLocator: {
|
|
62
|
+
...initialInstance,
|
|
63
|
+
requestId,
|
|
64
|
+
digest
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Read only an explicit Session locator; the client codec and Host lookup validate it. */
|
|
69
|
+
function studioAuthoringSessionLocator(href = globalThis.location?.href) {
|
|
70
|
+
if (href === void 0) return void 0;
|
|
71
|
+
const url = new URL(href);
|
|
72
|
+
const locators = url.searchParams.getAll("agentId");
|
|
73
|
+
if (!locators.length) return void 0;
|
|
74
|
+
if (locators.length !== 1 || url.pathname !== "/design-studio/" && url.pathname !== "/design-studio") throw new Error("Studio authoring requires one explicit Session locator on its owned page");
|
|
75
|
+
return locators[0];
|
|
76
|
+
}
|
|
9
77
|
/** Recognize only the official Desktop application authority, not a redirect hint. */
|
|
10
78
|
function isDshDesktopLocation(href = globalThis.location?.href) {
|
|
11
79
|
if (href === void 0) return false;
|
|
@@ -23,4 +91,4 @@ function studioConversationReturnPath(href = globalThis.location?.href) {
|
|
|
23
91
|
return pathname === "/design-studio/" || pathname === "/design-studio" ? "/" : void 0;
|
|
24
92
|
}
|
|
25
93
|
//#endregion
|
|
26
|
-
export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, isDshDesktopLocation, studioConversationReturnPath };
|
|
94
|
+
export { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH, isDshDesktopLocation, studioAuthoringLocation, studioAuthoringSessionLocator, studioConversationReturnPath, studioPreparedUrl };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { n as DshStudioRemote } from "./client-adapter-BmIzQcMT.js";
|
|
2
|
+
import { r as StudioStrictCodec } from "./typert.remote-client-CWz-anTr.js";
|
|
3
|
+
//#region src/page-rpc.d.ts
|
|
4
|
+
/** Page transport with the same signature as the global fetch. */
|
|
5
|
+
type StudioPageFetch = (input: URL, init: RequestInit) => Promise<Response>;
|
|
6
|
+
/** Shared carrier for the existing read namespace and optional scoped methods. */
|
|
7
|
+
interface StudioPageRpcDescriptor {
|
|
8
|
+
readonly namespace: string;
|
|
9
|
+
readonly method: string;
|
|
10
|
+
readonly result: StudioStrictCodec;
|
|
11
|
+
}
|
|
12
|
+
/** One session-free read-only studioManagement method. */
|
|
13
|
+
type StudioRpcMethod = 'workspace' | 'readKitWorkspace' | 'readKitFiles' | 'readKitFile' | 'readBundleWorkspace' | 'readBundleFile';
|
|
14
|
+
/** Same Host wire, correlation, strict result and custom-scheme proxy for every Studio namespace. */
|
|
15
|
+
declare function callStudioRemote<T>(descriptor: StudioPageRpcDescriptor, args: Record<string, unknown>, doFetch: StudioPageFetch): Promise<T>;
|
|
16
|
+
/**
|
|
17
|
+
* Build the session-free Studio Remote over one same-origin /api transport.
|
|
18
|
+
* No DSH client plugin, runtime, or module loader boots for this page.
|
|
19
|
+
* @param doFetch - transport override for tests; defaults to the page fetch.
|
|
20
|
+
* @returns the Remote namespace consumed by the shared client adapter.
|
|
21
|
+
*/
|
|
22
|
+
declare function createSameOriginStudioRemote(doFetch?: StudioPageFetch): DshStudioRemote;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createSameOriginStudioRemote as a, callStudioRemote as i, StudioPageRpcDescriptor as n, StudioRpcMethod as r, StudioPageFetch as t };
|
package/lib/page-rpc.d.ts
CHANGED
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/** Page transport with the same signature as the global fetch. */
|
|
4
|
-
type StudioPageFetch = (input: URL, init: RequestInit) => Promise<Response>;
|
|
5
|
-
/** One session-free read-only studioManagement method. */
|
|
6
|
-
type StudioRpcMethod = 'workspace' | 'readKitWorkspace' | 'readKitFiles' | 'readKitFile' | 'readBundleWorkspace' | 'readBundleFile';
|
|
7
|
-
/**
|
|
8
|
-
* Build the session-free Studio Remote over one same-origin /api transport.
|
|
9
|
-
* No DSH client plugin, runtime, or module loader boots for this page.
|
|
10
|
-
* @param doFetch - transport override for tests; defaults to the page fetch.
|
|
11
|
-
* @returns the Remote namespace consumed by the shared client adapter.
|
|
12
|
-
*/
|
|
13
|
-
declare function createSameOriginStudioRemote(doFetch?: StudioPageFetch): DshStudioRemote;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { StudioPageFetch, StudioRpcMethod, createSameOriginStudioRemote };
|
|
1
|
+
import { a as createSameOriginStudioRemote, i as callStudioRemote, n as StudioPageRpcDescriptor, r as StudioRpcMethod, t as StudioPageFetch } from "./page-rpc-DXbf4seT.js";
|
|
2
|
+
export { StudioPageFetch, StudioPageRpcDescriptor, StudioRpcMethod, callStudioRemote, createSameOriginStudioRemote };
|
package/lib/page-rpc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isDshDesktopLocation } from "./page-path.js";
|
|
2
1
|
import { TYPERT_REMOTE } from "./typert.remote-client.js";
|
|
2
|
+
import { isDshDesktopLocation } from "./page-path.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/page-rpc.ts
|
|
5
5
|
/** Thin same-origin RPC transport for the standalone Studio page. */
|
|
@@ -40,14 +40,19 @@ function resolveBase() {
|
|
|
40
40
|
async function callStudioManagement(method, args, doFetch) {
|
|
41
41
|
const descriptor = TYPERT_REMOTE.descriptors.find((candidate) => candidate.method === method);
|
|
42
42
|
if (descriptor === void 0) throw new Error(`studio page rpc: unknown method ${JSON.stringify(method)}`);
|
|
43
|
+
return callStudioRemote(descriptor, args, doFetch);
|
|
44
|
+
}
|
|
45
|
+
/** Same Host wire, correlation, strict result and custom-scheme proxy for every Studio namespace. */
|
|
46
|
+
async function callStudioRemote(descriptor, args, doFetch) {
|
|
47
|
+
const { namespace, method } = descriptor;
|
|
43
48
|
const rpcId = crypto.randomUUID();
|
|
44
|
-
const response = await doFetch(new URL(`/api
|
|
49
|
+
const response = await doFetch(new URL(`/api/${namespace}/${method}`, resolveBase()), {
|
|
45
50
|
method: "POST",
|
|
46
51
|
headers: { "content-type": "application/json" },
|
|
47
52
|
body: JSON.stringify({
|
|
48
53
|
type: "client-request",
|
|
49
54
|
rpcId,
|
|
50
|
-
method:
|
|
55
|
+
method: `${namespace}/${method}`,
|
|
51
56
|
payload: { args }
|
|
52
57
|
})
|
|
53
58
|
});
|
|
@@ -100,4 +105,4 @@ function createSameOriginStudioRemote(doFetch = (input, init) => globalThis.fetc
|
|
|
100
105
|
});
|
|
101
106
|
}
|
|
102
107
|
//#endregion
|
|
103
|
-
export { createSameOriginStudioRemote };
|
|
108
|
+
export { callStudioRemote, createSameOriginStudioRemote };
|
package/lib/page.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { r as STUDIO_PAGE_PATH, t as STUDIO_PAGE_BASE } from "./page-path-
|
|
1
|
+
import { r as STUDIO_PAGE_PATH, t as STUDIO_PAGE_BASE } from "./page-path-BkPiqrUH.js";
|
|
2
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
3
|
//#region src/page.d.ts
|
|
3
4
|
/** Minimal WebServer surface consumed by this plugin. */
|
|
4
5
|
interface StudioPageWebServer {
|
|
@@ -6,18 +7,19 @@ interface StudioPageWebServer {
|
|
|
6
7
|
register(route: {
|
|
7
8
|
readonly kind: 'exact' | 'prefix';
|
|
8
9
|
readonly path: string;
|
|
9
|
-
readonly handler: (req:
|
|
10
|
+
readonly handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
|
|
10
11
|
}): () => void;
|
|
11
12
|
}
|
|
12
13
|
/** Incoming request slice read by the Studio route. */
|
|
13
14
|
interface StudioPageRequest {
|
|
14
15
|
readonly url?: string;
|
|
16
|
+
readonly method?: string;
|
|
15
17
|
}
|
|
16
18
|
/** Server response slice written by the Studio route. */
|
|
17
19
|
interface StudioPageResponse {
|
|
18
20
|
statusCode: number;
|
|
19
21
|
setHeader(name: string, value: string): void;
|
|
20
|
-
getHeader?(name: string): string | number | undefined;
|
|
22
|
+
getHeader?(name: string): string | number | readonly string[] | undefined;
|
|
21
23
|
end(body?: string | Uint8Array): void;
|
|
22
24
|
}
|
|
23
25
|
/** Cordis plugin name. */
|
package/lib/page.js
CHANGED
|
@@ -1,5 +1,219 @@
|
|
|
1
1
|
import { STUDIO_PAGE_BASE, STUDIO_PAGE_BUNDLE, STUDIO_PAGE_PATH } from "./page-path.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
2
4
|
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpathSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { TextDecoder } from "node:util";
|
|
8
|
+
//#region ../../scripts/css-asset-route.mjs
|
|
9
|
+
const MIME = Object.freeze({
|
|
10
|
+
eot: "application/vnd.ms-fontobject",
|
|
11
|
+
svg: "image/svg+xml",
|
|
12
|
+
ttf: "font/ttf",
|
|
13
|
+
otf: "font/otf",
|
|
14
|
+
woff: "font/woff",
|
|
15
|
+
woff2: "font/woff2",
|
|
16
|
+
png: "image/png",
|
|
17
|
+
jpg: "image/jpeg",
|
|
18
|
+
jpeg: "image/jpeg",
|
|
19
|
+
gif: "image/gif",
|
|
20
|
+
webp: "image/webp",
|
|
21
|
+
avif: "image/avif"
|
|
22
|
+
});
|
|
23
|
+
/** Serve only a verified, immutable CSS asset table, never a directory.
|
|
24
|
+
* Mount behind the owning host's existing access policy. This helper does not
|
|
25
|
+
* supply authentication, register a host route, or certify a packaged build.
|
|
26
|
+
*/
|
|
27
|
+
function createCssAssetHandler({ prefix, assets }) {
|
|
28
|
+
if (typeof prefix !== "string" || !/^\/design-system-assets\/[a-z0-9-]+\/$/u.test(prefix)) throw Error("Invalid owned CSS asset prefix");
|
|
29
|
+
if (!Array.isArray(assets) || assets.length > 512) throw Error("CSS asset table exceeds budget");
|
|
30
|
+
const table = /* @__PURE__ */ new Map();
|
|
31
|
+
let total = 0;
|
|
32
|
+
for (const asset of assets) {
|
|
33
|
+
if (!Buffer.isBuffer(asset?.bytes) || asset.bytes.length > 33554432) throw Error("Invalid CSS asset bytes or budget");
|
|
34
|
+
total += asset.bytes.length;
|
|
35
|
+
if (total > 67108864) throw Error("CSS asset table bytes exceed budget");
|
|
36
|
+
const match = /^([a-f0-9]{64})\.([a-z0-9]+)$/u.exec(asset.name ?? "");
|
|
37
|
+
if (!match || !Object.hasOwn(MIME, match[2]) || asset.mime !== MIME[match[2]]) throw Error("Invalid CSS asset identity or MIME");
|
|
38
|
+
const bytes = Buffer.from(asset.bytes);
|
|
39
|
+
const hash = createHash("sha256").update(bytes).digest("hex");
|
|
40
|
+
if (hash !== match[1] || asset.sha256 !== hash) throw Error("CSS asset bytes do not match content identity");
|
|
41
|
+
if (table.has(asset.name)) throw Error("Duplicate CSS asset identity");
|
|
42
|
+
table.set(asset.name, {
|
|
43
|
+
bytes,
|
|
44
|
+
mime: asset.mime,
|
|
45
|
+
etag: `"${hash}"`
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return function cssAssetHandler(request, response) {
|
|
49
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
50
|
+
if (!["GET", "HEAD"].includes(request.method)) {
|
|
51
|
+
response.statusCode = 405;
|
|
52
|
+
response.setHeader("Allow", "GET, HEAD");
|
|
53
|
+
response.end();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const url = request.url;
|
|
57
|
+
let name;
|
|
58
|
+
if (typeof url === "string" && url.startsWith(prefix) && !/[\u0000-\u0020\u007f#]/u.test(url)) {
|
|
59
|
+
const pathname = url.split("?", 1)[0];
|
|
60
|
+
try {
|
|
61
|
+
name = decodeURIComponent(pathname.slice(prefix.length));
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
const asset = typeof name === "string" && /^[a-f0-9]{64}\.[a-z0-9]+$/u.test(name) ? table.get(name) : void 0;
|
|
65
|
+
if (!asset) {
|
|
66
|
+
response.statusCode = 404;
|
|
67
|
+
response.end();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
response.statusCode = 200;
|
|
71
|
+
response.setHeader("Content-Type", asset.mime);
|
|
72
|
+
response.setHeader("Content-Length", asset.bytes.length);
|
|
73
|
+
response.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
74
|
+
response.setHeader("ETag", asset.etag);
|
|
75
|
+
response.end(request.method === "HEAD" ? void 0 : asset.bytes);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region ../../scripts/css-asset-manifest.mjs
|
|
80
|
+
function ownedRead(root, relative, limit) {
|
|
81
|
+
let file = root;
|
|
82
|
+
const parents = [{
|
|
83
|
+
path: root,
|
|
84
|
+
info: lstatSync(root)
|
|
85
|
+
}];
|
|
86
|
+
const verifyParents = () => {
|
|
87
|
+
for (const parent of parents) {
|
|
88
|
+
const current = lstatSync(parent.path);
|
|
89
|
+
if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== parent.info.dev || current.ino !== parent.info.ino || realpathSync(parent.path) !== parent.path) throw Error("Packaged CSS parent ownership changed");
|
|
90
|
+
}
|
|
91
|
+
if (realpathSync(file) !== file) throw Error("Packaged CSS resolved ownership changed");
|
|
92
|
+
};
|
|
93
|
+
const parts = relative.split("/");
|
|
94
|
+
for (const [index, part] of parts.entries()) {
|
|
95
|
+
if (!part || part === "." || part === ".." || /[\\\u0000-\u001f]/u.test(part)) throw Error("Invalid packaged CSS asset path");
|
|
96
|
+
file = join(file, part);
|
|
97
|
+
const info = lstatSync(file);
|
|
98
|
+
if (info.isSymbolicLink() || !(index === parts.length - 1 ? info.isFile() : info.isDirectory())) throw Error("CSS package assets must be regular owned files");
|
|
99
|
+
if (index !== parts.length - 1) parents.push({
|
|
100
|
+
path: file,
|
|
101
|
+
info
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const checked = lstatSync(file);
|
|
105
|
+
verifyParents();
|
|
106
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
107
|
+
try {
|
|
108
|
+
const info = fstatSync(fd);
|
|
109
|
+
verifyParents();
|
|
110
|
+
if (!info.isFile() || info.dev !== checked.dev || info.ino !== checked.ino || info.size !== checked.size) throw Error("Packaged CSS asset identity changed");
|
|
111
|
+
if (info.size > limit) throw Error("Packaged CSS bytes exceed budget");
|
|
112
|
+
const bytes = Buffer.alloc(info.size + 1);
|
|
113
|
+
let length = 0;
|
|
114
|
+
while (length < bytes.length) {
|
|
115
|
+
const count = readSync(fd, bytes, length, bytes.length - length, null);
|
|
116
|
+
if (!count) break;
|
|
117
|
+
length += count;
|
|
118
|
+
}
|
|
119
|
+
const final = fstatSync(fd);
|
|
120
|
+
verifyParents();
|
|
121
|
+
if (length !== info.size || final.size !== info.size || final.mtimeMs !== info.mtimeMs || final.ctimeMs !== info.ctimeMs) throw Error("Packaged CSS asset changed");
|
|
122
|
+
return bytes.subarray(0, length);
|
|
123
|
+
} finally {
|
|
124
|
+
closeSync(fd);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Read only the current content-addressed manifest, never enumerate a directory.
|
|
128
|
+
* Stale build files are neither admitted nor exposed. The package root is an
|
|
129
|
+
* explicit owner-selected directory; native list paths are not accepted here.
|
|
130
|
+
*/
|
|
131
|
+
function loadPackagedCssAssets(packageRoot, prefix) {
|
|
132
|
+
createCssAssetHandler({
|
|
133
|
+
prefix,
|
|
134
|
+
assets: []
|
|
135
|
+
});
|
|
136
|
+
const root = realpathSync(packageRoot);
|
|
137
|
+
const folder = `css-assets/${prefix.split("/")[2]}`;
|
|
138
|
+
const manifestBytes = ownedRead(root, `${folder}/manifest.json`, 262144);
|
|
139
|
+
const manifest = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(manifestBytes));
|
|
140
|
+
if (manifest?.schema !== "design-system-css-assets-v1" || manifest.prefix !== prefix || !Array.isArray(manifest.assets) || manifest.assets.length > 512) throw Error("Invalid packaged CSS asset manifest");
|
|
141
|
+
const seen = /* @__PURE__ */ new Set(), assets = [];
|
|
142
|
+
let total = 0;
|
|
143
|
+
for (const entry of manifest.assets) {
|
|
144
|
+
if (!/^[a-f0-9]{64}\.[a-z0-9]+$/u.test(entry?.name ?? "") || entry.file !== `${folder}/${entry.name}` || seen.has(entry.name)) throw Error("Invalid or duplicate packaged CSS identity");
|
|
145
|
+
if (!Number.isSafeInteger(entry.bytes) || entry.bytes < 0 || entry.bytes > 33554432) throw Error("Invalid packaged CSS byte budget");
|
|
146
|
+
total += entry.bytes;
|
|
147
|
+
if (total > 67108864) throw Error("Packaged CSS total exceeds budget");
|
|
148
|
+
seen.add(entry.name);
|
|
149
|
+
}
|
|
150
|
+
for (const entry of manifest.assets) {
|
|
151
|
+
const bytes = ownedRead(root, entry.file, entry.bytes);
|
|
152
|
+
if (bytes.length !== entry.bytes) throw Error("Packaged CSS byte length mismatch");
|
|
153
|
+
assets.push({
|
|
154
|
+
name: entry.name,
|
|
155
|
+
bytes,
|
|
156
|
+
sha256: entry.sha256,
|
|
157
|
+
mime: entry.mime
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
handler: createCssAssetHandler({
|
|
162
|
+
prefix,
|
|
163
|
+
assets
|
|
164
|
+
}),
|
|
165
|
+
manifest,
|
|
166
|
+
assets
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region ../../scripts/css-asset-host.mjs
|
|
171
|
+
/** Match the official webServer.register route/disposer contract.
|
|
172
|
+
* Asset tables are verified before the first registration. Call within the
|
|
173
|
+
* owning plugin lifecycle and existing host access policy; no fallback seat
|
|
174
|
+
* or upstream /plugins route is claimed by this helper.
|
|
175
|
+
*/
|
|
176
|
+
function registerPackagedCssAssets(webServer, packageRoot, prefixes) {
|
|
177
|
+
if (typeof webServer?.register !== "function" || !Array.isArray(prefixes) || !prefixes.length || new Set(prefixes).size !== prefixes.length) throw Error("Explicit CSS route service and unique prefixes required");
|
|
178
|
+
const tables = prefixes.map((prefix) => ({
|
|
179
|
+
prefix,
|
|
180
|
+
...loadPackagedCssAssets(packageRoot, prefix)
|
|
181
|
+
}));
|
|
182
|
+
const disposers = [];
|
|
183
|
+
try {
|
|
184
|
+
for (const table of tables) {
|
|
185
|
+
const dispose = webServer.register({
|
|
186
|
+
kind: "prefix",
|
|
187
|
+
path: table.prefix.slice(0, -1),
|
|
188
|
+
handler: table.handler
|
|
189
|
+
});
|
|
190
|
+
if (typeof dispose !== "function") throw Error("Host CSS registration did not return a disposer");
|
|
191
|
+
disposers.push(dispose);
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const failures = [];
|
|
195
|
+
for (const dispose of disposers.reverse()) try {
|
|
196
|
+
dispose();
|
|
197
|
+
} catch (cleanupError) {
|
|
198
|
+
failures.push(cleanupError);
|
|
199
|
+
}
|
|
200
|
+
if (failures.length) throw new AggregateError([error, ...failures], "CSS route registration and rollback failed");
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
let closed = false;
|
|
204
|
+
return () => {
|
|
205
|
+
if (closed) return;
|
|
206
|
+
closed = true;
|
|
207
|
+
const failures = [];
|
|
208
|
+
for (const dispose of disposers.reverse()) try {
|
|
209
|
+
dispose();
|
|
210
|
+
} catch (error) {
|
|
211
|
+
failures.push(error);
|
|
212
|
+
}
|
|
213
|
+
if (failures.length) throw new AggregateError(failures, "CSS route disposal failed");
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
3
217
|
//#region src/page.ts
|
|
4
218
|
/** Plugin-owned same-origin route serving the standalone Design Studio page. */
|
|
5
219
|
/** Cordis plugin name. */
|
|
@@ -46,8 +260,10 @@ function shellHtml(base) {
|
|
|
46
260
|
function apply(ctx) {
|
|
47
261
|
const face = ctx;
|
|
48
262
|
const bundleUrl = new URL(`./${STUDIO_PAGE_BUNDLE}`, import.meta.url);
|
|
49
|
-
const redirect = async (
|
|
50
|
-
|
|
263
|
+
const redirect = async (req, res) => {
|
|
264
|
+
const queryIndex = req.url?.indexOf("?") ?? -1;
|
|
265
|
+
const query = queryIndex < 0 ? "" : req.url.slice(queryIndex);
|
|
266
|
+
send(res, 308, "text/plain; charset=utf-8", "", { Location: STUDIO_PAGE_BASE + query });
|
|
51
267
|
};
|
|
52
268
|
const handler = async (req, res) => {
|
|
53
269
|
const path = requestPath(req);
|
|
@@ -73,6 +289,7 @@ function apply(ctx) {
|
|
|
73
289
|
face.effect(() => {
|
|
74
290
|
const acquired = [];
|
|
75
291
|
try {
|
|
292
|
+
acquired.push(registerPackagedCssAssets(face.webServer, fileURLToPath(new URL(".", import.meta.url)), ["/design-system-assets/dsh-design-studio-page/"]));
|
|
76
293
|
acquired.push(face.webServer.register({
|
|
77
294
|
kind: "exact",
|
|
78
295
|
path: STUDIO_PAGE_PATH,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Remote } from "@deepseek-ai/dsh-typert-protocol";
|
|
2
|
+
//#region src/remote-method.ts
|
|
3
|
+
/** The public SDK marker, without requiring runtime decorator syntax. */
|
|
4
|
+
function markStudioRemote(prototype, name, method) {
|
|
5
|
+
Remote(method, {
|
|
6
|
+
kind: "method",
|
|
7
|
+
name,
|
|
8
|
+
static: false,
|
|
9
|
+
private: false,
|
|
10
|
+
addInitializer(initializer) {
|
|
11
|
+
initializer.call(Object.create(prototype));
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { markStudioRemote as t };
|