@parlor/shell-web 0.0.0-stage → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cychien
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # @parlor/shell-web
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The web shell: the Vite config every target builds from, file routes, the PWA and the content security policy. Installed by `@parlor/cli`; you do not depend on it directly.
4
+
5
+ ```ts
6
+ import { FRAMEWORK_VITE_KEYS } from "@parlor/shell-web";
7
+
8
+ console.log(FRAMEWORK_VITE_KEYS);
9
+ ```
10
+
11
+ Docs: https://cychien.github.io/parlor/guides/styling/
@@ -0,0 +1,12 @@
1
+ import { type LoadedConfig } from "@parlor/config";
2
+ import type { WebOptions } from "./plugin.js";
3
+ export declare function webOutDir(rootDir: string): string;
4
+ export declare function buildWeb(loaded: LoadedConfig, options?: WebOptions): Promise<string>;
5
+ export declare function ssrOutDir(rootDir: string): string;
6
+ export interface RenderBuildOptions {
7
+ /** Export conditions of the runtime the server runs on, such as `workerd`. */
8
+ readonly conditions: readonly string[];
9
+ }
10
+ /** One self-contained module for the server bundle, resolved for the runtime it runs on. */
11
+ export declare function buildRender(loaded: LoadedConfig, { conditions }: RenderBuildOptions): Promise<string>;
12
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,4FAA4F;AAC5F,wBAAsB,WAAW,CAC/B,MAAM,EAAE,YAAY,EACpB,EAAE,UAAU,EAAE,EAAE,kBAAkB,GACjC,OAAO,CAAC,MAAM,CAAC,CAmCjB"}
package/dist/build.js ADDED
@@ -0,0 +1,51 @@
1
+ import { join } from "node:path";
2
+ import { generatedDir } from "@parlor/config";
3
+ import { build, defaultServerConditions } from "vite";
4
+ import { RENDER_FILE, RENDER_MODULE } from "./ssr.js";
5
+ import { parlorViteConfig } from "./vite-config.js";
6
+ export function webOutDir(rootDir) {
7
+ return join(generatedDir(rootDir), "web");
8
+ }
9
+ export async function buildWeb(loaded, options = {}) {
10
+ const outDir = webOutDir(loaded.rootDir);
11
+ await build(parlorViteConfig(loaded, options, {
12
+ build: { outDir, emptyOutDir: true },
13
+ logLevel: "warn",
14
+ }));
15
+ return outDir;
16
+ }
17
+ export function ssrOutDir(rootDir) {
18
+ return join(generatedDir(rootDir), "ssr");
19
+ }
20
+ /** One self-contained module for the server bundle, resolved for the runtime it runs on. */
21
+ export async function buildRender(loaded, { conditions }) {
22
+ const outDir = ssrOutDir(loaded.rootDir);
23
+ await build(parlorViteConfig(loaded, { render: true }, {
24
+ build: {
25
+ ssr: true,
26
+ outDir,
27
+ emptyOutDir: true,
28
+ copyPublicDir: false,
29
+ rolldownOptions: {
30
+ input: RENDER_MODULE,
31
+ output: { entryFileNames: RENDER_FILE, codeSplitting: false },
32
+ },
33
+ },
34
+ // The same mode Vite compiles JSX for, so React's runtime matches it.
35
+ define: {
36
+ "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV ?? "production"),
37
+ },
38
+ ssr: {
39
+ noExternal: true,
40
+ resolve: {
41
+ conditions: [
42
+ ...conditions,
43
+ ...defaultServerConditions.filter((condition) => condition !== "node"),
44
+ ],
45
+ },
46
+ },
47
+ logLevel: "warn",
48
+ }));
49
+ return join(outDir, RENDER_FILE);
50
+ }
51
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,MAAM,MAAM,CAAC;AAGtD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAoB,EACpB,OAAO,GAAe,EAAE;IAExB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,KAAK,CACT,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE;QAChC,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE;QACpC,QAAQ,EAAE,MAAM;KACjB,CAAC,CACH,CAAC;IACF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC;AAOD,4FAA4F;AAC5F,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAoB,EACpB,EAAE,UAAU,EAAsB;IAElC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,KAAK,CACT,gBAAgB,CACd,MAAM,EACN,EAAE,MAAM,EAAE,IAAI,EAAE,EAChB;QACE,KAAK,EAAE;YACL,GAAG,EAAE,IAAI;YACT,MAAM;YACN,WAAW,EAAE,IAAI;YACjB,aAAa,EAAE,KAAK;YACpB,eAAe,EAAE;gBACf,KAAK,EAAE,aAAa;gBACpB,MAAM,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,KAAK,EAAE;aAC9D;SACF;QACD,sEAAsE;QACtE,MAAM,EAAE;YACN,sBAAsB,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,YAAY,CAAC;SAC7E;QACD,GAAG,EAAE;YACH,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE;gBACP,UAAU,EAAE;oBACV,GAAG,UAAU;oBACb,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,MAAM,CAAC;iBACvE;aACF;SACF;QACD,QAAQ,EAAE,MAAM;KACjB,CACF,CACF,CAAC;IACF,OAAO,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AACnC,CAAC","sourcesContent":["import { join } from \"node:path\";\n\nimport { generatedDir, type LoadedConfig } from \"@parlor/config\";\nimport { build, defaultServerConditions } from \"vite\";\n\nimport type { WebOptions } from \"./plugin.js\";\nimport { RENDER_FILE, RENDER_MODULE } from \"./ssr.js\";\nimport { parlorViteConfig } from \"./vite-config.js\";\n\nexport function webOutDir(rootDir: string): string {\n return join(generatedDir(rootDir), \"web\");\n}\n\nexport async function buildWeb(\n loaded: LoadedConfig,\n options: WebOptions = {},\n): Promise<string> {\n const outDir = webOutDir(loaded.rootDir);\n await build(\n parlorViteConfig(loaded, options, {\n build: { outDir, emptyOutDir: true },\n logLevel: \"warn\",\n }),\n );\n return outDir;\n}\n\nexport function ssrOutDir(rootDir: string): string {\n return join(generatedDir(rootDir), \"ssr\");\n}\n\nexport interface RenderBuildOptions {\n /** Export conditions of the runtime the server runs on, such as `workerd`. */\n readonly conditions: readonly string[];\n}\n\n/** One self-contained module for the server bundle, resolved for the runtime it runs on. */\nexport async function buildRender(\n loaded: LoadedConfig,\n { conditions }: RenderBuildOptions,\n): Promise<string> {\n const outDir = ssrOutDir(loaded.rootDir);\n await build(\n parlorViteConfig(\n loaded,\n { render: true },\n {\n build: {\n ssr: true,\n outDir,\n emptyOutDir: true,\n copyPublicDir: false,\n rolldownOptions: {\n input: RENDER_MODULE,\n output: { entryFileNames: RENDER_FILE, codeSplitting: false },\n },\n },\n // The same mode Vite compiles JSX for, so React's runtime matches it.\n define: {\n \"process.env.NODE_ENV\": JSON.stringify(process.env.NODE_ENV ?? \"production\"),\n },\n ssr: {\n noExternal: true,\n resolve: {\n conditions: [\n ...conditions,\n ...defaultServerConditions.filter((condition) => condition !== \"node\"),\n ],\n },\n },\n logLevel: \"warn\",\n },\n ),\n );\n return join(outDir, RENDER_FILE);\n}\n"]}
package/dist/csp.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { CSP_DIRECTIVES, type ParlorConfig } from "@parlor/config";
2
+ export type CspDirective = (typeof CSP_DIRECTIVES)[number];
3
+ export type CspDirectives = {
4
+ readonly [D in CspDirective]?: readonly string[];
5
+ };
6
+ export interface DefaultCspInput {
7
+ /** The backend origin the shell build talks to (`server.url`), or the page's own. */
8
+ readonly serverOrigin?: string | undefined;
9
+ /** Extra origins `connect-src` needs, such as a sync relay. */
10
+ readonly connect?: readonly string[] | undefined;
11
+ /** Allows Vite's HMR client and websocket. */
12
+ readonly dev?: boolean | undefined;
13
+ }
14
+ /**
15
+ * The policy a chat-enabled app ships by default: `script-src 'self'` with no inline
16
+ * script or eval, `connect-src` only to its backend, `frame-ancestors 'none'`,
17
+ * `object-src 'none'`.
18
+ */
19
+ export declare function defaultCsp(input?: DefaultCspInput): CspDirectives;
20
+ /**
21
+ * Adds sources an app allows on top of the default. Never removes a restriction: a
22
+ * `script-src` addition of `'unsafe-inline'` or `'unsafe-eval'` throws.
23
+ */
24
+ export declare function extendCsp(base: CspDirectives, additions: CspDirectives): CspDirectives;
25
+ /** The header or `<meta http-equiv>` value. */
26
+ export declare function serializeCsp(directives: CspDirectives): string;
27
+ /** A `<meta http-equiv>` policy cannot carry `frame-ancestors`; browsers ignore it and warn. */
28
+ export declare function metaCsp(directives: CspDirectives): CspDirectives;
29
+ /** The policy an app ships: the default for this shell's backend plus `web.csp`. */
30
+ export declare function appCsp(config: ParlorConfig, input: DefaultCspInput): CspDirectives;
31
+ //# sourceMappingURL=csp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../src/csp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,YAAY,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE;CAAE,CAAC;AAEjF,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACjD,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACpC;AAOD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,GAAE,eAAoB,GAAG,aAAa,CAwBrE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,GAAG,aAAa,CAqBtF;AAED,+CAA+C;AAC/C,wBAAgB,YAAY,CAAC,UAAU,EAAE,aAAa,GAAG,MAAM,CAW9D;AAED,gGAAgG;AAChG,wBAAgB,OAAO,CAAC,UAAU,EAAE,aAAa,GAAG,aAAa,CAGhE;AAED,oFAAoF;AACpF,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,GAAG,aAAa,CAElF"}
package/dist/csp.js ADDED
@@ -0,0 +1,83 @@
1
+ import { CSP_DIRECTIVES } from "@parlor/config";
2
+ const SELF = "'self'";
3
+ const NONE = "'none'";
4
+ const SCRIPT_LOOSENERS = new Set(["'unsafe-inline'", "'unsafe-eval'"]);
5
+ const UNSAFE_SOURCE = /[\s;,]/;
6
+ /**
7
+ * The policy a chat-enabled app ships by default: `script-src 'self'` with no inline
8
+ * script or eval, `connect-src` only to its backend, `frame-ancestors 'none'`,
9
+ * `object-src 'none'`.
10
+ */
11
+ export function defaultCsp(input = {}) {
12
+ const connect = [
13
+ SELF,
14
+ ...(input.serverOrigin ? [new URL(input.serverOrigin).origin] : []),
15
+ ...(input.connect ?? []),
16
+ ...(input.dev ? ["ws:"] : []),
17
+ ];
18
+ const inline = input.dev ? ["'unsafe-inline'"] : [];
19
+ return {
20
+ "default-src": [SELF],
21
+ "script-src": [SELF, ...inline],
22
+ "style-src": [SELF, ...inline],
23
+ "img-src": [SELF, "data:"],
24
+ "font-src": [SELF],
25
+ "connect-src": unique(connect),
26
+ "media-src": [SELF],
27
+ "worker-src": [SELF],
28
+ "manifest-src": [SELF],
29
+ "frame-src": [NONE],
30
+ "frame-ancestors": [NONE],
31
+ "object-src": [NONE],
32
+ "base-uri": [SELF],
33
+ "form-action": [SELF],
34
+ };
35
+ }
36
+ /**
37
+ * Adds sources an app allows on top of the default. Never removes a restriction: a
38
+ * `script-src` addition of `'unsafe-inline'` or `'unsafe-eval'` throws.
39
+ */
40
+ export function extendCsp(base, additions) {
41
+ const extended = { ...base };
42
+ for (const directive of CSP_DIRECTIVES) {
43
+ const added = additions[directive];
44
+ if (!added || added.length === 0)
45
+ continue;
46
+ if (directive === "script-src") {
47
+ const loosening = added.find((source) => SCRIPT_LOOSENERS.has(source.toLowerCase()));
48
+ if (loosening) {
49
+ throw new Error(`script-src cannot take ${loosening}: the default policy forbids it`);
50
+ }
51
+ }
52
+ const current = (base[directive] ?? []).filter((source) => source !== NONE || added.includes(NONE));
53
+ extended[directive] = unique([...current, ...added]);
54
+ }
55
+ return extended;
56
+ }
57
+ /** The header or `<meta http-equiv>` value. */
58
+ export function serializeCsp(directives) {
59
+ return CSP_DIRECTIVES.flatMap((directive) => {
60
+ const sources = directives[directive];
61
+ if (!sources)
62
+ return [];
63
+ for (const source of sources) {
64
+ if (UNSAFE_SOURCE.test(source) || source === "") {
65
+ throw new Error(`${directive} has an invalid source: ${JSON.stringify(source)}`);
66
+ }
67
+ }
68
+ return [[directive, ...(sources.length === 0 ? [NONE] : sources)].join(" ")];
69
+ }).join("; ");
70
+ }
71
+ /** A `<meta http-equiv>` policy cannot carry `frame-ancestors`; browsers ignore it and warn. */
72
+ export function metaCsp(directives) {
73
+ const { "frame-ancestors": _ignored, ...rest } = directives;
74
+ return rest;
75
+ }
76
+ /** The policy an app ships: the default for this shell's backend plus `web.csp`. */
77
+ export function appCsp(config, input) {
78
+ return extendCsp(defaultCsp(input), config.web.csp ?? {});
79
+ }
80
+ function unique(sources) {
81
+ return [...new Set(sources)];
82
+ }
83
+ //# sourceMappingURL=csp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csp.js","sourceRoot":"","sources":["../src/csp.ts"],"names":[],"mappings":"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","sourcesContent":["import { CSP_DIRECTIVES, type ParlorConfig } from \"@parlor/config\";\n\nexport type CspDirective = (typeof CSP_DIRECTIVES)[number];\n\nexport type CspDirectives = { readonly [D in CspDirective]?: readonly string[] };\n\nexport interface DefaultCspInput {\n /** The backend origin the shell build talks to (`server.url`), or the page's own. */\n readonly serverOrigin?: string | undefined;\n /** Extra origins `connect-src` needs, such as a sync relay. */\n readonly connect?: readonly string[] | undefined;\n /** Allows Vite's HMR client and websocket. */\n readonly dev?: boolean | undefined;\n}\n\nconst SELF = \"'self'\";\nconst NONE = \"'none'\";\nconst SCRIPT_LOOSENERS = new Set([\"'unsafe-inline'\", \"'unsafe-eval'\"]);\nconst UNSAFE_SOURCE = /[\\s;,]/;\n\n/**\n * The policy a chat-enabled app ships by default: `script-src 'self'` with no inline\n * script or eval, `connect-src` only to its backend, `frame-ancestors 'none'`,\n * `object-src 'none'`.\n */\nexport function defaultCsp(input: DefaultCspInput = {}): CspDirectives {\n const connect = [\n SELF,\n ...(input.serverOrigin ? [new URL(input.serverOrigin).origin] : []),\n ...(input.connect ?? []),\n ...(input.dev ? [\"ws:\"] : []),\n ];\n const inline = input.dev ? [\"'unsafe-inline'\"] : [];\n return {\n \"default-src\": [SELF],\n \"script-src\": [SELF, ...inline],\n \"style-src\": [SELF, ...inline],\n \"img-src\": [SELF, \"data:\"],\n \"font-src\": [SELF],\n \"connect-src\": unique(connect),\n \"media-src\": [SELF],\n \"worker-src\": [SELF],\n \"manifest-src\": [SELF],\n \"frame-src\": [NONE],\n \"frame-ancestors\": [NONE],\n \"object-src\": [NONE],\n \"base-uri\": [SELF],\n \"form-action\": [SELF],\n };\n}\n\n/**\n * Adds sources an app allows on top of the default. Never removes a restriction: a\n * `script-src` addition of `'unsafe-inline'` or `'unsafe-eval'` throws.\n */\nexport function extendCsp(base: CspDirectives, additions: CspDirectives): CspDirectives {\n const extended: { -readonly [D in CspDirective]?: readonly string[] } = { ...base };\n for (const directive of CSP_DIRECTIVES) {\n const added = additions[directive];\n if (!added || added.length === 0) continue;\n if (directive === \"script-src\") {\n const loosening = added.find((source) =>\n SCRIPT_LOOSENERS.has(source.toLowerCase()),\n );\n if (loosening) {\n throw new Error(\n `script-src cannot take ${loosening}: the default policy forbids it`,\n );\n }\n }\n const current = (base[directive] ?? []).filter(\n (source) => source !== NONE || added.includes(NONE),\n );\n extended[directive] = unique([...current, ...added]);\n }\n return extended;\n}\n\n/** The header or `<meta http-equiv>` value. */\nexport function serializeCsp(directives: CspDirectives): string {\n return CSP_DIRECTIVES.flatMap((directive) => {\n const sources = directives[directive];\n if (!sources) return [];\n for (const source of sources) {\n if (UNSAFE_SOURCE.test(source) || source === \"\") {\n throw new Error(`${directive} has an invalid source: ${JSON.stringify(source)}`);\n }\n }\n return [[directive, ...(sources.length === 0 ? [NONE] : sources)].join(\" \")];\n }).join(\"; \");\n}\n\n/** A `<meta http-equiv>` policy cannot carry `frame-ancestors`; browsers ignore it and warn. */\nexport function metaCsp(directives: CspDirectives): CspDirectives {\n const { \"frame-ancestors\": _ignored, ...rest } = directives;\n return rest;\n}\n\n/** The policy an app ships: the default for this shell's backend plus `web.csp`. */\nexport function appCsp(config: ParlorConfig, input: DefaultCspInput): CspDirectives {\n return extendCsp(defaultCsp(input), config.web.csp ?? {});\n}\n\nfunction unique(sources: readonly string[]): string[] {\n return [...new Set(sources)];\n}\n"]}
package/dist/dev.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { LoadedConfig } from "@parlor/config";
2
+ import { type ViteDevServer } from "vite";
3
+ import type { WebOptions } from "./plugin.js";
4
+ export declare function startWebDev(loaded: LoadedConfig, options?: WebOptions): Promise<ViteDevServer>;
5
+ //# sourceMappingURL=dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,MAAM,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,wBAAsB,WAAW,CAC/B,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,aAAa,CAAC,CAKxB"}
package/dist/dev.js ADDED
@@ -0,0 +1,9 @@
1
+ import { createServer } from "vite";
2
+ import { parlorViteConfig } from "./vite-config.js";
3
+ export async function startWebDev(loaded, options = {}) {
4
+ const server = await createServer(parlorViteConfig(loaded, options, {}));
5
+ await server.listen();
6
+ server.printUrls();
7
+ return server;
8
+ }
9
+ //# sourceMappingURL=dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAsB,MAAM,MAAM,CAAC;AAGxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAoB,EACpB,OAAO,GAAe,EAAE;IAExB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACzE,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,SAAS,EAAE,CAAC;IACnB,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { LoadedConfig } from \"@parlor/config\";\nimport { createServer, type ViteDevServer } from \"vite\";\n\nimport type { WebOptions } from \"./plugin.js\";\nimport { parlorViteConfig } from \"./vite-config.js\";\n\nexport async function startWebDev(\n loaded: LoadedConfig,\n options: WebOptions = {},\n): Promise<ViteDevServer> {\n const server = await createServer(parlorViteConfig(loaded, options, {}));\n await server.listen();\n server.printUrls();\n return server;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export { buildRender, buildWeb, type RenderBuildOptions, ssrOutDir, webOutDir, } from "./build.js";
2
+ export { appCsp, type CspDirective, type CspDirectives, defaultCsp, type DefaultCspInput, extendCsp, metaCsp, serializeCsp, } from "./csp.js";
3
+ export { startWebDev } from "./dev.js";
4
+ export { generateRouteTree, routeGeneratorOptions, routesDirectory, routeTreeFile, } from "./route-tree.js";
5
+ export { DEFAULT_ROUTE_TREE, FRAMEWORK_ROUTE_PREFIX, parlorWeb, webManifest, type WebOptions, } from "./plugin.js";
6
+ export { RENDER_MODULE } from "./ssr.js";
7
+ export { FRAMEWORK_VITE_KEYS, mergeViteConfig, type MergedViteConfig, parlorViteConfig, strayViteConfig, } from "./vite-config.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,QAAQ,EACR,KAAK,kBAAkB,EACvB,SAAS,EACT,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,UAAU,EACV,KAAK,eAAe,EACpB,SAAS,EACT,OAAO,EACP,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,SAAS,EACT,WAAW,EACX,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { buildRender, buildWeb, ssrOutDir, webOutDir, } from "./build.js";
2
+ export { appCsp, defaultCsp, extendCsp, metaCsp, serializeCsp, } from "./csp.js";
3
+ export { startWebDev } from "./dev.js";
4
+ export { generateRouteTree, routeGeneratorOptions, routesDirectory, routeTreeFile, } from "./route-tree.js";
5
+ export { DEFAULT_ROUTE_TREE, FRAMEWORK_ROUTE_PREFIX, parlorWeb, webManifest, } from "./plugin.js";
6
+ export { RENDER_MODULE } from "./ssr.js";
7
+ export { FRAMEWORK_VITE_KEYS, mergeViteConfig, parlorViteConfig, strayViteConfig, } from "./vite-config.js";
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,QAAQ,EAER,SAAS,EACT,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,MAAM,EAGN,UAAU,EAEV,SAAS,EACT,OAAO,EACP,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,SAAS,EACT,WAAW,GAEZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACL,mBAAmB,EACnB,eAAe,EAEf,gBAAgB,EAChB,eAAe,GAChB,MAAM,kBAAkB,CAAC","sourcesContent":["export {\n buildRender,\n buildWeb,\n type RenderBuildOptions,\n ssrOutDir,\n webOutDir,\n} from \"./build.js\";\nexport {\n appCsp,\n type CspDirective,\n type CspDirectives,\n defaultCsp,\n type DefaultCspInput,\n extendCsp,\n metaCsp,\n serializeCsp,\n} from \"./csp.js\";\nexport { startWebDev } from \"./dev.js\";\nexport {\n generateRouteTree,\n routeGeneratorOptions,\n routesDirectory,\n routeTreeFile,\n} from \"./route-tree.js\";\nexport {\n DEFAULT_ROUTE_TREE,\n FRAMEWORK_ROUTE_PREFIX,\n parlorWeb,\n webManifest,\n type WebOptions,\n} from \"./plugin.js\";\nexport { RENDER_MODULE } from \"./ssr.js\";\nexport {\n FRAMEWORK_VITE_KEYS,\n mergeViteConfig,\n type MergedViteConfig,\n parlorViteConfig,\n strayViteConfig,\n} from \"./vite-config.js\";\n"]}
@@ -0,0 +1,30 @@
1
+ import { DEFAULT_ROUTE_TREE } from "@parlor/agent-ui/vite";
2
+ import { type ParlorConfig } from "@parlor/config";
3
+ import { type PluginOption } from "vite";
4
+ export declare const FRAMEWORK_ROUTE_PREFIX = "/_parlor";
5
+ /** OAuth discovery lives at the origin root (RFC 8414, RFC 9728), outside the framework prefix. */
6
+ export declare const OAUTH_DISCOVERY_PREFIXES: readonly ["/.well-known/oauth-protected-resource", "/.well-known/oauth-authorization-server"];
7
+ export { DEFAULT_ROUTE_TREE };
8
+ export interface WebOptions {
9
+ readonly conditions?: readonly string[] | undefined;
10
+ readonly serverUrl?: string | undefined;
11
+ /** The origin every target of one `parlor dev` signs for and pairs with, when it is not this page's. */
12
+ readonly appOrigin?: string | undefined;
13
+ readonly root?: string | undefined;
14
+ /** Writes the policy into the built `index.html` for shells that serve it without HTTP headers. */
15
+ readonly cspMeta?: boolean | undefined;
16
+ /** Builds the server render module (`web.ssr`) instead of the browser app. */
17
+ readonly render?: boolean | undefined;
18
+ }
19
+ export declare function webManifest(config: ParlorConfig): {
20
+ id: string;
21
+ name: string;
22
+ short_name: string;
23
+ description?: string;
24
+ start_url: string;
25
+ display: "standalone";
26
+ theme_color: string;
27
+ background_color: string;
28
+ };
29
+ export declare function parlorWeb(config: ParlorConfig, options?: WebOptions): PluginOption[];
30
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGhF,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAQlE,eAAO,MAAM,sBAAsB,aAAa,CAAC;AACjD,mGAAmG;AACnG,eAAO,MAAM,wBAAwB,YACnC,uCAAuC,EACvC,yCAAyC,CACjC,CAAC;AACX,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,wGAAwG;IACxG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,mGAAmG;IACnG,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACvC;AAoCD,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY;;;;;;aAOnC,YAAY;;;EAIxB;AAmBD,wBAAgB,SAAS,CACvB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,UAAe,GACvB,YAAY,EAAE,CAsFhB"}
package/dist/plugin.js ADDED
@@ -0,0 +1,160 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { pagePattern } from "@parlor/agent-ui";
4
+ import { agentManifest, DEFAULT_ROUTE_TREE } from "@parlor/agent-ui/vite";
5
+ import { GENERATED_DIR, generatedDir } from "@parlor/config";
6
+ import { tanstackRouter } from "@tanstack/router-plugin/vite";
7
+ import react from "@vitejs/plugin-react";
8
+ import { defaultClientConditions } from "vite";
9
+ import { VitePWA } from "vite-plugin-pwa";
10
+ import { appCsp, metaCsp, serializeCsp } from "./csp.js";
11
+ import { routeGeneratorOptions, routesDirectory, routeTreeFile } from "./route-tree.js";
12
+ import { pageMatcher, serverRoutes } from "./server-routes.js";
13
+ import { renderModule } from "./ssr.js";
14
+ export const FRAMEWORK_ROUTE_PREFIX = "/_parlor";
15
+ /** OAuth discovery lives at the origin root (RFC 8414, RFC 9728), outside the framework prefix. */
16
+ export const OAUTH_DISCOVERY_PREFIXES = [
17
+ "/.well-known/oauth-protected-resource",
18
+ "/.well-known/oauth-authorization-server",
19
+ ];
20
+ export { DEFAULT_ROUTE_TREE };
21
+ function isFrameworkPath(url) {
22
+ const path = (url ?? "").split("?")[0] ?? "";
23
+ return [FRAMEWORK_ROUTE_PREFIX, ...OAUTH_DISCOVERY_PREFIXES].some((prefix) => path === prefix || path.startsWith(`${prefix}/`));
24
+ }
25
+ function cspPlugin(config, options) {
26
+ const serverOrigin = options.serverUrl || undefined;
27
+ return {
28
+ name: "parlor:csp",
29
+ configureServer(server) {
30
+ const header = serializeCsp(appCsp(config, { serverOrigin, dev: true }));
31
+ server.middlewares.use((request, response, next) => {
32
+ if (!isFrameworkPath(request.url)) {
33
+ response.setHeader("content-security-policy", header);
34
+ }
35
+ next();
36
+ });
37
+ },
38
+ transformIndexHtml(_html, context) {
39
+ if (context.server || !options.cspMeta)
40
+ return;
41
+ const content = serializeCsp(metaCsp(appCsp(config, { serverOrigin })));
42
+ return [
43
+ {
44
+ tag: "meta",
45
+ attrs: { "http-equiv": "Content-Security-Policy", content },
46
+ injectTo: "head-prepend",
47
+ },
48
+ ];
49
+ },
50
+ };
51
+ }
52
+ export function webManifest(config) {
53
+ return {
54
+ id: config.app.id,
55
+ name: config.app.name,
56
+ short_name: config.app.name,
57
+ ...(config.app.description ? { description: config.app.description } : {}),
58
+ start_url: "/",
59
+ display: "standalone",
60
+ theme_color: config.web.themeColor,
61
+ background_color: config.web.backgroundColor,
62
+ };
63
+ }
64
+ function fileRoutes(config, root) {
65
+ if (!existsSync(resolve(root, routesDirectory(config))))
66
+ return [];
67
+ return [
68
+ tanstackRouter({ ...routeGeneratorOptions(config, root), autoCodeSplitting: true }),
69
+ ];
70
+ }
71
+ function titlePlugin(config) {
72
+ return {
73
+ name: "parlor:title",
74
+ transformIndexHtml(html) {
75
+ if (/<title>/i.test(html))
76
+ return;
77
+ return [{ tag: "title", children: config.app.name, injectTo: "head" }];
78
+ },
79
+ };
80
+ }
81
+ export function parlorWeb(config, options = {}) {
82
+ const conditions = options.conditions ?? [];
83
+ const root = options.root ?? process.cwd();
84
+ const generated = generatedDir(root);
85
+ const ownGeneratedDir = generated !== resolve(root, GENERATED_DIR);
86
+ const manifest = agentManifest({
87
+ app: {
88
+ id: config.app.id,
89
+ name: config.app.name,
90
+ ...(config.app.description ? { description: config.app.description } : {}),
91
+ ...(config.app.scheme ? { scheme: config.app.scheme } : {}),
92
+ },
93
+ routeTree: routeTreeFile(config),
94
+ generatedDir: generated,
95
+ });
96
+ const plugins = [
97
+ ...fileRoutes(config, root),
98
+ react(),
99
+ cspPlugin(config, options),
100
+ titlePlugin(config),
101
+ manifest,
102
+ serverRoutes(config.server.port, pageMatcher(() => manifest.api?.manifest()), {
103
+ // A native shell's platform conditions: it runs the static build, never a server render.
104
+ render: config.web.ssr !== undefined && conditions.length === 0,
105
+ }),
106
+ {
107
+ name: "parlor:web",
108
+ config: () => ({
109
+ define: {
110
+ "import.meta.env.PARLOR_SERVER_URL": JSON.stringify(options.serverUrl ?? ""),
111
+ "import.meta.env.PARLOR_APP_ORIGIN": JSON.stringify(options.appOrigin ?? ""),
112
+ "import.meta.env.PARLOR_APP_SCHEME": JSON.stringify(config.app.scheme ?? ""),
113
+ },
114
+ resolve: { conditions: [...defaultClientConditions, ...conditions] },
115
+ // A run with its own generated directory may run beside another Vite on this root.
116
+ ...(ownGeneratedDir ? { cacheDir: join(generated, "vite") } : {}),
117
+ server: {
118
+ port: config.web.port,
119
+ strictPort: true,
120
+ watch: { ignored: [`**/${GENERATED_DIR}/**`, join(generated, "**")] },
121
+ proxy: Object.fromEntries([FRAMEWORK_ROUTE_PREFIX, ...OAUTH_DISCOVERY_PREFIXES].map((prefix) => [
122
+ prefix,
123
+ `http://localhost:${config.server.port}`,
124
+ ])),
125
+ },
126
+ }),
127
+ },
128
+ ];
129
+ if (config.web.ssr)
130
+ plugins.push(renderModule(config, root));
131
+ if (config.web.pwa && !options.render) {
132
+ plugins.push(VitePWA({
133
+ registerType: "autoUpdate",
134
+ manifest: webManifest(config),
135
+ workbox: {
136
+ navigateFallbackDenylist: [
137
+ FRAMEWORK_ROUTE_PREFIX,
138
+ ...OAUTH_DISCOVERY_PREFIXES,
139
+ ].map((prefix) => new RegExp(`^${prefix.replaceAll(".", "\\.")}(/|$)`)),
140
+ // A cached shell would stand in for the pages the server renders.
141
+ ...(config.web.ssr
142
+ ? {
143
+ navigateFallback: null,
144
+ globIgnores: ["**/node_modules/**/*", "index.html"],
145
+ }
146
+ : {}),
147
+ },
148
+ integration: {
149
+ beforeBuildServiceWorker: (pwa) => {
150
+ const pages = manifest.api?.manifest();
151
+ if (!pages || config.web.ssr)
152
+ return;
153
+ pwa.workbox.navigateFallbackAllowlist = pages.routes.map((route) => pagePattern(route.path));
154
+ },
155
+ },
156
+ }));
157
+ }
158
+ return plugins;
159
+ }
160
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\nimport { pagePattern } from \"@parlor/agent-ui\";\nimport { agentManifest, DEFAULT_ROUTE_TREE } from \"@parlor/agent-ui/vite\";\nimport { GENERATED_DIR, generatedDir, type ParlorConfig } from \"@parlor/config\";\nimport { tanstackRouter } from \"@tanstack/router-plugin/vite\";\nimport react from \"@vitejs/plugin-react\";\nimport { defaultClientConditions, type PluginOption } from \"vite\";\nimport { VitePWA } from \"vite-plugin-pwa\";\n\nimport { appCsp, metaCsp, serializeCsp } from \"./csp.js\";\nimport { routeGeneratorOptions, routesDirectory, routeTreeFile } from \"./route-tree.js\";\nimport { pageMatcher, serverRoutes } from \"./server-routes.js\";\nimport { renderModule } from \"./ssr.js\";\n\nexport const FRAMEWORK_ROUTE_PREFIX = \"/_parlor\";\n/** OAuth discovery lives at the origin root (RFC 8414, RFC 9728), outside the framework prefix. */\nexport const OAUTH_DISCOVERY_PREFIXES = [\n \"/.well-known/oauth-protected-resource\",\n \"/.well-known/oauth-authorization-server\",\n] as const;\nexport { DEFAULT_ROUTE_TREE };\n\nexport interface WebOptions {\n readonly conditions?: readonly string[] | undefined;\n readonly serverUrl?: string | undefined;\n /** The origin every target of one `parlor dev` signs for and pairs with, when it is not this page's. */\n readonly appOrigin?: string | undefined;\n readonly root?: string | undefined;\n /** Writes the policy into the built `index.html` for shells that serve it without HTTP headers. */\n readonly cspMeta?: boolean | undefined;\n /** Builds the server render module (`web.ssr`) instead of the browser app. */\n readonly render?: boolean | undefined;\n}\n\nfunction isFrameworkPath(url: string | undefined): boolean {\n const path = (url ?? \"\").split(\"?\")[0] ?? \"\";\n return [FRAMEWORK_ROUTE_PREFIX, ...OAUTH_DISCOVERY_PREFIXES].some(\n (prefix) => path === prefix || path.startsWith(`${prefix}/`),\n );\n}\n\nfunction cspPlugin(config: ParlorConfig, options: WebOptions): PluginOption {\n const serverOrigin = options.serverUrl || undefined;\n return {\n name: \"parlor:csp\",\n configureServer(server) {\n const header = serializeCsp(appCsp(config, { serverOrigin, dev: true }));\n server.middlewares.use((request, response, next) => {\n if (!isFrameworkPath(request.url)) {\n response.setHeader(\"content-security-policy\", header);\n }\n next();\n });\n },\n transformIndexHtml(_html, context) {\n if (context.server || !options.cspMeta) return;\n const content = serializeCsp(metaCsp(appCsp(config, { serverOrigin })));\n return [\n {\n tag: \"meta\",\n attrs: { \"http-equiv\": \"Content-Security-Policy\", content },\n injectTo: \"head-prepend\",\n },\n ];\n },\n };\n}\n\nexport function webManifest(config: ParlorConfig) {\n return {\n id: config.app.id,\n name: config.app.name,\n short_name: config.app.name,\n ...(config.app.description ? { description: config.app.description } : {}),\n start_url: \"/\",\n display: \"standalone\" as const,\n theme_color: config.web.themeColor,\n background_color: config.web.backgroundColor,\n };\n}\n\nfunction fileRoutes(config: ParlorConfig, root: string): PluginOption[] {\n if (!existsSync(resolve(root, routesDirectory(config)))) return [];\n return [\n tanstackRouter({ ...routeGeneratorOptions(config, root), autoCodeSplitting: true }),\n ];\n}\n\nfunction titlePlugin(config: ParlorConfig): PluginOption {\n return {\n name: \"parlor:title\",\n transformIndexHtml(html) {\n if (/<title>/i.test(html)) return;\n return [{ tag: \"title\", children: config.app.name, injectTo: \"head\" }];\n },\n };\n}\n\nexport function parlorWeb(\n config: ParlorConfig,\n options: WebOptions = {},\n): PluginOption[] {\n const conditions = options.conditions ?? [];\n const root = options.root ?? process.cwd();\n const generated = generatedDir(root);\n const ownGeneratedDir = generated !== resolve(root, GENERATED_DIR);\n const manifest = agentManifest({\n app: {\n id: config.app.id,\n name: config.app.name,\n ...(config.app.description ? { description: config.app.description } : {}),\n ...(config.app.scheme ? { scheme: config.app.scheme } : {}),\n },\n routeTree: routeTreeFile(config),\n generatedDir: generated,\n });\n const plugins: PluginOption[] = [\n ...fileRoutes(config, root),\n react(),\n cspPlugin(config, options),\n titlePlugin(config),\n manifest,\n serverRoutes(\n config.server.port,\n pageMatcher(() => manifest.api?.manifest()),\n {\n // A native shell's platform conditions: it runs the static build, never a server render.\n render: config.web.ssr !== undefined && conditions.length === 0,\n },\n ),\n {\n name: \"parlor:web\",\n config: () => ({\n define: {\n \"import.meta.env.PARLOR_SERVER_URL\": JSON.stringify(options.serverUrl ?? \"\"),\n \"import.meta.env.PARLOR_APP_ORIGIN\": JSON.stringify(options.appOrigin ?? \"\"),\n \"import.meta.env.PARLOR_APP_SCHEME\": JSON.stringify(config.app.scheme ?? \"\"),\n },\n resolve: { conditions: [...defaultClientConditions, ...conditions] },\n // A run with its own generated directory may run beside another Vite on this root.\n ...(ownGeneratedDir ? { cacheDir: join(generated, \"vite\") } : {}),\n server: {\n port: config.web.port,\n strictPort: true,\n watch: { ignored: [`**/${GENERATED_DIR}/**`, join(generated, \"**\")] },\n proxy: Object.fromEntries(\n [FRAMEWORK_ROUTE_PREFIX, ...OAUTH_DISCOVERY_PREFIXES].map((prefix) => [\n prefix,\n `http://localhost:${config.server.port}`,\n ]),\n ),\n },\n }),\n },\n ];\n if (config.web.ssr) plugins.push(renderModule(config, root));\n if (config.web.pwa && !options.render) {\n plugins.push(\n VitePWA({\n registerType: \"autoUpdate\",\n manifest: webManifest(config),\n workbox: {\n navigateFallbackDenylist: [\n FRAMEWORK_ROUTE_PREFIX,\n ...OAUTH_DISCOVERY_PREFIXES,\n ].map((prefix) => new RegExp(`^${prefix.replaceAll(\".\", \"\\\\.\")}(/|$)`)),\n // A cached shell would stand in for the pages the server renders.\n ...(config.web.ssr\n ? {\n navigateFallback: null,\n globIgnores: [\"**/node_modules/**/*\", \"index.html\"],\n }\n : {}),\n },\n integration: {\n beforeBuildServiceWorker: (pwa) => {\n const pages = manifest.api?.manifest();\n if (!pages || config.web.ssr) return;\n pwa.workbox.navigateFallbackAllowlist = pages.routes.map((route) =>\n pagePattern(route.path),\n );\n },\n },\n }),\n );\n }\n return plugins;\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { type LoadedConfig, type ParlorConfig } from "@parlor/config";
2
+ export declare function routeTreeFile(config: ParlorConfig): string;
3
+ export declare function routesDirectory(config: ParlorConfig): string;
4
+ /** Shared by the Vite plugin and `parlor typegen`, so both write the same file byte for byte. */
5
+ export declare function routeGeneratorOptions(config: ParlorConfig, root: string): {
6
+ readonly target: "react";
7
+ readonly routesDirectory: string;
8
+ readonly generatedRouteTree: string;
9
+ readonly tmpDir: string;
10
+ readonly quoteStyle: "double";
11
+ readonly semicolons: true;
12
+ };
13
+ /** The generator `@tanstack/router-plugin` itself depends on, so typegen and dev run one version. */
14
+ export declare function routerGeneratorPath(): string;
15
+ export declare function generateRouteTree({ config, rootDir, }: LoadedConfig): Promise<void>;
16
+ //# sourceMappingURL=route-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-tree.d.ts","sourceRoot":"","sources":["../src/route-tree.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKpF,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAE1D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAE5D;AAED,iGAAiG;AACjG,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM;qBAE5D,OAAO;;;;yBAIH,QAAQ;;EAGvB;AAED,qGAAqG;AACrG,wBAAgB,mBAAmB,IAAI,MAAM,CAU5C;AAED,wBAAsB,iBAAiB,CAAC,EACtC,MAAM,EACN,OAAO,GACR,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9B"}
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { DEFAULT_ROUTE_TREE } from "@parlor/agent-ui/vite";
6
+ import { generatedDir } from "@parlor/config";
7
+ export function routeTreeFile(config) {
8
+ return config.web.routeTree ?? DEFAULT_ROUTE_TREE;
9
+ }
10
+ export function routesDirectory(config) {
11
+ return join(dirname(routeTreeFile(config)), "routes");
12
+ }
13
+ /** Shared by the Vite plugin and `parlor typegen`, so both write the same file byte for byte. */
14
+ export function routeGeneratorOptions(config, root) {
15
+ return {
16
+ target: "react",
17
+ routesDirectory: routesDirectory(config),
18
+ generatedRouteTree: routeTreeFile(config),
19
+ tmpDir: join(generatedDir(root), "tanstack"),
20
+ quoteStyle: "double",
21
+ semicolons: true,
22
+ };
23
+ }
24
+ /** The generator `@tanstack/router-plugin` itself depends on, so typegen and dev run one version. */
25
+ export function routerGeneratorPath() {
26
+ const fromHere = createRequire(import.meta.url);
27
+ const fromPlugin = createRequire(fromHere.resolve("@tanstack/router-plugin/package.json"));
28
+ const manifestPath = fromPlugin.resolve("@tanstack/router-generator/package.json");
29
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
30
+ return join(dirname(manifestPath), manifest.exports["."].import.default);
31
+ }
32
+ export async function generateRouteTree({ config, rootDir, }) {
33
+ const { Generator, getConfig } = (await import(pathToFileURL(routerGeneratorPath()).href));
34
+ const generator = new Generator({
35
+ config: getConfig(routeGeneratorOptions(config, rootDir), rootDir),
36
+ root: rootDir,
37
+ });
38
+ await generator.run();
39
+ }
40
+ //# sourceMappingURL=route-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-tree.js","sourceRoot":"","sources":["../src/route-tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAwC,MAAM,gBAAgB,CAAC;AAKpF,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,OAAO,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,kBAAkB,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAoB;IAClD,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxD,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,qBAAqB,CAAC,MAAoB,EAAE,IAAY;IACtE,OAAO;QACL,MAAM,EAAE,OAAO;QACf,eAAe,EAAE,eAAe,CAAC,MAAM,CAAC;QACxC,kBAAkB,EAAE,aAAa,CAAC,MAAM,CAAC;QACzC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;QAC5C,UAAU,EAAE,QAAQ;QACpB,UAAU,EAAE,IAAI;KACkB,CAAC;AACvC,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,mBAAmB;IACjC,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,aAAa,CAC9B,QAAQ,CAAC,OAAO,CAAC,sCAAsC,CAAC,CACzD,CAAC;IACF,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC;IACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAE7D,CAAC;IACF,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EACtC,MAAM,EACN,OAAO,GACM;IACb,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,MAAM,CAC5C,aAAa,CAAC,mBAAmB,EAAE,CAAC,CAAC,IAAI,CAC1C,CAAoB,CAAC;IACtB,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC;QAC9B,MAAM,EAAE,SAAS,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAClE,IAAI,EAAE,OAAO;KACd,CAAC,CAAC;IACH,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC;AACxB,CAAC","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { DEFAULT_ROUTE_TREE } from \"@parlor/agent-ui/vite\";\nimport { generatedDir, type LoadedConfig, type ParlorConfig } from \"@parlor/config\";\nimport type { Config } from \"@tanstack/router-generator\";\n\ntype RouterGenerator = typeof import(\"@tanstack/router-generator\");\n\nexport function routeTreeFile(config: ParlorConfig): string {\n return config.web.routeTree ?? DEFAULT_ROUTE_TREE;\n}\n\nexport function routesDirectory(config: ParlorConfig): string {\n return join(dirname(routeTreeFile(config)), \"routes\");\n}\n\n/** Shared by the Vite plugin and `parlor typegen`, so both write the same file byte for byte. */\nexport function routeGeneratorOptions(config: ParlorConfig, root: string) {\n return {\n target: \"react\",\n routesDirectory: routesDirectory(config),\n generatedRouteTree: routeTreeFile(config),\n tmpDir: join(generatedDir(root), \"tanstack\"),\n quoteStyle: \"double\",\n semicolons: true,\n } as const satisfies Partial<Config>;\n}\n\n/** The generator `@tanstack/router-plugin` itself depends on, so typegen and dev run one version. */\nexport function routerGeneratorPath(): string {\n const fromHere = createRequire(import.meta.url);\n const fromPlugin = createRequire(\n fromHere.resolve(\"@tanstack/router-plugin/package.json\"),\n );\n const manifestPath = fromPlugin.resolve(\"@tanstack/router-generator/package.json\");\n const manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as {\n readonly exports: { readonly \".\": { readonly import: { readonly default: string } } };\n };\n return join(dirname(manifestPath), manifest.exports[\".\"].import.default);\n}\n\nexport async function generateRouteTree({\n config,\n rootDir,\n}: LoadedConfig): Promise<void> {\n const { Generator, getConfig } = (await import(\n pathToFileURL(routerGeneratorPath()).href\n )) as RouterGenerator;\n const generator = new Generator({\n config: getConfig(routeGeneratorOptions(config, rootDir), rootDir),\n root: rootDir,\n });\n await generator.run();\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { type UiManifest } from "@parlor/agent-ui";
2
+ import type { Plugin } from "vite";
3
+ export type PageMatcher = (path: string) => boolean;
4
+ export declare function pageMatcher(manifest: () => UiManifest | undefined): PageMatcher;
5
+ export interface ServerRoutesOptions {
6
+ /** Renders a page route's document on the server first, as a build with `web.ssr` does. */
7
+ readonly render?: boolean | undefined;
8
+ }
9
+ /**
10
+ * Vite answers what it has a file or module for. Every other request goes to the app's
11
+ * server, as in a build, except a browser's request for a page route's document.
12
+ */
13
+ export declare function serverRoutes(port: number, isPage: PageMatcher, options?: ServerRoutesOptions): Plugin;
14
+ //# sourceMappingURL=server-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-routes.d.ts","sourceRoot":"","sources":["../src/server-routes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,KAAK,EAAW,MAAM,EAAE,MAAM,MAAM,CAAC;AAI5C,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;AAEpD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW,CAW/E;AA8BD,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,OAAO,GAAE,mBAAwB,GAChC,MAAM,CAyCR"}
@@ -0,0 +1,81 @@
1
+ import { request } from "node:http";
2
+ import { pagePattern, UNROUTED_HEADER } from "@parlor/agent-ui";
3
+ import { renderInDev, sendResponse } from "./ssr.js";
4
+ export function pageMatcher(manifest) {
5
+ let seen;
6
+ let patterns = [];
7
+ return (path) => {
8
+ const current = manifest();
9
+ if (current !== seen) {
10
+ seen = current;
11
+ patterns = current?.routes.map((route) => pagePattern(route.path)) ?? [];
12
+ }
13
+ return patterns.some((pattern) => pattern.test(path));
14
+ };
15
+ }
16
+ function forward(req, res, port, url, unrouted) {
17
+ const upstream = request({ host: "localhost", port, method: req.method, path: url, headers: req.headers }, (response) => {
18
+ if (unrouted && response.statusCode === 404 && response.headers[UNROUTED_HEADER]) {
19
+ response.resume();
20
+ unrouted();
21
+ return;
22
+ }
23
+ res.removeHeader("content-security-policy");
24
+ res.writeHead(response.statusCode ?? 502, response.headers);
25
+ response.pipe(res);
26
+ });
27
+ upstream.on("error", () => {
28
+ if (res.headersSent)
29
+ res.destroy();
30
+ else if (unrouted)
31
+ unrouted();
32
+ else
33
+ res.writeHead(502).end();
34
+ });
35
+ req.pipe(upstream);
36
+ }
37
+ /**
38
+ * Vite answers what it has a file or module for. Every other request goes to the app's
39
+ * server, as in a build, except a browser's request for a page route's document.
40
+ */
41
+ export function serverRoutes(port, isPage, options = {}) {
42
+ return {
43
+ name: "parlor:server-routes",
44
+ apply: "serve",
45
+ configureServer: (server) => () => {
46
+ const renderPage = async (req, res, url, fallback) => {
47
+ try {
48
+ const page = await renderInDev(server, port, url, req.headers);
49
+ if (page)
50
+ return await sendResponse(page, res);
51
+ }
52
+ catch (error) {
53
+ if (error instanceof Error)
54
+ server.ssrFixStacktrace(error);
55
+ server.config.logger.error(`[parlor] Rendering ${url} on the server failed, so it is served without: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
56
+ if (res.headersSent)
57
+ return void res.end();
58
+ }
59
+ fallback();
60
+ };
61
+ server.middlewares.use((req, res, next) => {
62
+ const url = req.originalUrl ?? req.url ?? "/";
63
+ const path = url.split("?")[0] ?? "/";
64
+ if (path.endsWith(".html"))
65
+ return next();
66
+ const document = req.method === "GET" && (req.headers.accept ?? "").includes("text/html");
67
+ const serveDocument = () => {
68
+ req.url = "/index.html";
69
+ next();
70
+ };
71
+ if (document && isPage(path)) {
72
+ if (options.render)
73
+ return void renderPage(req, res, url, serveDocument);
74
+ return serveDocument();
75
+ }
76
+ forward(req, res, port, url, document ? serveDocument : undefined);
77
+ });
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=server-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-routes.js","sourceRoot":"","sources":["../src/server-routes.ts"],"names":[],"mappings":"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","sourcesContent":["import { request, type ServerResponse } from \"node:http\";\n\nimport { pagePattern, UNROUTED_HEADER, type UiManifest } from \"@parlor/agent-ui\";\nimport type { Connect, Plugin } from \"vite\";\n\nimport { renderInDev, sendResponse } from \"./ssr.js\";\n\nexport type PageMatcher = (path: string) => boolean;\n\nexport function pageMatcher(manifest: () => UiManifest | undefined): PageMatcher {\n let seen: UiManifest | undefined;\n let patterns: RegExp[] = [];\n return (path) => {\n const current = manifest();\n if (current !== seen) {\n seen = current;\n patterns = current?.routes.map((route) => pagePattern(route.path)) ?? [];\n }\n return patterns.some((pattern) => pattern.test(path));\n };\n}\n\nfunction forward(\n req: Connect.IncomingMessage,\n res: ServerResponse,\n port: number,\n url: string,\n unrouted: (() => void) | undefined,\n): void {\n const upstream = request(\n { host: \"localhost\", port, method: req.method, path: url, headers: req.headers },\n (response) => {\n if (unrouted && response.statusCode === 404 && response.headers[UNROUTED_HEADER]) {\n response.resume();\n unrouted();\n return;\n }\n res.removeHeader(\"content-security-policy\");\n res.writeHead(response.statusCode ?? 502, response.headers);\n response.pipe(res);\n },\n );\n upstream.on(\"error\", () => {\n if (res.headersSent) res.destroy();\n else if (unrouted) unrouted();\n else res.writeHead(502).end();\n });\n req.pipe(upstream);\n}\n\nexport interface ServerRoutesOptions {\n /** Renders a page route's document on the server first, as a build with `web.ssr` does. */\n readonly render?: boolean | undefined;\n}\n\n/**\n * Vite answers what it has a file or module for. Every other request goes to the app's\n * server, as in a build, except a browser's request for a page route's document.\n */\nexport function serverRoutes(\n port: number,\n isPage: PageMatcher,\n options: ServerRoutesOptions = {},\n): Plugin {\n return {\n name: \"parlor:server-routes\",\n apply: \"serve\",\n configureServer: (server) => () => {\n const renderPage = async (\n req: Connect.IncomingMessage,\n res: ServerResponse,\n url: string,\n fallback: () => void,\n ) => {\n try {\n const page = await renderInDev(server, port, url, req.headers);\n if (page) return await sendResponse(page, res);\n } catch (error) {\n if (error instanceof Error) server.ssrFixStacktrace(error);\n server.config.logger.error(\n `[parlor] Rendering ${url} on the server failed, so it is served without: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`,\n );\n if (res.headersSent) return void res.end();\n }\n fallback();\n };\n server.middlewares.use((req, res, next) => {\n const url = req.originalUrl ?? req.url ?? \"/\";\n const path = url.split(\"?\")[0] ?? \"/\";\n if (path.endsWith(\".html\")) return next();\n const document =\n req.method === \"GET\" && (req.headers.accept ?? \"\").includes(\"text/html\");\n const serveDocument = () => {\n req.url = \"/index.html\";\n next();\n };\n if (document && isPage(path)) {\n if (options.render) return void renderPage(req, res, url, serveDocument);\n return serveDocument();\n }\n forward(req, res, port, url, document ? serveDocument : undefined);\n });\n },\n };\n}\n"]}
package/dist/ssr.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { IncomingHttpHeaders, ServerResponse } from "node:http";
2
+ import type { ParlorConfig } from "@parlor/config";
3
+ import { type Plugin, type ViteDevServer } from "vite";
4
+ /** Exports `render`, the app's server renderer from `@parlor/client/ssr`. */
5
+ export declare const RENDER_MODULE = "virtual:parlor/render";
6
+ export declare const RENDER_FILE = "render.mjs";
7
+ export declare function renderModule(config: ParlorConfig, root: string): Plugin;
8
+ export declare function createNonce(): string;
9
+ /**
10
+ * What `parlor dev` serves for a page route's document: the page rendered as the build would,
11
+ * or undefined to serve `index.html` when it does not render on the server.
12
+ */
13
+ export declare function renderInDev(server: ViteDevServer, serverPort: number, url: string, headers: IncomingHttpHeaders): Promise<Response | undefined>;
14
+ export declare function sendResponse(response: Response, res: ServerResponse): Promise<void>;
15
+ //# sourceMappingURL=ssr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAA4B,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,MAAM,CAAC;AAEjF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,0BAA0B,CAAC;AAErD,eAAO,MAAM,WAAW,eAAe,CAAC;AAgBxC,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAcvE;AAED,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAWD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAuB/B;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CAQf"}
package/dist/ssr.js ADDED
@@ -0,0 +1,78 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { readFile } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
4
+ import { isRunnableDevEnvironment } from "vite";
5
+ /** Exports `render`, the app's server renderer from `@parlor/client/ssr`. */
6
+ export const RENDER_MODULE = "virtual:parlor/render";
7
+ const RESOLVED_RENDER_MODULE = `\0${RENDER_MODULE}`;
8
+ export const RENDER_FILE = "render.mjs";
9
+ /** Parlor's packages read `import.meta.env`, which only Vite provides, so Node must not load them. */
10
+ const PARLOR_PACKAGES = /^@parlor\//;
11
+ export function renderModule(config, root) {
12
+ return {
13
+ name: "parlor:render-module",
14
+ config: () => ({ ssr: { noExternal: [PARLOR_PACKAGES] } }),
15
+ resolveId: (id) => (id === RENDER_MODULE ? RESOLVED_RENDER_MODULE : undefined),
16
+ load(id) {
17
+ if (id !== RESOLVED_RENDER_MODULE || !config.web.ssr)
18
+ return undefined;
19
+ return [
20
+ `import createApp from ${JSON.stringify(resolve(root, config.web.ssr.app))};`,
21
+ 'import { createRenderer } from "@parlor/client/ssr";',
22
+ "export const render = createRenderer(createApp);",
23
+ ].join("\n");
24
+ },
25
+ };
26
+ }
27
+ export function createNonce() {
28
+ return randomBytes(16).toString("base64");
29
+ }
30
+ function requestHeaders(headers) {
31
+ const result = new Headers();
32
+ for (const [name, value] of Object.entries(headers)) {
33
+ if (value === undefined || name.startsWith(":"))
34
+ continue;
35
+ for (const item of Array.isArray(value) ? value : [value])
36
+ result.append(name, item);
37
+ }
38
+ return result;
39
+ }
40
+ /**
41
+ * What `parlor dev` serves for a page route's document: the page rendered as the build would,
42
+ * or undefined to serve `index.html` when it does not render on the server.
43
+ */
44
+ export async function renderInDev(server, serverPort, url, headers) {
45
+ const environment = server.environments.ssr;
46
+ if (!environment || !isRunnableDevEnvironment(environment))
47
+ return undefined;
48
+ const html = await readFile(join(server.config.root, "index.html"), "utf8");
49
+ const template = await server.transformIndexHtml(url, html);
50
+ const { render } = (await environment.runner.import(RENDER_MODULE));
51
+ const request = new Request(new URL(url, `http://${headers.host ?? "localhost"}`), {
52
+ headers: requestHeaders(headers),
53
+ });
54
+ return render(request, {
55
+ template,
56
+ nonce: createNonce(),
57
+ fetch: async (action) => {
58
+ const { pathname, search } = new URL(action.url);
59
+ return fetch(`http://localhost:${serverPort}${pathname}${search}`, {
60
+ method: action.method,
61
+ headers: action.headers,
62
+ body: action.method === "GET" ? null : await action.arrayBuffer(),
63
+ });
64
+ },
65
+ });
66
+ }
67
+ export async function sendResponse(response, res) {
68
+ res.statusCode = response.status;
69
+ for (const [name, value] of response.headers) {
70
+ if (name !== "set-cookie")
71
+ res.setHeader(name, value);
72
+ }
73
+ const cookies = response.headers.getSetCookie();
74
+ if (cookies.length > 0)
75
+ res.setHeader("set-cookie", cookies);
76
+ res.end(Buffer.from(await response.arrayBuffer()));
77
+ }
78
+ //# sourceMappingURL=ssr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { readFile } from \"node:fs/promises\";\nimport type { IncomingHttpHeaders, ServerResponse } from \"node:http\";\nimport { join, resolve } from \"node:path\";\n\nimport type { ParlorConfig } from \"@parlor/config\";\nimport { isRunnableDevEnvironment, type Plugin, type ViteDevServer } from \"vite\";\n\n/** Exports `render`, the app's server renderer from `@parlor/client/ssr`. */\nexport const RENDER_MODULE = \"virtual:parlor/render\";\nconst RESOLVED_RENDER_MODULE = `\\0${RENDER_MODULE}`;\nexport const RENDER_FILE = \"render.mjs\";\n\n/** Parlor's packages read `import.meta.env`, which only Vite provides, so Node must not load them. */\nconst PARLOR_PACKAGES = /^@parlor\\//;\n\ninterface RenderOptions {\n readonly template: string;\n readonly fetch: (request: Request) => Promise<Response>;\n readonly nonce: string;\n}\n\ntype RenderPage = (\n request: Request,\n options: RenderOptions,\n) => Promise<Response | undefined>;\n\nexport function renderModule(config: ParlorConfig, root: string): Plugin {\n return {\n name: \"parlor:render-module\",\n config: () => ({ ssr: { noExternal: [PARLOR_PACKAGES] } }),\n resolveId: (id) => (id === RENDER_MODULE ? RESOLVED_RENDER_MODULE : undefined),\n load(id) {\n if (id !== RESOLVED_RENDER_MODULE || !config.web.ssr) return undefined;\n return [\n `import createApp from ${JSON.stringify(resolve(root, config.web.ssr.app))};`,\n 'import { createRenderer } from \"@parlor/client/ssr\";',\n \"export const render = createRenderer(createApp);\",\n ].join(\"\\n\");\n },\n };\n}\n\nexport function createNonce(): string {\n return randomBytes(16).toString(\"base64\");\n}\n\nfunction requestHeaders(headers: IncomingHttpHeaders): Headers {\n const result = new Headers();\n for (const [name, value] of Object.entries(headers)) {\n if (value === undefined || name.startsWith(\":\")) continue;\n for (const item of Array.isArray(value) ? value : [value]) result.append(name, item);\n }\n return result;\n}\n\n/**\n * What `parlor dev` serves for a page route's document: the page rendered as the build would,\n * or undefined to serve `index.html` when it does not render on the server.\n */\nexport async function renderInDev(\n server: ViteDevServer,\n serverPort: number,\n url: string,\n headers: IncomingHttpHeaders,\n): Promise<Response | undefined> {\n const environment = server.environments.ssr;\n if (!environment || !isRunnableDevEnvironment(environment)) return undefined;\n const html = await readFile(join(server.config.root, \"index.html\"), \"utf8\");\n const template = await server.transformIndexHtml(url, html);\n const { render } = (await environment.runner.import(RENDER_MODULE)) as {\n readonly render: RenderPage;\n };\n const request = new Request(new URL(url, `http://${headers.host ?? \"localhost\"}`), {\n headers: requestHeaders(headers),\n });\n return render(request, {\n template,\n nonce: createNonce(),\n fetch: async (action) => {\n const { pathname, search } = new URL(action.url);\n return fetch(`http://localhost:${serverPort}${pathname}${search}`, {\n method: action.method,\n headers: action.headers,\n body: action.method === \"GET\" ? null : await action.arrayBuffer(),\n });\n },\n });\n}\n\nexport async function sendResponse(\n response: Response,\n res: ServerResponse,\n): Promise<void> {\n res.statusCode = response.status;\n for (const [name, value] of response.headers) {\n if (name !== \"set-cookie\") res.setHeader(name, value);\n }\n const cookies = response.headers.getSetCookie();\n if (cookies.length > 0) res.setHeader(\"set-cookie\", cookies);\n res.end(Buffer.from(await response.arrayBuffer()));\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { LoadedConfig } from "@parlor/config";
2
+ import { type InlineConfig, type UserConfig } from "vite";
3
+ import { type WebOptions } from "./plugin.js";
4
+ export declare const FRAMEWORK_VITE_KEYS: readonly ["root", "base", "configFile", "build.outDir", "build.emptyOutDir", "server.port", "server.strictPort"];
5
+ export declare const STRAY_VITE_CONFIGS: readonly ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"];
6
+ export interface MergedViteConfig {
7
+ readonly config: InlineConfig;
8
+ readonly ignored: readonly string[];
9
+ }
10
+ /** The framework wins every scalar it sets, so the proxy, conditions and defines Parlor needs survive. */
11
+ export declare function mergeViteConfig(user: UserConfig | undefined, framework: InlineConfig): MergedViteConfig;
12
+ export declare function strayViteConfig(rootDir: string): string | undefined;
13
+ export declare function parlorViteConfig({ config, rootDir }: LoadedConfig, options: WebOptions, framework: InlineConfig): InlineConfig;
14
+ //# sourceMappingURL=vite-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-config.d.ts","sourceRoot":"","sources":["../src/vite-config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,YAAY,EAAe,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvE,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzD,eAAO,MAAM,mBAAmB,YAC9B,MAAM,EACN,MAAM,EACN,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,mBAAmB,CACX,CAAC;AAEX,eAAO,MAAM,kBAAkB,YAC7B,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,CACT,CAAC;AAEX,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,0GAA0G;AAC1G,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,SAAS,EAAE,YAAY,GACtB,gBAAgB,CAwBlB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnE;AAYD,wBAAgB,gBAAgB,CAC9B,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,YAAY,EACjC,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,YAAY,GACtB,YAAY,CAoBd"}
@@ -0,0 +1,77 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { mergeConfig } from "vite";
4
+ import { parlorWeb } from "./plugin.js";
5
+ export const FRAMEWORK_VITE_KEYS = [
6
+ "root",
7
+ "base",
8
+ "configFile",
9
+ "build.outDir",
10
+ "build.emptyOutDir",
11
+ "server.port",
12
+ "server.strictPort",
13
+ ];
14
+ export const STRAY_VITE_CONFIGS = [
15
+ "vite.config.ts",
16
+ "vite.config.mts",
17
+ "vite.config.js",
18
+ "vite.config.mjs",
19
+ ];
20
+ /** The framework wins every scalar it sets, so the proxy, conditions and defines Parlor needs survive. */
21
+ export function mergeViteConfig(user, framework) {
22
+ if (!user)
23
+ return { config: framework, ignored: [] };
24
+ const { plugins: userPlugins = [], ...userRest } = user;
25
+ const own = { ...userRest };
26
+ const ignored = [];
27
+ for (const key of FRAMEWORK_VITE_KEYS) {
28
+ const [head = "", leaf] = key.split(".");
29
+ if (leaf === undefined) {
30
+ if (head in own)
31
+ ignored.push(key);
32
+ delete own[head];
33
+ continue;
34
+ }
35
+ const section = own[head];
36
+ if (typeof section !== "object" || section === null || !(leaf in section))
37
+ continue;
38
+ const { [leaf]: _dropped, ...rest } = section;
39
+ own[head] = rest;
40
+ ignored.push(key);
41
+ }
42
+ const { plugins: frameworkPlugins = [], ...frameworkRest } = framework;
43
+ const merged = mergeConfig(own, frameworkRest);
44
+ return {
45
+ config: { ...merged, plugins: [...frameworkPlugins, ...userPlugins] },
46
+ ignored,
47
+ };
48
+ }
49
+ export function strayViteConfig(rootDir) {
50
+ return STRAY_VITE_CONFIGS.find((name) => existsSync(join(rootDir, name)));
51
+ }
52
+ const warned = new Set();
53
+ function warnOnce(messages) {
54
+ for (const message of messages) {
55
+ if (warned.has(message))
56
+ continue;
57
+ warned.add(message);
58
+ console.warn(message);
59
+ }
60
+ }
61
+ export function parlorViteConfig({ config, rootDir }, options, framework) {
62
+ const { config: merged, ignored } = mergeViteConfig(config.vite, {
63
+ ...framework,
64
+ root: rootDir,
65
+ configFile: false,
66
+ plugins: parlorWeb(config, { ...options, root: rootDir }),
67
+ });
68
+ warnOnce(ignored.map((key) => `vite.${key} in parlor.config.ts is ignored: Parlor sets it from its own config.`));
69
+ const stray = strayViteConfig(rootDir);
70
+ if (stray) {
71
+ warnOnce([
72
+ `${stray} is not read. Put Vite options in the vite field of parlor.config.ts.`,
73
+ ]);
74
+ }
75
+ return merged;
76
+ }
77
+ //# sourceMappingURL=vite-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-config.js","sourceRoot":"","sources":["../src/vite-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport type { LoadedConfig } from \"@parlor/config\";\nimport { type InlineConfig, mergeConfig, type UserConfig } from \"vite\";\n\nimport { parlorWeb, type WebOptions } from \"./plugin.js\";\n\nexport const FRAMEWORK_VITE_KEYS = [\n \"root\",\n \"base\",\n \"configFile\",\n \"build.outDir\",\n \"build.emptyOutDir\",\n \"server.port\",\n \"server.strictPort\",\n] as const;\n\nexport const STRAY_VITE_CONFIGS = [\n \"vite.config.ts\",\n \"vite.config.mts\",\n \"vite.config.js\",\n \"vite.config.mjs\",\n] as const;\n\nexport interface MergedViteConfig {\n readonly config: InlineConfig;\n readonly ignored: readonly string[];\n}\n\n/** The framework wins every scalar it sets, so the proxy, conditions and defines Parlor needs survive. */\nexport function mergeViteConfig(\n user: UserConfig | undefined,\n framework: InlineConfig,\n): MergedViteConfig {\n if (!user) return { config: framework, ignored: [] };\n const { plugins: userPlugins = [], ...userRest } = user;\n const own: Record<string, unknown> = { ...userRest };\n const ignored: string[] = [];\n for (const key of FRAMEWORK_VITE_KEYS) {\n const [head = \"\", leaf] = key.split(\".\");\n if (leaf === undefined) {\n if (head in own) ignored.push(key);\n delete own[head];\n continue;\n }\n const section = own[head];\n if (typeof section !== \"object\" || section === null || !(leaf in section)) continue;\n const { [leaf]: _dropped, ...rest } = section as Record<string, unknown>;\n own[head] = rest;\n ignored.push(key);\n }\n const { plugins: frameworkPlugins = [], ...frameworkRest } = framework;\n const merged = mergeConfig(own, frameworkRest) as InlineConfig;\n return {\n config: { ...merged, plugins: [...frameworkPlugins, ...userPlugins] },\n ignored,\n };\n}\n\nexport function strayViteConfig(rootDir: string): string | undefined {\n return STRAY_VITE_CONFIGS.find((name) => existsSync(join(rootDir, name)));\n}\n\nconst warned = new Set<string>();\n\nfunction warnOnce(messages: readonly string[]): void {\n for (const message of messages) {\n if (warned.has(message)) continue;\n warned.add(message);\n console.warn(message);\n }\n}\n\nexport function parlorViteConfig(\n { config, rootDir }: LoadedConfig,\n options: WebOptions,\n framework: InlineConfig,\n): InlineConfig {\n const { config: merged, ignored } = mergeViteConfig(config.vite, {\n ...framework,\n root: rootDir,\n configFile: false,\n plugins: parlorWeb(config, { ...options, root: rootDir }),\n });\n warnOnce(\n ignored.map(\n (key) =>\n `vite.${key} in parlor.config.ts is ignored: Parlor sets it from its own config.`,\n ),\n );\n const stray = strayViteConfig(rootDir);\n if (stray) {\n warnOnce([\n `${stray} is not read. Put Vite options in the vite field of parlor.config.ts.`,\n ]);\n }\n return merged;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@parlor/shell-web",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "homepage": "https://cychien.github.io/parlor",
5
+ "bugs": "https://github.com/cychien/parlor/issues",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/cychien/parlor.git",
10
+ "directory": "packages/shell-web"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ }
21
+ },
22
+ "dependencies": {
23
+ "@tanstack/router-plugin": "^1.168.47",
24
+ "@vitejs/plugin-react": "^6.1.2",
25
+ "vite-plugin-pwa": "^2.0.0",
26
+ "workbox-build": "^7.4.1",
27
+ "workbox-window": "^7.4.1",
28
+ "@parlor/agent-ui": "0.2.0",
29
+ "@parlor/config": "0.2.0"
30
+ },
31
+ "devDependencies": {
32
+ "@tanstack/react-query": "^5.104.1",
33
+ "@tanstack/react-router": "^1.170.45",
34
+ "@tanstack/router-generator": "^1.167.45",
35
+ "@types/node": "^24.14.0",
36
+ "react": "^19.3.0",
37
+ "react-dom": "^19.3.0",
38
+ "typescript": "^7.0.2",
39
+ "vite": "^8.3.5",
40
+ "vitest": "^5.0.3",
41
+ "@parlor/client": "0.2.0"
42
+ },
43
+ "peerDependencies": {
44
+ "vite": "^8.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.json",
48
+ "typecheck": "tsc -p tsconfig.json --noEmit",
49
+ "test": "vitest run"
50
+ }
6
51
  }