@storyshelf/app 0.10.0-next-e021f6b → 0.10.0-next-97f4b38
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/dist/index.mjs
CHANGED
|
@@ -380,7 +380,7 @@ function registerDocs(app) {
|
|
|
380
380
|
* from source (dev server, tests).
|
|
381
381
|
*/
|
|
382
382
|
function packageVersion() {
|
|
383
|
-
return "0.10.0-next-
|
|
383
|
+
return "0.10.0-next-97f4b38";
|
|
384
384
|
}
|
|
385
385
|
export { createAuthSystemHook, createShelfApp, createShelfLogger };
|
|
386
386
|
|
|
@@ -17,6 +17,13 @@ async function resolveRequestUser(c, auth) {
|
|
|
17
17
|
*/
|
|
18
18
|
const PUBLISHED_STORYBOOK = /^\/projects\/[^/]+\/storybook(?:\/|$)/u;
|
|
19
19
|
/**
|
|
20
|
+
* Short links to a published Storybook (`/_/<build id or project slug>/...`). The handler decides
|
|
21
|
+
* access itself (a public build opens, anything else answers like a private one), so a signed-out
|
|
22
|
+
* visitor with a link to a public build is not sent to login first (ADR 0011). Anchored: `/_s/` is
|
|
23
|
+
* the signed-URL prefix and `/_/` alone is not a link.
|
|
24
|
+
*/
|
|
25
|
+
const SHORT_LINK = /^\/_\/[^/]+(?:\/|$)/u;
|
|
26
|
+
/**
|
|
20
27
|
* Gate the server-rendered UI behind auth (ADR 0008): unauthenticated HTML
|
|
21
28
|
* requests redirect to login. API/auth routes answer 401/403 themselves, and
|
|
22
29
|
* published-Storybook routes enforce their own auth (public builds are
|
|
@@ -27,7 +34,7 @@ function authGate() {
|
|
|
27
34
|
return async (c, next) => {
|
|
28
35
|
const { user, authEnabled: isAuthEnabled } = getStore();
|
|
29
36
|
const { path } = c.req;
|
|
30
|
-
if (!isAuthEnabled || user || path.startsWith("/api/") || path.startsWith("/auth/") || path.startsWith("/assets/") || path.startsWith(STORYBOOK_TOKEN_PREFIX) || path.startsWith("/.well-known/") || PUBLISHED_STORYBOOK.test(path) || /^\/projects\/[^/]+\/?$/u.test(path)) {
|
|
37
|
+
if (!isAuthEnabled || user || path.startsWith("/api/") || path.startsWith("/auth/") || path.startsWith("/assets/") || path.startsWith(STORYBOOK_TOKEN_PREFIX) || path.startsWith("/.well-known/") || PUBLISHED_STORYBOOK.test(path) || SHORT_LINK.test(path) || /^\/projects\/[^/]+\/?$/u.test(path)) {
|
|
31
38
|
await next();
|
|
32
39
|
return;
|
|
33
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-gate.mjs","names":[],"sources":["../../src/middleware/auth-gate.ts"],"sourcesContent":["import type { Auth } from \"@storyshelf/core/auth\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { STORYBOOK_TOKEN_PREFIX } from \"@storyshelf/core/utils\";\nimport type { Context, Next } from \"hono\";\nimport { getStore } from \"../store.ts\";\n\n/** Resolve the request user from the auth adapter (null when auth is off or a token is used). */\nexport async function resolveRequestUser(\n c: Pick<Context, \"req\">,\n auth?: Auth,\n): Promise<AuthUser | null> {\n if (!auth) {\n return null;\n }\n if (c.req.header(\"authorization\")) {\n return null;\n }\n return await auth.check(c.req.raw);\n}\n\n/**\n * The published-Storybook routes, which enforce their own auth. Only a `storybook` segment right\n * after the project counts: a `/storybook` anywhere else in a path (a wildcard tail of a component\n * or label page, with a query naming another page) must not open the gate.\n */\nconst PUBLISHED_STORYBOOK = /^\\/projects\\/[^/]+\\/storybook(?:\\/|$)/u;\n\n/**\n * Gate the server-rendered UI behind auth (ADR 0008): unauthenticated HTML\n * requests redirect to login. API/auth routes answer 401/403 themselves, and\n * published-Storybook routes enforce their own auth (public builds are\n * viewable without a session, ADR 0011), and `/_s/<token>` is answered by its signed token\n * alone (ADR 0007).\n */\nexport function authGate() {\n // oxlint-disable-next-line typescript/no-invalid-void-type -- Hono middleware may not return Response\n return async (c: Context, next: Next): Promise<Response | void> => {\n const { user, authEnabled: isAuthEnabled } = getStore();\n const { path } = c.req;\n if (\n !isAuthEnabled ||\n user ||\n path.startsWith(\"/api/\") ||\n path.startsWith(\"/auth/\") ||\n path.startsWith(\"/assets/\") ||\n path.startsWith(STORYBOOK_TOKEN_PREFIX) ||\n path.startsWith(\"/.well-known/\") ||\n PUBLISHED_STORYBOOK.test(path) ||\n /^\\/projects\\/[^/]+\\/?$/u.test(path)\n ) {\n await next();\n return;\n }\n if (c.req.header(\"HX-Request\") === \"true\") {\n c.header(\"HX-Redirect\", \"/auth/login\");\n return c.body(null, 204);\n }\n return c.redirect(\"/auth/login\", 302);\n };\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,mBACpB,GACA,MAC0B;CAC1B,IAAI,CAAC,MACH,OAAO;CAET,IAAI,EAAE,IAAI,OAAO,eAAe,GAC9B,OAAO;CAET,OAAO,MAAM,KAAK,MAAM,EAAE,IAAI,GAAG;AACnC;;;;;;AAOA,MAAM,sBAAsB;;;;;;;;
|
|
1
|
+
{"version":3,"file":"auth-gate.mjs","names":[],"sources":["../../src/middleware/auth-gate.ts"],"sourcesContent":["import type { Auth } from \"@storyshelf/core/auth\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { STORYBOOK_TOKEN_PREFIX } from \"@storyshelf/core/utils\";\nimport type { Context, Next } from \"hono\";\nimport { getStore } from \"../store.ts\";\n\n/** Resolve the request user from the auth adapter (null when auth is off or a token is used). */\nexport async function resolveRequestUser(\n c: Pick<Context, \"req\">,\n auth?: Auth,\n): Promise<AuthUser | null> {\n if (!auth) {\n return null;\n }\n if (c.req.header(\"authorization\")) {\n return null;\n }\n return await auth.check(c.req.raw);\n}\n\n/**\n * The published-Storybook routes, which enforce their own auth. Only a `storybook` segment right\n * after the project counts: a `/storybook` anywhere else in a path (a wildcard tail of a component\n * or label page, with a query naming another page) must not open the gate.\n */\nconst PUBLISHED_STORYBOOK = /^\\/projects\\/[^/]+\\/storybook(?:\\/|$)/u;\n\n/**\n * Short links to a published Storybook (`/_/<build id or project slug>/...`). The handler decides\n * access itself (a public build opens, anything else answers like a private one), so a signed-out\n * visitor with a link to a public build is not sent to login first (ADR 0011). Anchored: `/_s/` is\n * the signed-URL prefix and `/_/` alone is not a link.\n */\nconst SHORT_LINK = /^\\/_\\/[^/]+(?:\\/|$)/u;\n\n/**\n * Gate the server-rendered UI behind auth (ADR 0008): unauthenticated HTML\n * requests redirect to login. API/auth routes answer 401/403 themselves, and\n * published-Storybook routes enforce their own auth (public builds are\n * viewable without a session, ADR 0011), and `/_s/<token>` is answered by its signed token\n * alone (ADR 0007).\n */\nexport function authGate() {\n // oxlint-disable-next-line typescript/no-invalid-void-type -- Hono middleware may not return Response\n return async (c: Context, next: Next): Promise<Response | void> => {\n const { user, authEnabled: isAuthEnabled } = getStore();\n const { path } = c.req;\n if (\n !isAuthEnabled ||\n user ||\n path.startsWith(\"/api/\") ||\n path.startsWith(\"/auth/\") ||\n path.startsWith(\"/assets/\") ||\n path.startsWith(STORYBOOK_TOKEN_PREFIX) ||\n path.startsWith(\"/.well-known/\") ||\n PUBLISHED_STORYBOOK.test(path) ||\n SHORT_LINK.test(path) ||\n /^\\/projects\\/[^/]+\\/?$/u.test(path)\n ) {\n await next();\n return;\n }\n if (c.req.header(\"HX-Request\") === \"true\") {\n c.header(\"HX-Redirect\", \"/auth/login\");\n return c.body(null, 204);\n }\n return c.redirect(\"/auth/login\", 302);\n };\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,mBACpB,GACA,MAC0B;CAC1B,IAAI,CAAC,MACH,OAAO;CAET,IAAI,EAAE,IAAI,OAAO,eAAe,GAC9B,OAAO;CAET,OAAO,MAAM,KAAK,MAAM,EAAE,IAAI,GAAG;AACnC;;;;;;AAOA,MAAM,sBAAsB;;;;;;;AAQ5B,MAAM,aAAa;;;;;;;;AASnB,SAAgB,WAAW;CAEzB,OAAO,OAAO,GAAY,SAAyC;EACjE,MAAM,EAAE,MAAM,aAAa,kBAAkB,SAAS;EACtD,MAAM,EAAE,SAAS,EAAE;EACnB,IACE,CAAC,iBACD,QACA,KAAK,WAAW,OAAO,KACvB,KAAK,WAAW,QAAQ,KACxB,KAAK,WAAW,UAAU,KAC1B,KAAK,WAAW,sBAAsB,KACtC,KAAK,WAAW,eAAe,KAC/B,oBAAoB,KAAK,IAAI,KAC7B,WAAW,KAAK,IAAI,KACpB,0BAA0B,KAAK,IAAI,GACnC;GACA,MAAM,KAAK;GACX;EACF;EACA,IAAI,EAAE,IAAI,OAAO,YAAY,MAAM,QAAQ;GACzC,EAAE,OAAO,eAAe,aAAa;GACrC,OAAO,EAAE,KAAK,MAAM,GAAG;EACzB;EACA,OAAO,EAAE,SAAS,eAAe,GAAG;CACtC;AACF"}
|
|
@@ -14,6 +14,40 @@ async function staticsReady(projectId, buildId) {
|
|
|
14
14
|
if (await getStore().storage.exists(posix.join(storybookDir(projectId, buildId), "manifest.json"))) return true;
|
|
15
15
|
return await getStore().storage.exists(posix.join(storybookDir(projectId, buildId), "iframe.html"));
|
|
16
16
|
}
|
|
17
|
+
/** Whether a short-link id names one build (a 26-character ULID) rather than a project slug. */
|
|
18
|
+
function isBuildId(id) {
|
|
19
|
+
return id.length === 26 && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(id);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The build a short link opens: the build itself for a ULID, whatever its status; for a project
|
|
23
|
+
* slug the latest approved build on the default branch (never a feature branch or an unreviewed
|
|
24
|
+
* build). Null when nothing matches.
|
|
25
|
+
*/
|
|
26
|
+
async function resolveShortLink(id) {
|
|
27
|
+
const { db } = getStore();
|
|
28
|
+
if (isBuildId(id)) {
|
|
29
|
+
const build = await new BuildModel(db).get(id);
|
|
30
|
+
const project = build ? await new ProjectModel(db).get(build.projectId) : null;
|
|
31
|
+
return build && project ? {
|
|
32
|
+
project,
|
|
33
|
+
build
|
|
34
|
+
} : null;
|
|
35
|
+
}
|
|
36
|
+
const project = await new ProjectModel(db).getBySlug(id);
|
|
37
|
+
const build = project ? await new BuildModel(db).latestApprovedDefault(project) : null;
|
|
38
|
+
return project && build ? {
|
|
39
|
+
project,
|
|
40
|
+
build
|
|
41
|
+
} : null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The answer for a short link that names nothing the caller may open: a missing build or project
|
|
45
|
+
* and a private one look the same, so the link never confirms that a private id exists. Without
|
|
46
|
+
* sign-in every build is open, so a missing id is a plain 404.
|
|
47
|
+
*/
|
|
48
|
+
function unreachable(c) {
|
|
49
|
+
return getStore().authEnabled ? c.redirect("/auth/login", 302) : c.notFound();
|
|
50
|
+
}
|
|
17
51
|
/** Register the published Storybook resolver and static-asset routes. */
|
|
18
52
|
function registerStorybook(app) {
|
|
19
53
|
app.get("/_", (c) => c.redirect("/", 302));
|
|
@@ -29,29 +63,18 @@ function registerStorybook(app) {
|
|
|
29
63
|
});
|
|
30
64
|
app.get("/_/:id", async (c) => {
|
|
31
65
|
const id = c.req.param("id");
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
} else if (!await new ProjectModel(getStore().db).getBySlug(id)) notFound();
|
|
66
|
+
const target = await resolveShortLink(id);
|
|
67
|
+
if (!target || !await publishedAccess(target.build, target.project)) return unreachable(c);
|
|
35
68
|
return c.redirect(`/_/${id}/`, 302);
|
|
36
69
|
});
|
|
37
70
|
app.get("/_/:id/*", async (c) => {
|
|
38
71
|
const id = c.req.param("id");
|
|
39
|
-
const isUlid26 =
|
|
72
|
+
const isUlid26 = isBuildId(id);
|
|
40
73
|
const rest = publishedRelativePath(c.req.path, `/_/${id}/`);
|
|
41
74
|
if (rest === null) return c.notFound();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
build = await new BuildModel(getStore().db).get(id);
|
|
46
|
-
if (!build) return c.notFound();
|
|
47
|
-
project = await new ProjectModel(getStore().db).get(build.projectId);
|
|
48
|
-
if (!project) return c.notFound();
|
|
49
|
-
} else {
|
|
50
|
-
project = await new ProjectModel(getStore().db).getBySlug(id);
|
|
51
|
-
if (!project) return c.notFound();
|
|
52
|
-
build = await new BuildModel(getStore().db).latestApprovedDefault(project);
|
|
53
|
-
if (!build) return c.notFound();
|
|
54
|
-
}
|
|
75
|
+
const target = await resolveShortLink(id);
|
|
76
|
+
if (!target) return unreachable(c);
|
|
77
|
+
const { project, build } = target;
|
|
55
78
|
const access = await publishedAccess(build, project);
|
|
56
79
|
if (!access) return c.redirect("/auth/login", 302);
|
|
57
80
|
if (rest === "" && !await staticsReady(project.id, build.id)) return c.html(renderStorybookPreparingPage(project, build, project.slug), 200);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/routers/storybook.ts"],"sourcesContent":["import { BuildModel } from \"@storyshelf/core/models\";\nimport { LabelModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport type { Build } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport { storybookDir } from \"@storyshelf/core/utils\";\nimport { posix } from \"node:path\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { renderStorybookPage, renderStorybookPreparingPage } from \"../pages/storybook.tsx\";\nimport { getStore } from \"../store.ts\";\nimport { notFound } from \"./helpers.ts\";\nimport { publishedAccess } from \"./published-access.ts\";\nimport { publishedRelativePath, servePublishedFile } from \"./published-files.ts\";\n\n/** Check whether a build's extracted statics are available for serving. */\nasync function staticsReady(projectId: string, buildId: string): Promise<boolean> {\n // New path: manifest.json indicates content-addressed storage; fallback to legacy iframe.html\n if (\n await getStore().storage.exists(posix.join(storybookDir(projectId, buildId), \"manifest.json\"))\n ) {\n return true;\n }\n return await getStore().storage.exists(\n posix.join(storybookDir(projectId, buildId), \"iframe.html\"),\n );\n}\n\n/** Register the published Storybook resolver and static-asset routes. */\nexport function registerStorybook(app: ShelfRouter): void {\n app.get(\"/_\", (c) => c.redirect(\"/\", 302));\n app.get(\"/_/\", (c) => c.redirect(\"/\", 302));\n\n // Resolver: latest approved build on the default branch (never feature branches).\n app.get(\"/projects/:slug/storybook\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const build = await new BuildModel(getStore().db).latestApprovedDefault(project);\n if (!build) {\n notFound(\"No approved build on the default branch yet\");\n }\n if (!(await publishedAccess(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n return c.redirect(`/projects/${slug}/storybook/build/${build.id}/`, 302);\n });\n\n // Short link canonical is /_/:id/ with trailing slash (so relative ./sb-manager/... resolves).\n // 26-char ULID → that exact build, whatever its status (visibility check only).\n // Any other id → project slug → latest approved build on the default branch, else 404\n // (no fallback to feature-branch or unreviewed builds). No fall-through on ULID.\n app.get(\"/_/:id\", async (c) => {\n const id = c.req.param(\"id\");\n const isUlid26 = id.length === 26 && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(id);\n if (isUlid26) {\n const build = await new BuildModel(getStore().db).get(id);\n if (!build) notFound();\n } else {\n const project = await new ProjectModel(getStore().db).getBySlug(id);\n if (!project) notFound();\n }\n return c.redirect(`/_/${id}/`, 302);\n });\n\n app.get(\"/_/:id/*\", async (c) => {\n const id = c.req.param(\"id\");\n const isUlid26 = id.length === 26 && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(id);\n const rest = publishedRelativePath(c.req.path, `/_/${id}/`);\n if (rest === null) return c.notFound();\n let project: Project | null = null;\n let build: Build | null = null;\n if (isUlid26) {\n build = await new BuildModel(getStore().db).get(id);\n if (!build) return c.notFound();\n project = await new ProjectModel(getStore().db).get(build.projectId);\n if (!project) return c.notFound();\n } else {\n project = await new ProjectModel(getStore().db).getBySlug(id);\n if (!project) return c.notFound();\n build = await new BuildModel(getStore().db).latestApprovedDefault(project);\n if (!build) return c.notFound();\n }\n const access = await publishedAccess(build, project);\n if (!access) return c.redirect(\"/auth/login\", 302);\n if (rest === \"\" && !(await staticsReady(project.id, build.id))) {\n return c.html(renderStorybookPreparingPage(project, build, project.slug), 200);\n }\n const fallback = isUlid26\n ? `/projects/${project.slug}/builds/${build.id}`\n : `/projects/${project.slug}`;\n const served = await servePublishedFile(c, {\n projectId: project.id,\n buildId: build.id,\n rel: rest === \"\" ? \"index.html\" : rest,\n access,\n cacheControl: shortLinkCache(isUlid26, rest === \"\"),\n });\n if (served) return served;\n return rest === \"\" || rest === \"index.html\" ? c.redirect(fallback, 302) : c.notFound();\n });\n\n // Canonical build route: serves the static Storybook and the landing page.\n app.get(\"/projects/:slug/storybook/build/:buildId/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // remainder from the raw request path (un-encoded) and decode it ourselves.\n const rest = publishedRelativePath(c.req.path, `/projects/${slug}/storybook/build/${buildId}/`);\n if (rest === null) {\n return c.notFound();\n }\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n notFound(\"Build not found\");\n }\n const access = await publishedAccess(build, project);\n if (!access) {\n return c.redirect(\"/auth/login\", 302);\n }\n\n if (rest === \"\") {\n if (!(await staticsReady(project.id, build.id))) {\n return c.html(renderStorybookPreparingPage(project, build, slug), 200);\n }\n const storyId = c.req.query(\"storyId\") ?? c.req.query(\"id\") ?? undefined;\n const viewMode = c.req.query(\"viewMode\") === \"docs\" ? \"docs\" : undefined;\n return c.html(renderStorybookPage(project, build, slug, { storyId, viewMode }), 200);\n }\n\n return (\n (await servePublishedFile(c, {\n projectId: project.id,\n buildId: build.id,\n rel: rest,\n access,\n cacheControl: \"public, max-age=31536000, immutable\",\n })) ?? c.notFound()\n );\n });\n\n // Resolver: latest build bearing a label (`build` is a reserved label key).\n app.get(\"/projects/:slug/storybook/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n if (key === \"build\") {\n return c.notFound();\n }\n let value: string;\n try {\n value = decodeURIComponent(c.req.path.split(\"/\").slice(5).join(\"/\"));\n } catch {\n return c.notFound();\n }\n if (!value) return c.notFound();\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const buildId = await new LabelModel(getStore().db).latestBuildId(project.id, key, value);\n if (!buildId) {\n notFound(\"No build carries that label\");\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || !(await publishedAccess(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n return c.redirect(`/projects/${slug}/storybook/build/${buildId}/`, 302);\n });\n}\n\n/** Cache policy of the short link: a ULID names one build forever; a slug follows the latest. */\nfunction shortLinkCache(isUlid26: boolean, isRoot: boolean): string {\n if (isUlid26) {\n return \"public, max-age=31536000, immutable\";\n }\n return isRoot ? \"public, max-age=60\" : \"public, max-age=3600\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storybook.mjs","names":[],"sources":["../../src/routers/storybook.ts"],"sourcesContent":["import { BuildModel } from \"@storyshelf/core/models\";\nimport { LabelModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport type { Build } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport { storybookDir } from \"@storyshelf/core/utils\";\nimport type { Context } from \"hono\";\nimport { posix } from \"node:path\";\nimport type { ShelfRouter } from \"../app-types.ts\";\nimport { renderStorybookPage, renderStorybookPreparingPage } from \"../pages/storybook.tsx\";\nimport { getStore } from \"../store.ts\";\nimport { notFound } from \"./helpers.ts\";\nimport { publishedAccess } from \"./published-access.ts\";\nimport { publishedRelativePath, servePublishedFile } from \"./published-files.ts\";\n\n/** Check whether a build's extracted statics are available for serving. */\nasync function staticsReady(projectId: string, buildId: string): Promise<boolean> {\n // New path: manifest.json indicates content-addressed storage; fallback to legacy iframe.html\n if (\n await getStore().storage.exists(posix.join(storybookDir(projectId, buildId), \"manifest.json\"))\n ) {\n return true;\n }\n return await getStore().storage.exists(\n posix.join(storybookDir(projectId, buildId), \"iframe.html\"),\n );\n}\n\n/** Whether a short-link id names one build (a 26-character ULID) rather than a project slug. */\nfunction isBuildId(id: string): boolean {\n return id.length === 26 && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(id);\n}\n\n/**\n * The build a short link opens: the build itself for a ULID, whatever its status; for a project\n * slug the latest approved build on the default branch (never a feature branch or an unreviewed\n * build). Null when nothing matches.\n */\nasync function resolveShortLink(id: string): Promise<{ project: Project; build: Build } | null> {\n const { db } = getStore();\n if (isBuildId(id)) {\n const build = await new BuildModel(db).get(id);\n const project = build ? await new ProjectModel(db).get(build.projectId) : null;\n return build && project ? { project, build } : null;\n }\n const project = await new ProjectModel(db).getBySlug(id);\n const build = project ? await new BuildModel(db).latestApprovedDefault(project) : null;\n return project && build ? { project, build } : null;\n}\n\n/**\n * The answer for a short link that names nothing the caller may open: a missing build or project\n * and a private one look the same, so the link never confirms that a private id exists. Without\n * sign-in every build is open, so a missing id is a plain 404.\n */\nfunction unreachable(c: Context): Response | Promise<Response> {\n return getStore().authEnabled ? c.redirect(\"/auth/login\", 302) : c.notFound();\n}\n\n/** Register the published Storybook resolver and static-asset routes. */\nexport function registerStorybook(app: ShelfRouter): void {\n app.get(\"/_\", (c) => c.redirect(\"/\", 302));\n app.get(\"/_/\", (c) => c.redirect(\"/\", 302));\n\n // Resolver: latest approved build on the default branch (never feature branches).\n app.get(\"/projects/:slug/storybook\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const build = await new BuildModel(getStore().db).latestApprovedDefault(project);\n if (!build) {\n notFound(\"No approved build on the default branch yet\");\n }\n if (!(await publishedAccess(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n return c.redirect(`/projects/${slug}/storybook/build/${build.id}/`, 302);\n });\n\n // Short link canonical is /_/:id/ with trailing slash (so relative ./sb-manager/... resolves).\n // 26-char ULID → that exact build, whatever its status (visibility check only).\n // Any other id → project slug → latest approved build on the default branch, else 404\n // (no fallback to feature-branch or unreviewed builds). No fall-through on ULID.\n app.get(\"/_/:id\", async (c) => {\n const id = c.req.param(\"id\");\n const target = await resolveShortLink(id);\n // The same access check as the canonical form, so an id the caller may not open answers like\n // one that does not exist (no private id is confirmed by a redirect).\n if (!target || !(await publishedAccess(target.build, target.project))) return unreachable(c);\n return c.redirect(`/_/${id}/`, 302);\n });\n\n app.get(\"/_/:id/*\", async (c) => {\n const id = c.req.param(\"id\");\n const isUlid26 = isBuildId(id);\n const rest = publishedRelativePath(c.req.path, `/_/${id}/`);\n if (rest === null) return c.notFound();\n const target = await resolveShortLink(id);\n if (!target) return unreachable(c);\n const { project, build } = target;\n const access = await publishedAccess(build, project);\n if (!access) return c.redirect(\"/auth/login\", 302);\n if (rest === \"\" && !(await staticsReady(project.id, build.id))) {\n return c.html(renderStorybookPreparingPage(project, build, project.slug), 200);\n }\n const fallback = isUlid26\n ? `/projects/${project.slug}/builds/${build.id}`\n : `/projects/${project.slug}`;\n const served = await servePublishedFile(c, {\n projectId: project.id,\n buildId: build.id,\n rel: rest === \"\" ? \"index.html\" : rest,\n access,\n cacheControl: shortLinkCache(isUlid26, rest === \"\"),\n });\n if (served) return served;\n return rest === \"\" || rest === \"index.html\" ? c.redirect(fallback, 302) : c.notFound();\n });\n\n // Canonical build route: serves the static Storybook and the landing page.\n app.get(\"/projects/:slug/storybook/build/:buildId/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const buildId = c.req.param(\"buildId\");\n // Hono's bare `/*` wildcard is not exposed through `param()`, so derive the\n // remainder from the raw request path (un-encoded) and decode it ourselves.\n const rest = publishedRelativePath(c.req.path, `/projects/${slug}/storybook/build/${buildId}/`);\n if (rest === null) {\n return c.notFound();\n }\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || build.projectId !== project.id) {\n notFound(\"Build not found\");\n }\n const access = await publishedAccess(build, project);\n if (!access) {\n return c.redirect(\"/auth/login\", 302);\n }\n\n if (rest === \"\") {\n if (!(await staticsReady(project.id, build.id))) {\n return c.html(renderStorybookPreparingPage(project, build, slug), 200);\n }\n const storyId = c.req.query(\"storyId\") ?? c.req.query(\"id\") ?? undefined;\n const viewMode = c.req.query(\"viewMode\") === \"docs\" ? \"docs\" : undefined;\n return c.html(renderStorybookPage(project, build, slug, { storyId, viewMode }), 200);\n }\n\n return (\n (await servePublishedFile(c, {\n projectId: project.id,\n buildId: build.id,\n rel: rest,\n access,\n cacheControl: \"public, max-age=31536000, immutable\",\n })) ?? c.notFound()\n );\n });\n\n // Resolver: latest build bearing a label (`build` is a reserved label key).\n app.get(\"/projects/:slug/storybook/:key/*\", async (c) => {\n const slug = c.req.param(\"slug\");\n const key = c.req.param(\"key\");\n if (key === \"build\") {\n return c.notFound();\n }\n let value: string;\n try {\n value = decodeURIComponent(c.req.path.split(\"/\").slice(5).join(\"/\"));\n } catch {\n return c.notFound();\n }\n if (!value) return c.notFound();\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n const buildId = await new LabelModel(getStore().db).latestBuildId(project.id, key, value);\n if (!buildId) {\n notFound(\"No build carries that label\");\n }\n const build = await new BuildModel(getStore().db).get(buildId);\n if (!build || !(await publishedAccess(build, project))) {\n return c.redirect(\"/auth/login\", 302);\n }\n return c.redirect(`/projects/${slug}/storybook/build/${buildId}/`, 302);\n });\n}\n\n/** Cache policy of the short link: a ULID names one build forever; a slug follows the latest. */\nfunction shortLinkCache(isUlid26: boolean, isRoot: boolean): string {\n if (isUlid26) {\n return \"public, max-age=31536000, immutable\";\n }\n return isRoot ? \"public, max-age=60\" : \"public, max-age=3600\";\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storyshelf/app",
|
|
3
|
-
"version": "0.10.0-next-
|
|
3
|
+
"version": "0.10.0-next-97f4b38",
|
|
4
4
|
"description": "StoryShelf app: Hono app, API routes, and server-rendered UI over @storyshelf/core.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chromatic-alternative",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@hono/swagger-ui": "^0.6.1",
|
|
63
63
|
"@hono/zod-openapi": "^1.1.5",
|
|
64
|
-
"@storyshelf/core": "0.10.0-next-
|
|
65
|
-
"@storyshelf/observability": "0.10.0-next-
|
|
64
|
+
"@storyshelf/core": "0.10.0-next-97f4b38",
|
|
65
|
+
"@storyshelf/observability": "0.10.0-next-97f4b38",
|
|
66
66
|
"hono": "^4.11.0",
|
|
67
67
|
"zod": "^4.6.7"
|
|
68
68
|
},
|
|
@@ -72,9 +72,9 @@
|
|
|
72
72
|
"@opentelemetry/core": "^2.11.0",
|
|
73
73
|
"@opentelemetry/sdk-trace-base": "^2.11.0",
|
|
74
74
|
"@storybook/html-vite": "10.6.5",
|
|
75
|
-
"@storyshelf/auth": "0.10.0-next-
|
|
76
|
-
"@storyshelf/db-sqlite": "0.10.0-next-
|
|
77
|
-
"@storyshelf/design-figma": "0.10.0-next-
|
|
75
|
+
"@storyshelf/auth": "0.10.0-next-97f4b38",
|
|
76
|
+
"@storyshelf/db-sqlite": "0.10.0-next-97f4b38",
|
|
77
|
+
"@storyshelf/design-figma": "0.10.0-next-97f4b38",
|
|
78
78
|
"@types/node": "^26.0.0",
|
|
79
79
|
"@vitest/coverage-v8": "^4.0.0",
|
|
80
80
|
"adm-zip": "0.6.1",
|