create-vitnode-app 2.0.0-canary.7 → 2.0.0-canary.9

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 (33) hide show
  1. package/README.md +1 -0
  2. package/copy-of-vitnode-app/api-single-app/src/routes/api/$.ts +4 -5
  3. package/copy-of-vitnode-app/api-single-app/src/server/vitnode-api.server.ts +13 -13
  4. package/copy-of-vitnode-app/root/src/components/admin-shell.tsx +2 -10
  5. package/copy-of-vitnode-app/root/src/components/main-footer.tsx +44 -0
  6. package/copy-of-vitnode-app/root/src/lib/i18n.ts +37 -0
  7. package/copy-of-vitnode-app/root/src/router.tsx +4 -6
  8. package/copy-of-vitnode-app/root/src/routes/__root.tsx +6 -5
  9. package/copy-of-vitnode-app/root/src/routes/_admin.tsx +3 -3
  10. package/copy-of-vitnode-app/root/src/routes/_main/index.tsx +180 -14
  11. package/copy-of-vitnode-app/root/src/routes/_main.tsx +12 -5
  12. package/copy-of-vitnode-app/root/src/server/messages.server.ts +1 -1
  13. package/copy-of-vitnode-app/root/src/start.ts +1 -1
  14. package/copy-of-vitnode-app/root/src/styles.css +4 -0
  15. package/copy-of-vitnode-app/root/src/vitnode.config.ts +3 -0
  16. package/copy-of-vitnode-app/root/src/vitnode.server.config.ts +3 -3
  17. package/copy-of-vitnode-app/root/tsconfig.json +8 -3
  18. package/dist/src/create/create-vitnode.js +15 -1
  19. package/dist/src/create/package-versions.js +1 -1
  20. package/dist/src/helpers/init-git.js +38 -0
  21. package/dist/src/index.js +1 -0
  22. package/dist/src/questions.js +7 -0
  23. package/dist/tsconfig.build.tsbuildinfo +1 -1
  24. package/package.json +2 -2
  25. package/copy-of-vitnode-app/root/src/lib/admin-auth.ts +0 -11
  26. package/copy-of-vitnode-app/root/src/lib/admin-nav.ts +0 -6
  27. package/copy-of-vitnode-app/root/src/lib/admin-search.ts +0 -12
  28. package/copy-of-vitnode-app/root/src/lib/auth.ts +0 -73
  29. package/copy-of-vitnode-app/root/src/lib/content-registry.ts +0 -11
  30. package/copy-of-vitnode-app/root/src/lib/i18n/runtime.ts +0 -26
  31. package/copy-of-vitnode-app/root/src/lib/i18n/shared.ts +0 -12
  32. package/copy-of-vitnode-app/root/src/lib/navigation.ts +0 -8
  33. package/copy-of-vitnode-app/root/src/lib/page-head.ts +0 -6
package/README.md CHANGED
@@ -75,6 +75,7 @@ plugins: [myPlugin()]
75
75
  | `--package-manager` | Choose `npm` or `pnpm` for the generated project. |
76
76
  | `--eslint` | Include ESLint and Prettier configuration. |
77
77
  | `--skip-install` | Skip dependency installation after scaffolding. |
78
+ | `--skip-git` | Skip initializing a git repository (created on `main` after install). |
78
79
  | `--mode` | Choose `singleApp`, `apiMonorepo`, or `onlyApi`. |
79
80
  | `--monorepo` | Create a workspace layout for plugins and multiple applications. |
80
81
  | `--docker` | Include local Docker services. |
@@ -1,11 +1,10 @@
1
- import { createFileRoute } from '@tanstack/react-router'
1
+ import { createFileRoute } from "@tanstack/react-router";
2
2
 
3
- import { apiBridge } from '#/server/vitnode-api.server'
3
+ import { apiBridge } from "@/server/vitnode-api.server";
4
4
 
5
-
6
- export const Route = createFileRoute('/api/$')({
5
+ export const Route = createFileRoute("/api/$")({
7
6
  server: {
8
7
  handlers: ({ createHandlers }) =>
9
8
  createHandlers({ ANY: async ({ request }) => apiBridge(request) }),
10
9
  },
11
- })
10
+ });
@@ -1,29 +1,29 @@
1
- import '@tanstack/react-start/server-only'
2
- import { OpenAPIHono } from '@hono/zod-openapi'
3
- import { VitNodeAPI } from '@vitnode/core/api/config'
1
+ import "@tanstack/react-start/server-only";
2
+ import { OpenAPIHono } from "@hono/zod-openapi";
3
+ import { VitNodeAPI } from "@vitnode/core/api/config";
4
4
 
5
- import { createApiBridge } from '#/server/api-bridge'
6
- import { vitNodeApiConfig } from '#/vitnode.api.config'
5
+ import { createApiBridge } from "@/server/api-bridge";
6
+ import { vitNodeApiConfig } from "@/vitnode.api.config";
7
7
 
8
8
  // The same two lines `apps/api` and `apps/docs` run. `basePath("/api")` is what
9
9
  // makes the mount point part of the API's own routing, so every path the plugins
10
10
  // register - `/api/@vitnode/core/...` - resolves identically here.
11
11
  const createVitNodeApi = () => {
12
- const app = new OpenAPIHono().basePath('/api')
12
+ const app = new OpenAPIHono().basePath("/api");
13
13
 
14
- VitNodeAPI({ app, vitNodeApiConfig })
14
+ VitNodeAPI({ app, vitNodeApiConfig });
15
15
 
16
- return app
17
- }
16
+ return app;
17
+ };
18
18
 
19
19
  // `VitNodeAPI` is a boot step, not a request step: it opens the Redis client,
20
20
  // starts the cron scheduler and registers every plugin route. Vite re-evaluates
21
21
  // server modules on HMR, so the instance is parked on `globalThis` to keep one
22
22
  // API per process instead of one per edit.
23
23
  const cache = globalThis as typeof globalThis & {
24
- __vitnodeApi?: ReturnType<typeof createVitNodeApi>
25
- }
24
+ __vitnodeApi?: ReturnType<typeof createVitNodeApi>;
25
+ };
26
26
 
27
- export const vitNodeApi = (cache.__vitnodeApi ??= createVitNodeApi())
27
+ export const vitNodeApi = (cache.__vitnodeApi ??= createVitNodeApi());
28
28
 
29
- export const apiBridge = createApiBridge(vitNodeApi)
29
+ export const apiBridge = createApiBridge(vitNodeApi);
@@ -1,15 +1,8 @@
1
- import type { AdminUserSearch } from "@vitnode/core/tanstack/admin";
2
-
3
1
  import { AdminShellContent } from "@vitnode/core/tanstack/admin";
2
+ import { useAppNavigate } from "@vitnode/core/tanstack/auth";
4
3
  import { LanguageSwitcher } from "@vitnode/core/tanstack/layout";
5
4
 
6
- import { adminNav } from "#/lib/admin-nav";
7
- import { adminUserSearchFn } from "#/lib/admin-search";
8
- import { useAppNavigate } from "#/lib/navigation";
9
-
10
-
11
- const searchUsers: AdminUserSearch = async search =>
12
- await adminUserSearchFn({ data: { search } });
5
+ import { adminNav } from "@/admin-nav.gen";
13
6
 
14
7
  export const AdminShell = ({ children }: { children: React.ReactNode }) => {
15
8
  const navigate = useAppNavigate();
@@ -21,7 +14,6 @@ export const AdminShell = ({ children }: { children: React.ReactNode }) => {
21
14
  onNavigate={href => {
22
15
  void navigate(href);
23
16
  }}
24
- searchUsers={searchUsers}
25
17
  >
26
18
  {children}
27
19
  </AdminShellContent>
@@ -0,0 +1,44 @@
1
+ import {
2
+ VITNODE_DOCS_URL,
3
+ VITNODE_SPONSOR_URL,
4
+ VITNODE_WEBSITE_URL,
5
+ } from "@vitnode/core/lib/docs-links";
6
+ import { Heart } from "lucide-react";
7
+
8
+ export const MainFooter = () => (
9
+ <footer className="border-t">
10
+ <div className="text-muted-foreground container mx-auto flex flex-col items-center justify-between gap-3 px-4 py-6 text-sm sm:flex-row">
11
+ <p>
12
+ Powered by{" "}
13
+ <a
14
+ className="text-foreground font-medium underline-offset-4 hover:underline"
15
+ href={VITNODE_WEBSITE_URL}
16
+ rel="noopener noreferrer"
17
+ target="_blank"
18
+ >
19
+ VitNode
20
+ </a>
21
+ </p>
22
+
23
+ <nav aria-label="VitNode" className="flex items-center gap-5">
24
+ <a
25
+ className="hover:text-foreground transition-colors"
26
+ href={VITNODE_DOCS_URL}
27
+ rel="noopener noreferrer"
28
+ target="_blank"
29
+ >
30
+ Docs
31
+ </a>
32
+ <a
33
+ className="hover:text-foreground flex items-center gap-1.5 transition-colors"
34
+ href={VITNODE_SPONSOR_URL}
35
+ rel="noopener noreferrer"
36
+ target="_blank"
37
+ >
38
+ <Heart className="size-3.5" />
39
+ Sponsor
40
+ </a>
41
+ </nav>
42
+ </div>
43
+ </footer>
44
+ );
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The one bootstrap adapter this application still owns.
3
+ *
4
+ * Auth, the AdminCP session and the AdminCP user search all reach the API
5
+ * through `@vitnode/core`'s own default transports now, so none of them needs a
6
+ * module here. Messages cannot work that way, for two reasons that only hold
7
+ * in app source:
8
+ *
9
+ * - `loadIntlMessages` reads *this* application's locale barrels, which are the
10
+ * app's own files plus whichever packages it installed. A package cannot know
11
+ * them.
12
+ * - `createServerFn` is extracted by the TanStack Start compiler, and the
13
+ * compiler only runs over the application's source. `@vitnode/core` ships
14
+ * precompiled, so a server function declared there would never have its
15
+ * handler extracted and would resolve to `undefined` at runtime.
16
+ *
17
+ * `configureIntl` is where the two meet: the app hands core the fetch and its
18
+ * configured languages, and everything else in the package reads them back
19
+ * through `getIntlRuntime()`.
20
+ */
21
+ import { createServerFn } from "@tanstack/react-start";
22
+ import { configureIntl, validateIntlInput } from "@vitnode/core/tanstack/i18n";
23
+ import { IntlProvider } from "use-intl";
24
+
25
+ import { loadIntlMessages } from "@/server/messages.server";
26
+ import { vitNodeConfig } from "@/vitnode.config";
27
+
28
+ export const getIntlMessages = createServerFn()
29
+ .validator(validateIntlInput)
30
+ .handler(async ({ data }) => await loadIntlMessages(data));
31
+
32
+ export const { localeRouting } = configureIntl({
33
+ fetchMessages: async input => await getIntlMessages({ data: input }),
34
+
35
+ hostIntlProvider: IntlProvider,
36
+ i18n: vitNodeConfig.i18n,
37
+ });
@@ -3,6 +3,8 @@ import type { AnyRouter } from "@tanstack/react-router";
3
3
  import { createRouter as createTanStackRouter } from "@tanstack/react-router";
4
4
  import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
5
5
  import { createVitNodeQueryClient } from "@vitnode/core/lib/query-client";
6
+ import { createLocaleRewrite } from "@vitnode/core/tanstack/i18n";
7
+ import { pageHead } from "@vitnode/core/tanstack/metadata";
6
8
  import { RoutePendingSpinner } from "@vitnode/core/tanstack/pending";
7
9
  import {
8
10
  pluginRouteSpecs,
@@ -14,18 +16,14 @@ import {
14
16
  withCoreRootRoutes,
15
17
  } from "@vitnode/core/tanstack/routes";
16
18
 
17
- import "./lib/auth";
18
-
19
- import "./lib/admin-auth";
20
- import { createLocaleRewrite, localeRouting } from "./lib/i18n/runtime";
21
- import { pageHead } from "./lib/page-head";
19
+ import { localeRouting } from "./lib/i18n";
22
20
  import { pluginRouteSources } from "./plugin-routes.gen";
23
21
  import { Route as adminShellRoute } from "./routes/_admin";
24
22
  import { Route as mainShellRoute } from "./routes/_main";
25
23
  import { routeTree as fileRouteTree } from "./routeTree.gen";
26
24
 
27
25
  const loadContentRegistry = async () =>
28
- (await import("./lib/content-registry")).contentRegistry;
26
+ (await import("./content-registry.gen")).contentRegistry;
29
27
 
30
28
  const routeTree = withCoreRootRoutes(
31
29
  withCoreAdminRoutes(
@@ -18,9 +18,9 @@ import {
18
18
  } from "@vitnode/core/tanstack/i18n";
19
19
  import { VitNodeRootProviders } from "@vitnode/core/tanstack/layout";
20
20
 
21
- import type { Locale } from "#/lib/i18n/shared";
21
+ import type { Locale } from "@/vitnode.config";
22
22
 
23
- import { vitNodeConfig } from "#/vitnode.config";
23
+ import { vitNodeConfig } from "@/vitnode.config";
24
24
 
25
25
  import appCss from "../styles.css?url";
26
26
 
@@ -53,9 +53,10 @@ export const Route = createRootRouteWithContext<RootRouterContext>()({
53
53
  ],
54
54
  }),
55
55
  loader: async ({ context }) => {
56
- await context.queryClient.ensureQueryData(
57
- intlQueryOptions({ locale: context.locale }),
58
- );
56
+ await context.queryClient.query({
57
+ ...intlQueryOptions({ locale: context.locale }),
58
+ staleTime: "static",
59
+ });
59
60
  },
60
61
  shellComponent: RootDocument,
61
62
  });
@@ -7,9 +7,9 @@ import {
7
7
  loadAdminMessages,
8
8
  preloadAdminAccess,
9
9
  } from "@vitnode/core/tanstack/admin";
10
+ import { pageHead } from "@vitnode/core/tanstack/metadata";
10
11
 
11
- import { AdminShell } from "#/components/admin-shell";
12
- import { pageHead } from "#/lib/page-head";
12
+ import { AdminShell } from "@/components/admin-shell";
13
13
 
14
14
  export const Route = createFileRoute("/_admin")({
15
15
  beforeLoad: async ({ context, location, preload }) => {
@@ -47,7 +47,7 @@ export const Route = createFileRoute("/_admin")({
47
47
  },
48
48
 
49
49
  loader: async ({ context }) => {
50
- const { adminNav } = await import("#/lib/admin-nav");
50
+ const { adminNav } = await import("@/admin-nav.gen");
51
51
 
52
52
  await loadAdminMessages({ ...context, namespaces: adminNav.namespaces });
53
53
  },
@@ -1,30 +1,196 @@
1
- import { createFileRoute } from "@tanstack/react-router";
1
+ import type { LucideIcon } from "lucide-react";
2
2
 
3
- import { pageHead } from "#/lib/page-head";
3
+ import type { RegisteredRouter } from "@tanstack/react-router";
4
+
5
+ import { createFileRoute, Link } from "@tanstack/react-router";
6
+ import { LogoVitNode } from "@vitnode/core/components/logo-vitnode";
7
+ import { buttonVariants } from "@vitnode/core/components/ui/button";
8
+ import {
9
+ VITNODE_DOCS_URL,
10
+ VITNODE_WEBSITE_URL,
11
+ } from "@vitnode/core/lib/docs-links";
12
+ import { useSessionQuery } from "@vitnode/core/tanstack/auth";
13
+ import { pageHead } from "@vitnode/core/tanstack/metadata";
14
+ import { cn } from "cn";
15
+ import {
16
+ ArrowUpRight,
17
+ BookOpen,
18
+ Globe,
19
+ ShieldCheck,
20
+ UserRoundPlus,
21
+ } from "lucide-react";
22
+
23
+ const REGISTER_HREF = "/register";
24
+ const ADMIN_HREF = "/admin";
4
25
 
5
26
  export const Route = createFileRoute("/_main/")({
6
27
  head: () =>
7
28
  pageHead({
8
- description: "A VitNode application.",
29
+ description: "Your VitNode app is up and running.",
9
30
  robots: "index, follow",
10
- title: "Home",
31
+ title: "Welcome",
11
32
  }),
12
33
  component: HomeRoute,
13
34
  });
14
35
 
36
+ interface NextStep {
37
+ description: string;
38
+ external?: boolean;
39
+ href: string;
40
+ icon: LucideIcon;
41
+ title: string;
42
+ }
43
+
44
+ const registerStep: NextStep = {
45
+ description:
46
+ "Create a member account. On a fresh install the first one becomes the administrator.",
47
+ href: REGISTER_HREF,
48
+ icon: UserRoundPlus,
49
+ title: "Register",
50
+ };
51
+
52
+ const sharedSteps: NextStep[] = [
53
+ {
54
+ description: "Settings, members, roles, plugins and content live here.",
55
+ href: ADMIN_HREF,
56
+ icon: ShieldCheck,
57
+ title: "AdminCP",
58
+ },
59
+ {
60
+ description: "Configuration, routing, plugins and deployment guides.",
61
+ external: true,
62
+ href: VITNODE_DOCS_URL,
63
+ icon: BookOpen,
64
+ title: "Documentation",
65
+ },
66
+ {
67
+ description: "Project website, release notes and community links.",
68
+ external: true,
69
+ href: VITNODE_WEBSITE_URL,
70
+ icon: Globe,
71
+ title: "vitnode.com",
72
+ },
73
+ ];
74
+
15
75
  function HomeRoute() {
76
+ const { data } = useSessionQuery();
77
+ const user = data?.user ?? null;
78
+ const steps = user ? sharedSteps : [registerStep, ...sharedSteps];
79
+
16
80
  return (
17
- <div className="container mx-auto flex max-w-2xl flex-col gap-4 p-4">
18
- <h1 className="text-3xl font-semibold tracking-tight text-balance">
19
- Welcome to VitNode
20
- </h1>
21
-
22
- <p className="text-muted-foreground leading-relaxed text-pretty">
23
- Edit <code>src/routes/_main/index.tsx</code> to change this page. The
24
- header, the footer and the shell around it are{" "}
25
- <code>@vitnode/core</code>&rsquo;s; everything under{" "}
26
- <code>src/routes</code> is yours.
81
+ <div className="container mx-auto flex max-w-3xl flex-col gap-14 px-4 py-16 sm:py-24">
82
+ <section className="flex flex-col items-center gap-6 text-center">
83
+ <LogoVitNode className="size-14" idPrefix="welcome-logo" small />
84
+
85
+ <div className="flex flex-col gap-3">
86
+ <h1 className="text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
87
+ Your VitNode app is running
88
+ </h1>
89
+
90
+ <p className="text-muted-foreground mx-auto max-w-xl text-lg leading-relaxed text-pretty">
91
+ {user
92
+ ? `You are signed in as ${user.name}. Open the AdminCP to configure the site.`
93
+ : "Create an account or sign in, then open the AdminCP to configure the site. On a fresh install the first registered member becomes the administrator."}
94
+ </p>
95
+ </div>
96
+
97
+ <div className="flex flex-col gap-3 sm:flex-row">
98
+ {user ? null : (
99
+ <Link
100
+ className={cn(buttonVariants({ size: "lg" }), "px-5")}
101
+ to={REGISTER_HREF}
102
+ >
103
+ <UserRoundPlus />
104
+ Create an account
105
+ </Link>
106
+ )}
107
+
108
+ <Link
109
+ className={cn(
110
+ buttonVariants({
111
+ size: "lg",
112
+ variant: user ? "default" : "outline",
113
+ }),
114
+ "px-5",
115
+ )}
116
+ to={ADMIN_HREF}
117
+ >
118
+ <ShieldCheck />
119
+ Open AdminCP
120
+ </Link>
121
+ </div>
122
+ </section>
123
+
124
+ <section className="flex flex-col gap-4">
125
+ <h2 className="text-muted-foreground text-xs font-medium tracking-wider uppercase">
126
+ Next steps
127
+ </h2>
128
+
129
+ <div
130
+ className={cn(
131
+ "grid gap-3",
132
+ steps.length === 3 ? "sm:grid-cols-3" : "sm:grid-cols-2",
133
+ )}
134
+ >
135
+ {steps.map(step => (
136
+ <NextStepCard key={step.href} {...step} />
137
+ ))}
138
+ </div>
139
+ </section>
140
+
141
+ <p className="text-muted-foreground text-center text-sm text-pretty">
142
+ This page lives in <code>src/routes/_main/index.tsx</code>. Replace it
143
+ whenever you are ready.
27
144
  </p>
28
145
  </div>
29
146
  );
30
147
  }
148
+
149
+ function NextStepCard({
150
+ description,
151
+ external = false,
152
+ href,
153
+ icon: Icon,
154
+ title,
155
+ }: NextStep) {
156
+ const className =
157
+ "group bg-card hover:bg-muted/50 focus-visible:border-ring focus-visible:ring-ring/50 flex flex-col gap-3 rounded-xl border p-5 transition-colors outline-none focus-visible:ring-3";
158
+
159
+ const content = (
160
+ <>
161
+ <div className="flex items-center justify-between">
162
+ <span className="bg-primary/10 text-primary flex size-9 items-center justify-center rounded-lg">
163
+ <Icon className="size-4.5" />
164
+ </span>
165
+
166
+ <ArrowUpRight className="text-muted-foreground size-4 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
167
+ </div>
168
+
169
+ <div className="flex flex-col gap-1">
170
+ <span className="font-medium">{title}</span>
171
+ <span className="text-muted-foreground text-sm leading-relaxed text-pretty">
172
+ {description}
173
+ </span>
174
+ </div>
175
+ </>
176
+ );
177
+
178
+ if (external) {
179
+ return (
180
+ <a
181
+ className={className}
182
+ href={href}
183
+ rel="noopener noreferrer"
184
+ target="_blank"
185
+ >
186
+ {content}
187
+ </a>
188
+ );
189
+ }
190
+
191
+ return (
192
+ <Link<RegisteredRouter, string, string> className={className} href={href}>
193
+ {content}
194
+ </Link>
195
+ );
196
+ }
@@ -5,8 +5,8 @@ import {
5
5
  ThemeLayoutContent,
6
6
  } from "@vitnode/core/tanstack/layout";
7
7
 
8
- import { MainHeader } from "#/components/main-header";
9
-
8
+ import { MainFooter } from "@/components/main-footer";
9
+ import { MainHeader } from "@/components/main-header";
10
10
 
11
11
  export const Route = createFileRoute("/_main")({
12
12
  loader: async ({ context }) => await loadMainShell(context),
@@ -15,8 +15,15 @@ export const Route = createFileRoute("/_main")({
15
15
 
16
16
  function MainLayout() {
17
17
  return (
18
- <ThemeLayoutContent breadcrumb={<MainBreadcrumb />} header={<MainHeader />}>
19
- <Outlet />
20
- </ThemeLayoutContent>
18
+ <div className="flex min-h-svh flex-col [&>main]:flex-1">
19
+ <ThemeLayoutContent
20
+ breadcrumb={<MainBreadcrumb />}
21
+ header={<MainHeader />}
22
+ >
23
+ <Outlet />
24
+ </ThemeLayoutContent>
25
+
26
+ <MainFooter />
27
+ </div>
21
28
  );
22
29
  }
@@ -1,7 +1,7 @@
1
1
  import "@tanstack/react-start/server-only";
2
2
  import { createIntlMessagesLoader } from "@vitnode/core/tanstack/i18n/server";
3
3
 
4
- import { vitNodeServerConfig } from "#/vitnode.server.config";
4
+ import { vitNodeServerConfig } from "@/vitnode.server.config";
5
5
 
6
6
  export type { IntlMessages } from "@vitnode/core/tanstack/i18n/server";
7
7
 
@@ -1,5 +1,5 @@
1
1
  import { createVitNodeStart } from "@vitnode/core/tanstack/start";
2
2
 
3
- import { vitNodeConfig } from "#/vitnode.config";
3
+ import { vitNodeConfig } from "@/vitnode.config";
4
4
 
5
5
  export const startInstance = createVitNodeStart({ config: vitNodeConfig });
@@ -86,6 +86,10 @@
86
86
  }
87
87
 
88
88
  @theme inline {
89
+ --font-sans:
90
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
91
+ "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
92
+ "Segoe UI Symbol", "Noto Color Emoji";
89
93
  --font-heading: var(--font-sans);
90
94
  --color-sidebar-ring: var(--sidebar-ring);
91
95
  --color-sidebar-border: var(--sidebar-border);
@@ -5,6 +5,7 @@ export const vitNodeConfig = buildConfig({
5
5
  i18n: {
6
6
  defaultLocale: "en",
7
7
  locales: [{ code: "en", name: "English" }],
8
+ timeZone: "UTC",
8
9
  },
9
10
  metadata: {
10
11
  shortTitle: "VitNode",
@@ -15,3 +16,5 @@ export const vitNodeConfig = buildConfig({
15
16
  defaultTheme: "system",
16
17
  },
17
18
  });
19
+
20
+ export type Locale = (typeof vitNodeConfig.i18n.locales)[number]["code"];
@@ -1,9 +1,9 @@
1
1
  import "@tanstack/react-start/server-only";
2
2
  import { buildServerConfig } from "@vitnode/core/vitnode.config";
3
3
 
4
- import { appMessages } from "#/locales/app";
5
- import { packageMessages } from "#/locales/packages";
6
- import { vitNodeConfig } from "#/vitnode.config";
4
+ import { appMessages } from "@/locales/app";
5
+ import { packageMessages } from "@/locales/packages";
6
+ import { vitNodeConfig } from "@/vitnode.config";
7
7
 
8
8
  export const vitNodeServerConfig = buildServerConfig({
9
9
  config: vitNodeConfig,
@@ -1,12 +1,17 @@
1
1
  {
2
- "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"],
3
-
2
+ "include": [
3
+ "**/*.ts",
4
+ "**/*.tsx",
5
+ "eslint.config.js",
6
+ "prettier.config.js",
7
+ "vite.config.js"
8
+ ],
9
+
4
10
  "compilerOptions": {
5
11
  "target": "ES2022",
6
12
  "jsx": "react-jsx",
7
13
  "module": "ESNext",
8
14
  "paths": {
9
- "#/*": ["./src/*"],
10
15
  "@/*": ["./src/*"]
11
16
  },
12
17
  "lib": ["ES2022", "DOM", "DOM.Iterable"],
@@ -4,11 +4,17 @@ import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import ora from "ora";
6
6
  import color from "picocolors";
7
+ import { initGitRepository } from "../helpers/init-git.js";
7
8
  import { generateMigrationsVitnode } from "../helpers/init-vitnode.js";
8
9
  import { installDependencies } from "../helpers/install-dependencies.js";
9
10
  import { isFolderEmpty } from "../helpers/is-folder-empty.js";
10
11
  import { createPackageJSON } from "./create-package-json.js";
11
- export const createVitNode = async ({ root, appName, packageManager, eslint, install, docker, mode, monorepo, }) => {
12
+ const GIT_WARNINGS = {
13
+ "already-in-repository": "Skipped git setup - the project already lives inside a git repository.",
14
+ failed: 'Could not initialize a git repository. Run "git init" in the project yourself once git is configured.',
15
+ "git-unavailable": 'Git is not installed, so no repository was created. Run "git init" in the project after installing git.',
16
+ };
17
+ export const createVitNode = async ({ root, appName, packageManager, eslint, git, install, docker, mode, monorepo, }) => {
12
18
  const spinner = ora(`Creating a new VitNode app in ${color.green(root)}. Using ${color.green(packageManager)}...`).start();
13
19
  const __filename = fileURLToPath(import.meta.url);
14
20
  const __dirname = dirname(__filename);
@@ -200,5 +206,13 @@ export const createVitNode = async ({ root, appName, packageManager, eslint, ins
200
206
  process.exit(1);
201
207
  }
202
208
  }
209
+ let gitResult;
210
+ if (git) {
211
+ spinner.text = "Initializing git repository...";
212
+ gitResult = await initGitRepository({ root });
213
+ }
203
214
  spinner.succeed(`${color.green("Success!")} Created ${color.cyan(appName)} at ${color.cyan(root)}`);
215
+ if (gitResult && gitResult !== "created") {
216
+ console.log(color.yellow(GIT_WARNINGS[gitResult]));
217
+ }
204
218
  };
@@ -33,7 +33,7 @@ export const versionsPackageJson = {
33
33
  cva: "^0.7.1",
34
34
  tanstackReactRouter: "^1.170",
35
35
  tanstackReactStart: "^1.168",
36
- tanstackReactQuery: "^5.101",
36
+ tanstackReactQuery: "^5.102",
37
37
  tanstackRouterSsrQuery: "^1.167",
38
38
  tanstackReactDevtools: "^0.10",
39
39
  tanstackDevtoolsVite: "^0.8",
@@ -0,0 +1,38 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { spawnCommand } from "./spawn-command.js";
4
+ export const DEFAULT_GIT_BRANCH = "main";
5
+ const runGit = async (args, cwd) => new Promise(resolve => {
6
+ const child = spawnCommand("git", args, { cwd, stdio: "ignore" });
7
+ child.on("error", () => {
8
+ resolve(false);
9
+ });
10
+ child.on("close", code => {
11
+ resolve(code === 0);
12
+ });
13
+ });
14
+ export const initGitRepository = async ({ root, branch = DEFAULT_GIT_BRANCH, commitMessage = "Initial commit from Create VitNode App", }) => {
15
+ if (!(await runGit(["--version"], root))) {
16
+ return "git-unavailable";
17
+ }
18
+ if (await runGit(["rev-parse", "--is-inside-work-tree"], root)) {
19
+ return "already-in-repository";
20
+ }
21
+ const removeGitDirectory = async () => {
22
+ await rm(join(root, ".git"), { force: true, recursive: true });
23
+ };
24
+ const initialized = (await runGit(["init", "--initial-branch", branch], root)) ||
25
+ ((await runGit(["init"], root)) &&
26
+ (await runGit(["symbolic-ref", "HEAD", `refs/heads/${branch}`], root)));
27
+ if (!initialized) {
28
+ await removeGitDirectory();
29
+ return "failed";
30
+ }
31
+ const committed = (await runGit(["add", "-A"], root)) &&
32
+ (await runGit(["commit", "-m", commitMessage], root));
33
+ if (!committed) {
34
+ await removeGitDirectory();
35
+ return "failed";
36
+ }
37
+ return "created";
38
+ };