create-vitnode-app 2.0.0-canary.2 → 2.0.0-canary.4
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/copy-of-vitnode-app/api/src/vitnode.api.config.ts +1 -19
- package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +1 -22
- package/copy-of-vitnode-app/api-single-app/src/server/api-bridge.ts +1 -12
- package/copy-of-vitnode-app/api-single-app/src/vitnode.api.config.ts +1 -9
- package/copy-of-vitnode-app/monorepo/apps/web/.env.example +0 -11
- package/copy-of-vitnode-app/root/.env.example +0 -7
- package/copy-of-vitnode-app/root/global.d.ts +1 -19
- package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +1 -44
- package/copy-of-vitnode-app/root/src/components/main-header.tsx +1 -30
- package/copy-of-vitnode-app/root/src/lib/admin-nav.ts +1 -38
- package/copy-of-vitnode-app/root/src/lib/admin-search.ts +1 -26
- package/copy-of-vitnode-app/root/src/lib/content-registry.ts +1 -42
- package/copy-of-vitnode-app/root/src/lib/i18n/runtime.ts +4 -53
- package/copy-of-vitnode-app/root/src/lib/i18n/shared.ts +1 -9
- package/copy-of-vitnode-app/root/src/lib/navigation.ts +1 -17
- package/copy-of-vitnode-app/root/src/lib/page-head.ts +1 -14
- package/copy-of-vitnode-app/root/src/locales/app.ts +1 -25
- package/copy-of-vitnode-app/root/src/locales/packages.ts +1 -34
- package/copy-of-vitnode-app/root/src/router.tsx +6 -160
- package/copy-of-vitnode-app/root/src/routes/__root.tsx +1 -98
- package/copy-of-vitnode-app/root/src/routes/_admin/admin.core.index.tsx +2 -51
- package/copy-of-vitnode-app/root/src/routes/_admin.tsx +6 -184
- package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +0 -30
- package/copy-of-vitnode-app/root/src/routes/_main.tsx +1 -43
- package/copy-of-vitnode-app/root/src/server/messages.server.ts +0 -13
- package/copy-of-vitnode-app/root/src/start.ts +0 -19
- package/copy-of-vitnode-app/root/src/vitnode.config.ts +0 -56
- package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +0 -11
- package/copy-of-vitnode-app/root/vite.config.ts +0 -84
- package/dist/src/plugin/create/route-templates.js +3 -40
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -7,49 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
import { MainHeader } from "#/components/main-header";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
* The main application shell - the header, the breadcrumb area and the one
|
|
12
|
-
* `<main>` landmark that every public page renders inside.
|
|
13
|
-
*
|
|
14
|
-
* Pathless, so it contributes no URL segment: `/discover` is `/discover`, not
|
|
15
|
-
* `/_main/discover`. A page joins the shell by *where its file lives*, which is
|
|
16
|
-
* the same rule `_authenticated` uses for the session guard - and the reason
|
|
17
|
-
* `_authenticated` lives underneath this one: a signed-in page is still a page
|
|
18
|
-
* on the public site, so it wants the shell *and* the guard rather than a second
|
|
19
|
-
* copy of the shell.
|
|
20
|
-
*
|
|
21
|
-
* ## What is deliberately outside it
|
|
22
|
-
*
|
|
23
|
-
* The four auth screens: `/login`, `/login/sso/$providerId`, `/register` and
|
|
24
|
-
* `/login/reset-password`. An auth screen is a full-height card on an otherwise
|
|
25
|
-
* empty document, and the header it would render is a header whose only
|
|
26
|
-
* interesting control is "sign in". Keeping them out is what makes this a shell
|
|
27
|
-
* that routes opt into rather than one every route is subject to.
|
|
28
|
-
* `routes/api/$` is outside for a different reason: it is a server route and
|
|
29
|
-
* renders no document at all. Note this is a visual difference from the Next.js
|
|
30
|
-
* app, where all four sit inside `(main)` and do render the header.
|
|
31
|
-
*
|
|
32
|
-
* `src/tests/main-shell.test.ts` asserts both halves - the settings paths
|
|
33
|
-
* inside, the four auth screens outside.
|
|
34
|
-
*
|
|
35
|
-
* ## The slots
|
|
36
|
-
*
|
|
37
|
-
* `ThemeLayoutContent`'s, and two of the same three the Next.js `ThemeLayout`
|
|
38
|
-
* fills: `header` and `breadcrumb`.
|
|
39
|
-
*
|
|
40
|
-
* `listeners` is deliberately left empty here. The Next.js app puts the
|
|
41
|
-
* notification toasts and the WebSocket's sign-in resync in it because its
|
|
42
|
-
* `/login` is inside the main shell; this app's is not, so a sync mounted here
|
|
43
|
-
* would not exist during the sign-in it has to notice. They are mounted by
|
|
44
|
-
* `__root` instead, next to the connection whose lifetime they share.
|
|
45
|
-
*
|
|
46
|
-
* ## What it is not
|
|
47
|
-
*
|
|
48
|
-
* A provider. Every technical provider this app has - the QueryClient, the two
|
|
49
|
-
* intl records, the theme, the WebSocket - is mounted once by `__root`, above
|
|
50
|
-
* every route, because a login screen needs them just as much as a page under
|
|
51
|
-
* this shell does. What lives here is structure: markup, and where the slots go.
|
|
52
|
-
*/
|
|
10
|
+
|
|
53
11
|
export const Route = createFileRoute("/_main")({
|
|
54
12
|
loader: async ({ context }) => await loadMainShell(context),
|
|
55
13
|
component: MainLayout,
|
|
@@ -5,17 +5,4 @@ import { vitNodeServerConfig } from "#/vitnode.server.config";
|
|
|
5
5
|
|
|
6
6
|
export type { IntlMessages } from "@vitnode/core/tanstack/i18n/server";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* The messages one page needs, in one language - this app's loader.
|
|
10
|
-
*
|
|
11
|
-
* Everything about *how* messages load is core's: the source order (core, then
|
|
12
|
-
* each plugin, then this app's overrides, later ones winning), the `web` scope
|
|
13
|
-
* stamped on each so the API's tree in this same process cannot be served in its
|
|
14
|
-
* place, the per-key fallback to the default locale, and the namespace pick that
|
|
15
|
-
* keeps every plugin's AdminCP copy out of a page that renders none of it.
|
|
16
|
-
*
|
|
17
|
-
* What is this app's is the server config it is handed - the plugins it
|
|
18
|
-
* registered, the languages it declares, and the loaders that stand in for each
|
|
19
|
-
* package's own locale barrel.
|
|
20
|
-
*/
|
|
21
8
|
export const loadIntlMessages = createIntlMessagesLoader(vitNodeServerConfig);
|
|
@@ -2,23 +2,4 @@ import { createVitNodeStart } from "@vitnode/core/tanstack/start";
|
|
|
2
2
|
|
|
3
3
|
import { vitNodeConfig } from "#/vitnode.config";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* This app's Start instance.
|
|
7
|
-
*
|
|
8
|
-
* The whole request pipeline is VitNode's, and it is mandatory rather than a
|
|
9
|
-
* default: CSRF protection on server functions, canonical locale redirects with
|
|
10
|
-
* the remembered-locale cookie, and the `private, no-store` directive every
|
|
11
|
-
* document carrying a dehydrated session needs.
|
|
12
|
-
*
|
|
13
|
-
* Add this app's own request middleware - a request id, a tracing span, a
|
|
14
|
-
* maintenance gate - with `requestMiddleware`. It runs after all of the above,
|
|
15
|
-
* which is the only safe place for it: a redirect ends the request, so anything
|
|
16
|
-
* in front of the locale rule would run twice for every visitor arriving at a
|
|
17
|
-
* non-canonical URL.
|
|
18
|
-
*
|
|
19
|
-
* export const startInstance = createVitNodeStart({
|
|
20
|
-
* config: vitNodeConfig,
|
|
21
|
-
* requestMiddleware: [myMiddleware],
|
|
22
|
-
* })
|
|
23
|
-
*/
|
|
24
5
|
export const startInstance = createVitNodeStart({ config: vitNodeConfig });
|
|
@@ -1,66 +1,10 @@
|
|
|
1
1
|
import { buildConfig } from "@vitnode/core/vitnode.config";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* This app's configuration - the one file every VitNode app edits first.
|
|
5
|
-
*
|
|
6
|
-
* **Browser-safe, and everything here has to stay that way.** Three very
|
|
7
|
-
* different readers depend on it: `routes/__root.tsx` renders the document shell
|
|
8
|
-
* from `metadata`, `theme` and `debug`; `lib/i18n/runtime.ts` derives the locale
|
|
9
|
-
* routing from `i18n`; and Vite's own plugin registry loads this file with
|
|
10
|
-
* `jiti` while it is still resolving its config, to find out which plugins to
|
|
11
|
-
* generate route, navigation and content-registry imports for. So it is plain
|
|
12
|
-
* data and plugin *identity* - never a `() => import(...)` message loader, never
|
|
13
|
-
* a module that reaches a database. Anything like that goes in
|
|
14
|
-
* `vitnode.server.config.ts`.
|
|
15
|
-
*
|
|
16
|
-
* ## Adding a language
|
|
17
|
-
*
|
|
18
|
-
* Add an entry to `locales`. Packages ship their own translations, so a new
|
|
19
|
-
* locale needs nothing else: anything a package has not translated falls back to
|
|
20
|
-
* `defaultLocale` key by key. Register the package's file for that language in
|
|
21
|
-
* `src/locales/packages.ts`, and put your own rewording in
|
|
22
|
-
* `src/locales/app.ts` - both of which `vitnode.server.config.ts` picks up.
|
|
23
|
-
*
|
|
24
|
-
* `vitnode i18n:create de Deutsch` does all of it for you.
|
|
25
|
-
*
|
|
26
|
-
* ## Adding a plugin
|
|
27
|
-
*
|
|
28
|
-
* With the plugin's own factory:
|
|
29
|
-
*
|
|
30
|
-
* import { blogPlugin } from '@acme/blog/config'
|
|
31
|
-
*
|
|
32
|
-
* plugins: [blogPlugin()]
|
|
33
|
-
*
|
|
34
|
-
* That is the whole registration - the factory carries the plugin's content
|
|
35
|
-
* types, its AdminCP navigation and its translations. Register its locale files
|
|
36
|
-
* in `src/locales/packages.ts` as well, which is what the message loader
|
|
37
|
-
* actually reads.
|
|
38
|
-
*
|
|
39
|
-
* What the AdminCP renders comes back through `src/admin-nav.gen.ts` and
|
|
40
|
-
* `src/content-registry.gen.ts` rather than out of this object: the build writes
|
|
41
|
-
* one literal import per configured plugin, and `src/router.tsx` loads the
|
|
42
|
-
* content registry behind a dynamic `import()`, so a content type's editing
|
|
43
|
-
* screen arrives with the route that renders it.
|
|
44
|
-
*
|
|
45
|
-
* A plugin's *pages* need nothing in this file at all. It declares them in its
|
|
46
|
-
* own `src/routes.ts`, and this app's Vite build compiles them into
|
|
47
|
-
* `src/plugin-routes.gen.ts`, which `src/router.tsx` mounts under the shell the
|
|
48
|
-
* plugin's `area` names. No page is ever copied into `src/routes`.
|
|
49
|
-
*
|
|
50
|
-
* `buildConfig` also registers this object process-wide, which is how core's own
|
|
51
|
-
* route files find it without being handed it as a prop.
|
|
52
|
-
*/
|
|
53
3
|
export const vitNodeConfig = buildConfig({
|
|
54
4
|
debug: false,
|
|
55
5
|
i18n: {
|
|
56
6
|
defaultLocale: "en",
|
|
57
7
|
locales: [{ code: "en", name: "English" }],
|
|
58
|
-
/**
|
|
59
|
-
* Explicit, because the app renders on a server: without one, `use-intl`
|
|
60
|
-
* formats dates in whatever zone the server happens to run in and warns
|
|
61
|
-
* that the client will disagree.
|
|
62
|
-
*/
|
|
63
|
-
timeZone: "UTC",
|
|
64
8
|
},
|
|
65
9
|
metadata: {
|
|
66
10
|
shortTitle: "VitNode",
|
|
@@ -5,17 +5,6 @@ import { appMessages } from "#/locales/app";
|
|
|
5
5
|
import { packageMessages } from "#/locales/packages";
|
|
6
6
|
import { vitNodeConfig } from "#/vitnode.config";
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* The half of this app's configuration a browser may never hold.
|
|
10
|
-
*
|
|
11
|
-
* Both entries are `() => import(...)` loaders that read JSON out of a package's
|
|
12
|
-
* build output, which is exactly what `vitnode.config.ts` cannot carry - so they
|
|
13
|
-
* live here, beside the shared config rather than duplicating any of it. The
|
|
14
|
-
* `server-only` marker makes an accidental import from a component a build error
|
|
15
|
-
* instead of a browser bundle with every plugin's AdminCP copy in it.
|
|
16
|
-
*
|
|
17
|
-
* `src/server/messages.server.ts` is the only reader.
|
|
18
|
-
*/
|
|
19
8
|
export const vitNodeServerConfig = buildServerConfig({
|
|
20
9
|
config: vitNodeConfig,
|
|
21
10
|
messages: appMessages,
|
|
@@ -8,92 +8,8 @@ import { defineConfig } from "vite";
|
|
|
8
8
|
|
|
9
9
|
const config = defineConfig({
|
|
10
10
|
resolve: { tsconfigPaths: true },
|
|
11
|
-
/**
|
|
12
|
-
* A second dev server has to fail rather than quietly move.
|
|
13
|
-
*
|
|
14
|
-
* `tanstackStart()` runs the route generator and *writes*
|
|
15
|
-
* `src/routeTree.gen.ts`. Two servers means two generators owning one file: if
|
|
16
|
-
* their route lists ever differ - which is precisely what happens when one was
|
|
17
|
-
* started before a route file existed - they overwrite each other forever, and
|
|
18
|
-
* every write is a full page reload. Without `strictPort` the second `pnpm dev`
|
|
19
|
-
* says "Port 3000 is in use, trying another one" and succeeds, so the fight
|
|
20
|
-
* starts silently and looks like an inexplicable refresh loop on the first
|
|
21
|
-
* server.
|
|
22
|
-
*/
|
|
23
11
|
server: { strictPort: true },
|
|
24
|
-
ssr: {
|
|
25
|
-
/**
|
|
26
|
-
* The VitNode API packages mounted at `/api/*`, kept out of the SSR pass.
|
|
27
|
-
*
|
|
28
|
-
* They are Node libraries rather than app source: `@vitnode/core` loads its
|
|
29
|
-
* locale files with a runtime `import("./en.json", { with: { type: "json" } })`
|
|
30
|
-
* relative to its own `dist`. Bundling them in this pass moves that chunk
|
|
31
|
-
* and the JSON stops resolving, which fails the build outright. Left
|
|
32
|
-
* external here, Nitro resolves them from the package itself.
|
|
33
|
-
*
|
|
34
|
-
* Every VitNode package the app installs belongs on this list - add a
|
|
35
|
-
* plugin's package name here when you install one.
|
|
36
|
-
*
|
|
37
|
-
* This is also what decides the shape of `@vitnode/core/tanstack/*`, so it is
|
|
38
|
-
* worth naming the consequence rather than leaving it to be rediscovered.
|
|
39
|
-
* Externalised here, the package skips this pass entirely and Nitro's own
|
|
40
|
-
* Rollup run inlines its `dist` afterwards - and nothing in that path runs
|
|
41
|
-
* the TanStack Start compiler. The *client* build has no such gap: it inlines
|
|
42
|
-
* the package, so the compiler transforms it there. So package code reaches
|
|
43
|
-
* the browser compiled and the server un-compiled, which is exactly why the
|
|
44
|
-
* package may declare `createIsomorphicFn` (its stub falls back to the
|
|
45
|
-
* `.server()` branch, which is what a server wants) and may never declare
|
|
46
|
-
* `createServerFn` (un-compiled, its handler resolves to `undefined` with no
|
|
47
|
-
* error at all). `packages/vitnode/src/tanstack/boundary.test.ts` holds the
|
|
48
|
-
* package to that.
|
|
49
|
-
*
|
|
50
|
-
* Removing `@vitnode/core` from this list to close the gap was measured and
|
|
51
|
-
* does not work: the SSR pass then reaches the locale barrel above and the
|
|
52
|
-
* build fails on `Could not resolve './en.json'`.
|
|
53
|
-
*
|
|
54
|
-
* ## `tslib` is here for an entirely different reason
|
|
55
|
-
*
|
|
56
|
-
* It is not a VitNode package and it is not a Node library. It is here
|
|
57
|
-
* because bundling it is broken, in a way that only appears in a production
|
|
58
|
-
* build and only once a page renders.
|
|
59
|
-
*
|
|
60
|
-
* The Radix scroll-lock stack that every modal in a VitNode page pulls in -
|
|
61
|
-
* `react-remove-scroll`, `use-sidecar`, `aria-hidden` - does
|
|
62
|
-
* `import { __extends } from "tslib"`. Under the `node` condition that
|
|
63
|
-
* resolves to `tslib/modules/index.js`, a two-line ESM shim around the
|
|
64
|
-
* **CommonJS** `tslib.js`:
|
|
65
|
-
*
|
|
66
|
-
* import tslib from "../tslib.js";
|
|
67
|
-
* const { __extends, … } = tslib;
|
|
68
|
-
*
|
|
69
|
-
* `tslib.js` defines `__esModule` on its exports, so Rolldown's CJS interop
|
|
70
|
-
* helper does not synthesise a `default` - while the call site it generated
|
|
71
|
-
* still reads `.default`. Every page that renders a dialog then throws
|
|
72
|
-
* `Cannot destructure property '__extends' of undefined`.
|
|
73
|
-
*
|
|
74
|
-
* Externalised, the file is never bundled: Nitro traces the package into the
|
|
75
|
-
* output and Node loads it with Node's own interop, which is correct. An
|
|
76
|
-
* alias to `tslib/tslib.es6.mjs` was tried first and is worse - it fixes
|
|
77
|
-
* Vite's copy and not Nitro's, because Nitro re-resolves externals itself,
|
|
78
|
-
* and the tracer then copies only the file the alias named.
|
|
79
|
-
*/
|
|
80
|
-
external: ["@vitnode/core", "tslib"],
|
|
81
|
-
},
|
|
82
12
|
plugins: [
|
|
83
|
-
/**
|
|
84
|
-
* Environment handling, the dev server's dependency pre-bundling and the
|
|
85
|
-
* plugin route registry, in the order they have to run.
|
|
86
|
-
*
|
|
87
|
-
* `appRoot` is `import.meta.dirname` because a Vite config is loaded with
|
|
88
|
-
* the working directory set to wherever the command ran, which in a
|
|
89
|
-
* monorepo is regularly the repository root.
|
|
90
|
-
*
|
|
91
|
-
* `clientEnv` is not passed: this app publishes nothing to the browser
|
|
92
|
-
* beyond the two keys VitNode inlines for every install
|
|
93
|
-
* (`NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_WEB_URL`). Anything named there is
|
|
94
|
-
* compiled into JavaScript anyone can read, so a key is added only when
|
|
95
|
-
* something in the browser genuinely reads it.
|
|
96
|
-
*/
|
|
97
13
|
vitnode({ appRoot: import.meta.dirname }),
|
|
98
14
|
devtools(),
|
|
99
15
|
nitro({ rollupConfig: { external: [/^@sentry\//] } }),
|
|
@@ -31,30 +31,7 @@ export const routes = definePluginRoutes([
|
|
|
31
31
|
};
|
|
32
32
|
export const pluginRouteModuleTemplate = (pluginName) => `import { useTranslations } from "use-intl";
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
* The page \`routes.ts\` declares.
|
|
36
|
-
*
|
|
37
|
-
* Keep it framework-neutral. This module is compiled into the package's own
|
|
38
|
-
* \`dist\` and imported by whichever app installed the plugin, so anything from
|
|
39
|
-
* a router or a host-bound i18n package pins the plugin to one kind of host.
|
|
40
|
-
* \`use-intl\` - which is what VitNode itself renders through - and plain JSX are
|
|
41
|
-
* pinned to neither.
|
|
42
|
-
*
|
|
43
|
-
* No \`<main>\`: the application shell owns the document's one \`main\` landmark,
|
|
44
|
-
* and a page that renders a second gives a screen reader two to choose between.
|
|
45
|
-
* A page owns its container - width, padding, vertical rhythm - and nothing
|
|
46
|
-
* above it.
|
|
47
|
-
*
|
|
48
|
-
* To give this route a loader, page metadata or a breadcrumb, add a \`route\`
|
|
49
|
-
* export beside the default one:
|
|
50
|
-
*
|
|
51
|
-
* import { definePluginRoute } from "@vitnode/core/routing";
|
|
52
|
-
*
|
|
53
|
-
* export const route = definePluginRoute({
|
|
54
|
-
* load: ({ context, params }) => fetchThing(context.locale, params.id),
|
|
55
|
-
* head: ({ loaderData }) => ({ title: loaderData?.title }),
|
|
56
|
-
* });
|
|
57
|
-
*/
|
|
34
|
+
|
|
58
35
|
const HomePage = () => {
|
|
59
36
|
const t = useTranslations("${pluginName}");
|
|
60
37
|
|
|
@@ -83,11 +60,7 @@ export const pluginMessagesTemplate = (pluginName) => `${JSON.stringify({
|
|
|
83
60
|
}, null, 2)}\n`;
|
|
84
61
|
export const pluginMessagesBarrelTemplate = () => `import type { LocaleMessagesMap } from "@vitnode/core/lib/i18n/types";
|
|
85
62
|
|
|
86
|
-
|
|
87
|
-
* Every language this plugin ships. Add a file next to this one and a line here
|
|
88
|
-
* to add another; apps pick it up with no copy step, because they read this
|
|
89
|
-
* package's own \`dist\` rather than a copy of it.
|
|
90
|
-
*/
|
|
63
|
+
|
|
91
64
|
const messages: LocaleMessagesMap = {
|
|
92
65
|
en: async () => await import("./en.json", { with: { type: "json" } }),
|
|
93
66
|
};
|
|
@@ -109,17 +82,7 @@ export const pluginConfigTemplate = (pluginName) => `import { buildPlugin } from
|
|
|
109
82
|
import messages from "./locales";
|
|
110
83
|
import { routes } from "./routes";
|
|
111
84
|
|
|
112
|
-
|
|
113
|
-
* This plugin, as an application registers it.
|
|
114
|
-
*
|
|
115
|
-
* \`pluginId\` is the package name, and that is not a convention - it is how this
|
|
116
|
-
* plugin's route tree is imported (\`${pluginName}/routes\`) and how its messages
|
|
117
|
-
* are namespaced. The two cannot drift because they are one string.
|
|
118
|
-
*
|
|
119
|
-
* Add this to an app's \`src/vitnode.config.ts\` \`plugins\` array. A plugin that
|
|
120
|
-
* is installed but not listed there contributes nothing - no directory is ever
|
|
121
|
-
* scanned - so this is the only switch.
|
|
122
|
-
*/
|
|
85
|
+
|
|
123
86
|
export const ${pluginVariableName(pluginName)} = () =>
|
|
124
87
|
buildPlugin({
|
|
125
88
|
pluginId: "${pluginName}",
|