create-vitnode-app 2.0.0-canary.1 → 2.0.0-canary.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +9 -2
  2. package/copy-of-vitnode-app/README.md +22 -0
  3. package/copy-of-vitnode-app/api/src/vitnode.api.config.ts +10 -11
  4. package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +4 -26
  5. package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +1 -12
  6. package/copy-of-vitnode-app/api-single-app/src/server/vitnode-api.server.ts +13 -13
  7. package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +3 -12
  8. package/copy-of-vitnode-app/docker/docker-compose.yml +6 -4
  9. package/copy-of-vitnode-app/monorepo/apps/api/.env.example +1 -1
  10. package/copy-of-vitnode-app/monorepo/apps/web/.env.example +1 -12
  11. package/copy-of-vitnode-app/monorepo/turbo.json +2 -2
  12. package/copy-of-vitnode-app/root/.env.example +1 -8
  13. package/copy-of-vitnode-app/root/global.d.ts +1 -19
  14. package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +2 -53
  15. package/copy-of-vitnode-app/root/src/components/main-footer.tsx +44 -0
  16. package/copy-of-vitnode-app/root/src/components/main-header.tsx +1 -30
  17. package/copy-of-vitnode-app/root/src/lib/i18n.ts +37 -0
  18. package/copy-of-vitnode-app/root/src/locales/app.ts +1 -25
  19. package/copy-of-vitnode-app/root/src/locales/packages.ts +1 -34
  20. package/copy-of-vitnode-app/root/src/router.tsx +9 -165
  21. package/copy-of-vitnode-app/root/src/routes/__root.tsx +12 -115
  22. package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +4 -54
  23. package/copy-of-vitnode-app/root/src/routes/_admin.tsx +5 -198
  24. package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +182 -46
  25. package/copy-of-vitnode-app/root/src/routes/_main.tsx +12 -47
  26. package/copy-of-vitnode-app/root/src/server/messages.server.ts +2 -23
  27. package/copy-of-vitnode-app/root/src/start.ts +3 -86
  28. package/copy-of-vitnode-app/root/src/styles.css +70 -92
  29. package/copy-of-vitnode-app/root/src/vitnode.config.ts +15 -53
  30. package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +12 -0
  31. package/copy-of-vitnode-app/root/tsconfig.json +8 -3
  32. package/copy-of-vitnode-app/root/vite.config.ts +2 -99
  33. package/copy-of-vitnode-plugin/root/global.d.ts +6 -6
  34. package/dist/src/create/create-package-json.js +3 -3
  35. package/dist/src/create/create-vitnode.js +15 -1
  36. package/dist/src/create/package-versions.js +22 -22
  37. package/dist/src/helpers/init-git.js +38 -0
  38. package/dist/src/helpers/init-vitnode.js +2 -3
  39. package/dist/src/helpers/install-dependencies.js +2 -3
  40. package/dist/src/helpers/spawn-command.js +7 -0
  41. package/dist/src/index.js +1 -0
  42. package/dist/src/plugin/create/add-plugin-to-config.js +231 -0
  43. package/dist/src/plugin/create/create-package-json.js +2 -1
  44. package/dist/src/plugin/create/create-plugin-vitnode.js +23 -2
  45. package/dist/src/plugin/create/route-templates.js +103 -61
  46. package/dist/src/questions.js +7 -0
  47. package/dist/tsconfig.build.tsbuildinfo +1 -1
  48. package/package.json +6 -6
  49. package/copy-of-vitnode-app/api/src/i18n.ts +0 -38
  50. package/copy-of-vitnode-app/root/src/i18n.ts +0 -36
  51. package/copy-of-vitnode-app/root/src/lib/admin-auth.ts +0 -11
  52. package/copy-of-vitnode-app/root/src/lib/admin-nav.ts +0 -43
  53. package/copy-of-vitnode-app/root/src/lib/admin-search.ts +0 -37
  54. package/copy-of-vitnode-app/root/src/lib/auth.ts +0 -66
  55. package/copy-of-vitnode-app/root/src/lib/content-registry.ts +0 -51
  56. package/copy-of-vitnode-app/root/src/lib/document-headers.ts +0 -128
  57. package/copy-of-vitnode-app/root/src/lib/i18n/runtime.ts +0 -75
  58. package/copy-of-vitnode-app/root/src/lib/i18n/shared.ts +0 -20
  59. package/copy-of-vitnode-app/root/src/lib/navigation.ts +0 -24
  60. package/copy-of-vitnode-app/root/src/lib/page-head.ts +0 -19
  61. package/copy-of-vitnode-app/root/src/vitnode.shell.config.ts +0 -35
@@ -1,37 +0,0 @@
1
- import { createServerFn } from "@tanstack/react-start";
2
- import { readAdminUserSearchOnApi } from "@vitnode/core/tanstack/admin/server";
3
- import { z } from "zod";
4
-
5
- /**
6
- * The AdminCP command palette's user lookup, as this app's server function.
7
- *
8
- * One handler, one line, exactly like `lib/auth.ts` - and here for the same
9
- * reason. `createServerFn` needs the module it sits in to be transformed by the
10
- * Start compiler on both sides of the render, and `@vitnode/core` reaches the
11
- * server un-compiled (see the note in `lib/auth.ts`), so the declaration lives
12
- * in the host and the behaviour lives in the package.
13
- *
14
- * ## Why it has a validator
15
- *
16
- * A server function is a public same-origin endpoint: its input is whatever a
17
- * caller posts, not whatever the dialog typed. This value reaches an API query
18
- * string, so it is parsed rather than trusted - trimmed, length-bounded, and a
19
- * string at all.
20
- *
21
- * The bound is not a security control; the API authorizes the read from the
22
- * admin cookie and pages it itself. It is a cheap refusal of the requests that
23
- * could only ever be abuse - a megabyte of "search term" that the API would
24
- * otherwise have to hand to Postgres.
25
- *
26
- * `POST` puts it behind `createCsrfMiddleware` in `src/start.ts`, which matters
27
- * more here than it looks: this reads other people's names and email addresses,
28
- * and a `GET` would be reachable from another origin's page with the
29
- * administrator's cookies attached.
30
- */
31
- const adminUserSearchInput = z.object({
32
- search: z.string().trim().min(1).max(128),
33
- });
34
-
35
- export const adminUserSearchFn = createServerFn({ method: "POST" })
36
- .validator(adminUserSearchInput)
37
- .handler(async ({ data }) => await readAdminUserSearchOnApi(data.search));
@@ -1,66 +0,0 @@
1
- import { createServerFn } from "@tanstack/react-start";
2
- import {
3
- changePasswordInputSchema,
4
- passwordResetRequestInputSchema,
5
- setAuthTransport,
6
- signInInputSchema,
7
- signOutInputSchema,
8
- signUpInputSchema,
9
- ssoCallbackInputSchema,
10
- ssoStartInputSchema,
11
- } from "@vitnode/core/tanstack/auth";
12
- import {
13
- changePasswordFromResetOnApi,
14
- completeSsoOnApi,
15
- readSessionOnApi,
16
- requestPasswordResetOnApi,
17
- signInOnApi,
18
- signOutOnApi,
19
- signUpOnApi,
20
- startSsoOnApi,
21
- } from "@vitnode/core/tanstack/auth/server";
22
-
23
- export const readSessionFn = createServerFn().handler(
24
- async () => await readSessionOnApi(),
25
- );
26
-
27
- export const signInFn = createServerFn({ method: "POST" })
28
- .validator(signInInputSchema)
29
- .handler(async ({ data }) => await signInOnApi(data));
30
-
31
- export const signOutFn = createServerFn({ method: "POST" })
32
- .validator(signOutInputSchema)
33
- .handler(async ({ data }) => await signOutOnApi(data));
34
-
35
- export const startSsoFn = createServerFn({ method: "POST" })
36
- .validator(ssoStartInputSchema)
37
- .handler(async ({ data }) => await startSsoOnApi(data));
38
-
39
- export const completeSsoFn = createServerFn({ method: "POST" })
40
- .validator(ssoCallbackInputSchema)
41
- .handler(async ({ data }) => await completeSsoOnApi(data));
42
-
43
- export const signUpFn = createServerFn({ method: "POST" })
44
- .validator(signUpInputSchema)
45
- .handler(async ({ data }) => await signUpOnApi(data));
46
-
47
- export const requestPasswordResetFn = createServerFn({ method: "POST" })
48
- .validator(passwordResetRequestInputSchema)
49
- .handler(async ({ data }) => await requestPasswordResetOnApi(data));
50
-
51
- export const changePasswordFromResetFn = createServerFn({ method: "POST" })
52
- .validator(changePasswordInputSchema)
53
- .handler(async ({ data }) => await changePasswordFromResetOnApi(data));
54
-
55
- setAuthTransport({
56
- changePasswordFromReset: async input =>
57
- await changePasswordFromResetFn({ data: input }),
58
- completeSso: async input => await completeSsoFn({ data: input }),
59
- readSession: async () => await readSessionFn(),
60
- requestPasswordReset: async input =>
61
- await requestPasswordResetFn({ data: input }),
62
- signIn: async input => await signInFn({ data: input }),
63
- signOut: async input => await signOutFn({ data: input }),
64
- signUp: async input => await signUpFn({ data: input }),
65
- startSso: async input => await startSsoFn({ data: input }),
66
- });
@@ -1,51 +0,0 @@
1
- import {
2
- buildContentFrontendRegistry,
3
- setContentFrontendRegistry,
4
- } from "@vitnode/core/content";
5
-
6
- import { pluginContentTypes } from "#/content-registry.gen";
7
-
8
- /**
9
- * This installation's Content Engine registry - every content type its
10
- * configured plugins register, with their editing screens attached.
11
- *
12
- * Two lines, and both of them are host-only work by necessity. Which plugins are
13
- * installed is a property of this application, and `src/content-registry.gen.ts`
14
- * is where the build writes the answer: one literal import per configured plugin
15
- * that exports an `admin/content` module. Every *rule* about what a registration
16
- * is - which paths are legal, which two of them collide, how they are ordered,
17
- * how one is found by id or by `admin.path` - is `@vitnode/core`'s, in
18
- * `buildContentFrontendRegistry`. Nothing about the Content Engine is decided
19
- * here.
20
- *
21
- * ## Why it is not read from `vitnode.config.ts`
22
- *
23
- * That config is server-side on purpose (`vitnode.shell.config.ts` explains the
24
- * split): it carries message loaders and API wiring, which a browser bundle has
25
- * no business holding. The generated projection is the browser-safe half - the
26
- * definitions, the icons and the override components - so the AdminCP gets the
27
- * content screens without the server config. The same arrangement
28
- * `src/lib/admin-nav.ts` uses for the sidebar, one layer deeper.
29
- *
30
- * ## Registration, and where it belongs in the import graph
31
- *
32
- * `setContentFrontendRegistry` fills a module-scope slot in `@vitnode/core`, so
33
- * the package's own content code finds the registry without being handed it as
34
- * a prop - the same shape as `setAdminTransport`. Module scope means *per
35
- * bundle*: the browser has one instance and the server has one, and each
36
- * registers its own.
37
- *
38
- * This module is reached through a `() => import(...)` that `/admin/content`'s
39
- * loader awaits, never through a static import, and that is deliberate.
40
- * Registration only has to happen before a content screen runs, and deferring it
41
- * to the route is what lets Rollup put this whole graph - every plugin's field
42
- * components, table cells and form layouts, plus `zod` and the Content Engine
43
- * itself - in that route's chunk instead of in the bundle every page of the site
44
- * loads first. `router.tsx` holds the thunk because that is where the route tree
45
- * is composed; what it does *not* hold is the value. A plugin's own heavier
46
- * parts stay lazier still: `@vitnode/blog` draws a `React.lazy` boundary around
47
- * its Tiptap editor, so even opening the article list does not fetch it.
48
- */
49
- export const contentRegistry = buildContentFrontendRegistry(pluginContentTypes);
50
-
51
- setContentFrontendRegistry(contentRegistry);
@@ -1,128 +0,0 @@
1
- /**
2
- * What a VitNode document response may say about being stored.
3
- *
4
- * `private, no-store`, and it is not a precaution - it is a description of what
5
- * is in the body. Every page this application renders streams a dehydrated Query
6
- * cache into its HTML, and that cache always holds `["vitnode","session"]`: the
7
- * visitor's own name, avatar and `isAdmin` flag. Inside `/admin` it also holds
8
- * `["vitnode","admin-session"]`, which is that administrator's entire permission
9
- * set. `tanstack/auth/session-query` and `tanstack/admin/session-query` both say
10
- * so in their own words - "that document is personalised and must not be served
11
- * from a shared cache" - and until now nothing on the response said it back.
12
- *
13
- * Nothing caches these documents today. That is a property of the current
14
- * deployment, not of the application: the moment a CDN, a reverse proxy or a
15
- * `Cache-Control`-respecting edge sits in front of the Node server, an absent
16
- * directive is an invitation to store one visitor's HTML and serve it to the
17
- * next. Stage 15 is when that becomes likely, so the header belongs here now.
18
- *
19
- * ## It is an invariant, not a default
20
- *
21
- * This started as a fallback for documents that said nothing, and that was
22
- * wrong. A default is something a route may override, and at this architecture
23
- * level there is no override a route could correctly choose: the dehydrated
24
- * cache is written into the stream by `setupRouterSsrQueryIntegration` for
25
- * *every* document, so a route opting into `public, max-age=60` would be
26
- * publishing whichever visitor rendered first to everyone who asked next. The
27
- * route cannot know that, because the private payload is not something the route
28
- * put there.
29
- *
30
- * So the directive is forced rather than filled in, and a route that sets its
31
- * own is overwritten rather than obeyed. That is the whole difference between
32
- * this being a hardening measure and it being a security invariant.
33
- *
34
- * Public document caching is not forbidden forever - it is forbidden *while the
35
- * session is dehydrated into the document*. Introducing it later is a separate,
36
- * explicit piece of architecture in which the private state is kept out of the
37
- * shared body (a public shell fetching its session client-side, say), and the
38
- * invariant here would move with it rather than being quietly relaxed by a route
39
- * that wanted a faster page.
40
- *
41
- * `private` bars a shared cache. `no-store` bars every cache, including the
42
- * browser's own disk cache - which is the half that matters on a shared machine,
43
- * where the previous person's permission set should not be recoverable from
44
- * `chrome://cache` after they sign out. The pair is the standard spelling for
45
- * "this body belongs to exactly one person, once".
46
- *
47
- * The known cost is the back/forward cache. `no-store` used to make a page
48
- * outright ineligible for bfcache in Chromium; current versions keep such pages
49
- * eligible but evict them when cookies change - which for this app means a
50
- * sign-in or sign-out invalidates a back-navigation that would otherwise have
51
- * restored a page rendered for the previous session. That is the correct trade
52
- * and the outcome anybody would want, but it is a real difference and it is
53
- * worth a look during a manual pass rather than a surprise later.
54
- */
55
- export const DOCUMENT_CACHE_CONTROL = "private, no-store";
56
-
57
- /**
58
- * Whether this response is one of the documents the rule above describes.
59
- *
60
- * One question, and it is what keeps the API out of it. `/api/*` is served by
61
- * the Hono bridge through this same middleware, and a bare `GET` from it carries
62
- * no `Cache-Control` of its own - so a rule that applied to every response would
63
- * quietly forbid clients from caching the API. An HTML content-type is the
64
- * honest way to ask "is this a page", it needs no path list to be kept in step
65
- * with the router, and it cannot be wrong about a response that has already been
66
- * produced.
67
- *
68
- * It deliberately does *not* ask whether a directive is already present. That
69
- * used to be the second half of this predicate, and it is exactly the exemption
70
- * the invariant above cannot afford - see {@link applyDocumentCacheControl}.
71
- *
72
- * A redirect is deliberately not matched here - it has no content type - and is
73
- * handled by {@link applyRedirectCacheControl} instead, which wants a narrower
74
- * rule.
75
- */
76
- const isRenderedDocument = (headers: Headers): boolean =>
77
- (headers.get("content-type") ?? "").toLowerCase().startsWith("text/html");
78
-
79
- /**
80
- * Says what a rendered document is, on the response about to be sent.
81
- *
82
- * `set` rather than a conditional fill, and that is the fix: whatever the
83
- * response was carrying is replaced. A route cannot opt out, because a route is
84
- * not in a position to know what is in the body it is opting out for.
85
- *
86
- * Only `text/html` is touched. Everything else the middleware sees - the API,
87
- * assets, client chunks, a `204` with no content type at all - keeps whatever it
88
- * had, including nothing.
89
- *
90
- * Mutates rather than returning a new `Response`, because the middleware already
91
- * holds the one Start produced and rebuilding it would mean copying a stream.
92
- * The same reason `set-cookie` is appended in place a few lines away.
93
- */
94
- export const applyDocumentCacheControl = (response: Response): void => {
95
- if (!isRenderedDocument(response.headers)) return;
96
-
97
- response.headers.set("cache-control", DOCUMENT_CACHE_CONTROL);
98
- };
99
-
100
- /**
101
- * The same for a locale redirect, but only when it is carrying a cookie.
102
- *
103
- * A `308` from `/en/discover` to `/discover` is a fact about URLs, identical for
104
- * every visitor, and permanently cacheable - which is most of the point of
105
- * answering with one. So it keeps that property by default.
106
- *
107
- * The exception is the redirect that also writes the locale cookie, which is
108
- * what `/pl/admin` produces: a stored copy of that would hand the next visitor
109
- * through the same shared cache a `Set-Cookie` chosen by somebody else, and
110
- * quietly switch their language. Shared caches are generally expected to refuse
111
- * a `Set-Cookie` response, but "generally expected" is not a property this
112
- * application can assert about somebody else's proxy, and one visitor's cookie
113
- * reaching another's browser is not the kind of thing to leave to convention.
114
- *
115
- * So the cookie-carrying case is forced, for the same reason the document is: a
116
- * `public` directive already on such a redirect is overwritten rather than
117
- * respected, because the thing that makes it unsafe to share is the `Set-Cookie`
118
- * beside it and not whatever the directive claims.
119
- *
120
- * The cookie-less case keeps its existing semantics untouched, directive and
121
- * all. It carries no private state, so there is nothing here to protect and
122
- * nothing to override.
123
- */
124
- export const applyRedirectCacheControl = (response: Response): void => {
125
- if (!response.headers.has("set-cookie")) return;
126
-
127
- response.headers.set("cache-control", DOCUMENT_CACHE_CONTROL);
128
- };
@@ -1,75 +0,0 @@
1
- import { createServerFn } from "@tanstack/react-start";
2
- import { configureIntl, validateIntlInput } from "@vitnode/core/tanstack/i18n";
3
- import { IntlProvider } from "use-intl";
4
-
5
- import { i18n } from "#/i18n";
6
- import { loadIntlMessages } from "#/server/messages.server";
7
-
8
- /**
9
- * One language's messages for one set of namespaces, fetched on the server.
10
- *
11
- * The one piece of the i18n runtime that cannot live in `@vitnode/core`, and the
12
- * reason is the compiler rather than the code. A server function has to be
13
- * transformed by the Start plugin in *both* bundles; the package is externalised
14
- * from this app's SSR pass, so its modules reach the server un-compiled and a
15
- * `createServerFn` declared there resolves to `undefined` during SSR with no
16
- * error. See `packages/vitnode/src/tanstack/boundary.test.ts`.
17
- *
18
- * So the wrapper is here and the body is not: `validateIntlInput` is core's, and
19
- * `loadIntlMessages` delegates to core's loading engine. Start strips the
20
- * handler - and `#/server/messages.server` with it - out of the client build.
21
- */
22
- export const getIntlMessages = createServerFn()
23
- .validator(validateIntlInput)
24
- .handler(async ({ data }) => await loadIntlMessages(data));
25
-
26
- /**
27
- * This app's languages, handed to the package once.
28
- *
29
- * Everything in `@vitnode/core/tanstack/i18n` reads what this registers, so a
30
- * route file imports `RouteMessages` and `intlQueryOptions` straight from the
31
- * package. What must not happen is a route running before this module has been
32
- * evaluated - so the two framework entry points, `src/router.tsx` and
33
- * `src/start.ts`, both import from here, and `src/tests/intl-runtime.test.ts`
34
- * fails if either stops doing so.
35
- *
36
- * The registration is at module scope but reads `getIntlMessages` above only by
37
- * reference, so the order within this file does not matter: the validator and
38
- * the fetcher are both called per request, long after it has finished
39
- * evaluating.
40
- */
41
- export const {
42
- defaultLocale,
43
- isLocale: isSupportedLocale,
44
- localeRouting,
45
- } = configureIntl({
46
- fetchMessages: async input => await getIntlMessages({ data: input }),
47
- /**
48
- * This app's own `use-intl`, handed over so `RouteMessages` can provide it.
49
- *
50
- * `@vitnode/core` is external to this app's SSR pass (`vite.config.ts`), so
51
- * under `vite dev` Node loads the package and Vite loads this app, and the two
52
- * resolve `use-intl` to two different files - two `createContext` calls, two
53
- * React contexts. Everything the package renders reads its own; anything this
54
- * app renders with its own `useTranslations` - `routes/_main/index.tsx` does -
55
- * reads this one, and nothing inside the package can import it.
56
- *
57
- * So it is registered rather than imported, and `RouteMessages` mounts it
58
- * outermost. A production build resolves `use-intl` once and the providers
59
- * collapse into one, which is why leaving this out is a dev-only failure: the
60
- * route's server render throws "No intl context found", React quietly falls
61
- * back to client rendering, and the page still appears.
62
- */
63
- hostIntlProvider: IntlProvider,
64
- i18n,
65
- });
66
-
67
- /**
68
- * The router's half of locale routing, bound to this app's languages.
69
- *
70
- * Re-exported from here rather than imported straight from the package by
71
- * `src/router.tsx`: it is the router entry's only i18n import, and routing it
72
- * through this module is what makes `configureIntl` above run before the router
73
- * - and therefore before any route, loader or component - exists.
74
- */
75
- export { createLocaleRewrite } from "@vitnode/core/tanstack/i18n";
@@ -1,20 +0,0 @@
1
- import type { i18n } from "#/i18n";
2
-
3
- import { localeRouting } from "#/lib/i18n/runtime";
4
-
5
- /**
6
- * A language this app serves, as a type. `"en" | "pl"`, derived from the config
7
- * rather than written twice.
8
- *
9
- * The one i18n thing this app still owns, and it has to: `@vitnode/core` is
10
- * installed by apps with different language lists, so it types a locale as
11
- * `string` and takes this union as a type argument where the value originates
12
- * (`useLocale<Locale>()`, `resolveLocale<Locale>()`).
13
- */
14
- export type Locale = (typeof i18n.locales)[number]["code"];
15
-
16
- export { defaultLocale, localeRouting } from "#/lib/i18n/runtime";
17
-
18
- /** Narrows a string - a URL segment, a cookie, a `<select>` value - to a locale. */
19
- export const isLocale = (value: null | string | undefined): value is Locale =>
20
- localeRouting.isSupportedLocale(value);
@@ -1,24 +0,0 @@
1
- import { createAuthNavigation } from "@vitnode/core/tanstack/auth";
2
-
3
- import { localeRouting } from "#/lib/i18n/shared";
4
-
5
- /**
6
- * Going somewhere in this application from code, bound to this app's languages.
7
- *
8
- * One line of application, and everything else is
9
- * `@vitnode/core/tanstack/auth`: the two questions a user-supplied target has to
10
- * answer (may we send a browser there, and what does the router want to be
11
- * handed), the reason a redirect carries `to` rather than `href`, and the fact
12
- * that the same decision is made on a server and in a browser.
13
- *
14
- * What is left here is the only thing a package cannot answer - which languages
15
- * this installation serves, which is what decides whether `/pl/discover` is a
16
- * Polish page or a route called `pl`.
17
- *
18
- * `@vitnode/core/tanstack/routes` builds its own from the same factory, handed
19
- * the same `localeRouting`, so core's auth screens and this app's AdminCP command
20
- * palette navigate by one rule rather than two.
21
- */
22
- export const { internalDestination, useAppNavigate } = createAuthNavigation({
23
- localeRouting,
24
- });
@@ -1,19 +0,0 @@
1
- import { createRouteHead } from "@vitnode/core/tanstack/metadata";
2
-
3
- import { vitNodeShellConfig } from "#/vitnode.shell.config";
4
-
5
- /**
6
- * A route's `head`, bound to this app's name.
7
- *
8
- * Two lines of application, and everything else is
9
- * `@vitnode/core/tanstack/metadata`: the `"<page> - <site>"` title rule Next.js
10
- * applies through `title.template`, the decision that a robots directive is
11
- * stated rather than assumed, and the handling of a `loaderData` that is
12
- * `undefined` on a route's first pass.
13
- *
14
- * What is left here is the only thing a package cannot answer - this site's own
15
- * name, which is what every tab title ends with.
16
- *
17
- * head: ({ loaderData }) => pageHead({ robots: 'index, follow', ...loaderData })
18
- */
19
- export const pageHead = createRouteHead(vitNodeShellConfig.metadata);
@@ -1,35 +0,0 @@
1
- import type { VitNodeConfig } from "@vitnode/core/vitnode.config";
2
-
3
- import { i18n } from "./i18n";
4
-
5
- /**
6
- * The VitNode config the browser is allowed to see.
7
- *
8
- * Everything in `VitNodeConfig` except `plugins`, and that omission is the whole
9
- * point. The plugin registry carries each plugin's translations as `import()`s
10
- * of JSON inside its `dist`, and eventually its AdminCP components - neither of
11
- * which a browser bundle should hold. In Next.js the boundary is drawn for you:
12
- * `vitnode.config.ts` is only ever read by Server Components, so none of it
13
- * reaches the client. TanStack Start has no such boundary - anything the root
14
- * route imports is in the browser bundle - so the split is made here instead, by
15
- * hand.
16
- *
17
- * `vitnode.config.ts` spreads this into `buildConfig` with the plugins added, so
18
- * there is one source for the metadata, the theme and the locales rather than
19
- * two that agree until they don't.
20
- *
21
- * Everything here is plain, serializable data. That is a rule, not a
22
- * coincidence: this module is imported by the document shell, which renders on
23
- * both sides of hydration.
24
- */
25
- export const vitNodeShellConfig = {
26
- debug: false,
27
- i18n,
28
- metadata: {
29
- shortTitle: "VitNode",
30
- title: "VitNode",
31
- },
32
- theme: {
33
- defaultTheme: "system",
34
- },
35
- } satisfies Omit<VitNodeConfig, "plugins">;