@atelic-action/ui 0.14.0 → 0.15.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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Keeps a document's content out of every file the login page can load.
3
+ *
4
+ * With the wall armed, /assets/ stays open because the login page needs its
5
+ * own hashed CSS and JS to render (needsSession in src/gate/wall.ts). A content module that
6
+ * lands in a chunk there would be readable by anyone who opens /login, which
7
+ * is exactly how the example writeup and report first shipped: their route
8
+ * heads imported the content, so all of it rode in the entry bundle. So the
9
+ * client build writes every chunk carrying a private content module into
10
+ * /assets/doc/, which the wall holds back, and fails outright when one turns
11
+ * up anywhere else.
12
+ *
13
+ * Public content is the short list that is safe to ship to every page: the
14
+ * 404's copy, the sealed proposal (ciphertext by design, GATE.md), and the
15
+ * heads in `*.meta.ts`, which carry a title and nothing a snapshot produced.
16
+ * Every other module under src/content/ is treated as private, so a new
17
+ * artifact starts behind the wall.
18
+ */
19
+ import type { Plugin } from "vite";
20
+
21
+ /** Where the client build writes the chunks the wall holds back. */
22
+ export const DOC_DIR = "assets/doc";
23
+
24
+ const PUBLIC_CONTENT = /\/src\/content\/(not-found\.tsx|proposal[^/]*|[^/]+\.meta\.ts)$/;
25
+
26
+ /** Whether a module id is content that must never ship to the login page. */
27
+ export function isPrivateContent(id: string): boolean {
28
+ const path = id.split("?")[0].replaceAll("\\", "/");
29
+ return path.includes("/src/content/") && !PUBLIC_CONTENT.test(path);
30
+ }
31
+
32
+ export function privateChunks(): Plugin {
33
+ return {
34
+ name: "atelic:private-chunks",
35
+ configEnvironment(name) {
36
+ if (name !== "client") return;
37
+ return {
38
+ build: {
39
+ rollupOptions: {
40
+ output: {
41
+ chunkFileNames: (chunk) =>
42
+ chunk.moduleIds.some(isPrivateContent)
43
+ ? `${DOC_DIR}/[name]-[hash].js`
44
+ : "assets/[name]-[hash].js",
45
+ },
46
+ },
47
+ },
48
+ };
49
+ },
50
+ generateBundle(_options, bundle) {
51
+ if (this.environment?.name !== "client") return;
52
+ for (const file of Object.values(bundle)) {
53
+ if (file.type !== "chunk" || file.fileName.startsWith(`${DOC_DIR}/`)) continue;
54
+ const leaked = Object.entries(file.modules)
55
+ .filter(([id, module]) => module.renderedLength > 0 && isPrivateContent(id))
56
+ .map(([id]) => id.slice(id.indexOf("/src/") + 1));
57
+ if (leaked.length > 0) {
58
+ this.error(
59
+ `${file.fileName} would ship ${leaked.join(", ")} outside ${DOC_DIR}/, where the login page can load it with no session. A route's head is the usual cause: give it a *.meta.ts module and keep the content to the component.`,
60
+ );
61
+ }
62
+ }
63
+ },
64
+ };
65
+ }