@pylonsync/create-pylon 0.3.268 → 0.3.270

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 (76) hide show
  1. package/bin/create-pylon.js +11 -9
  2. package/package.json +1 -1
  3. package/templates/b2b/app/layout.tsx +1 -1
  4. package/templates/b2b/app/page.tsx +2 -2
  5. package/templates/b2b/tsconfig.json +1 -1
  6. package/templates/barebones/app/page.tsx +1 -1
  7. package/templates/barebones/tsconfig.json +1 -1
  8. package/templates/chat/app/page.tsx +1 -1
  9. package/templates/chat/tsconfig.json +1 -1
  10. package/templates/consumer/app/page.tsx +1 -1
  11. package/templates/consumer/tsconfig.json +1 -1
  12. package/templates/default/.env.example +19 -0
  13. package/templates/default/README.md +85 -0
  14. package/templates/default/app/auth-form.tsx +218 -0
  15. package/templates/default/app/auth-shell.tsx +76 -0
  16. package/templates/default/app/company/[slug]/page.tsx +28 -0
  17. package/templates/default/app/compare/[slug]/page.tsx +27 -0
  18. package/templates/default/app/dashboard/billing/page.tsx +49 -0
  19. package/templates/default/app/dashboard/dashboard-client.tsx +832 -0
  20. package/templates/default/app/dashboard/members/page.tsx +37 -0
  21. package/templates/default/app/dashboard/page.tsx +64 -0
  22. package/templates/default/app/dashboard/projects/page.tsx +37 -0
  23. package/templates/default/app/dashboard/settings/page.tsx +45 -0
  24. package/templates/{ssr → default}/app/globals.css +14 -0
  25. package/templates/default/app/layout.tsx +466 -0
  26. package/templates/default/app/login/page.tsx +27 -0
  27. package/templates/default/app/onboarding/onboarding-client.tsx +261 -0
  28. package/templates/default/app/onboarding/page.tsx +29 -0
  29. package/templates/default/app/page.tsx +653 -0
  30. package/templates/default/app/products/[slug]/page.tsx +134 -0
  31. package/templates/default/app/resources/[slug]/page.tsx +28 -0
  32. package/templates/default/app/signup/page.tsx +24 -0
  33. package/templates/default/app/sitemap.ts +40 -0
  34. package/templates/default/app/solutions/[slug]/page.tsx +28 -0
  35. package/templates/default/app.ts +194 -0
  36. package/templates/default/components/dashboard-shell.tsx +150 -0
  37. package/templates/default/components/marketing.tsx +370 -0
  38. package/templates/default/functions/_pylonStripeFindActiveSubForReference.ts +3 -0
  39. package/templates/default/functions/_pylonStripeFindByCustomerId.ts +3 -0
  40. package/templates/default/functions/_pylonStripeGetCustomerHolder.ts +3 -0
  41. package/templates/default/functions/_pylonStripeListSubsForReference.ts +3 -0
  42. package/templates/default/functions/_pylonStripeOrgMembership.ts +3 -0
  43. package/templates/default/functions/_pylonStripeSetCustomerId.ts +3 -0
  44. package/templates/default/functions/_pylonStripeUpsertSubscription.ts +3 -0
  45. package/templates/default/functions/cancelSubscription.ts +3 -0
  46. package/templates/default/functions/createBillingPortalSession.ts +3 -0
  47. package/templates/default/functions/createCheckoutSession.ts +3 -0
  48. package/templates/default/functions/restoreSubscription.ts +3 -0
  49. package/templates/default/functions/stripeWebhook.ts +3 -0
  50. package/templates/default/lib/billing.ts +46 -0
  51. package/templates/default/lib/products.ts +122 -0
  52. package/templates/default/lib/site.ts +261 -0
  53. package/templates/{ssr → default}/package.json +2 -0
  54. package/templates/{ssr → default}/tsconfig.json +2 -2
  55. package/templates/todo/app/page.tsx +1 -1
  56. package/templates/todo/tsconfig.json +1 -1
  57. package/templates/ssr/README.md +0 -56
  58. package/templates/ssr/app/auth-form.tsx +0 -142
  59. package/templates/ssr/app/dashboard/dashboard-client.tsx +0 -116
  60. package/templates/ssr/app/dashboard/page.tsx +0 -70
  61. package/templates/ssr/app/layout.tsx +0 -71
  62. package/templates/ssr/app/login/page.tsx +0 -47
  63. package/templates/ssr/app/page.tsx +0 -114
  64. package/templates/ssr/app/signup/page.tsx +0 -44
  65. package/templates/ssr/app/sitemap.ts +0 -27
  66. package/templates/ssr/app.ts +0 -94
  67. package/templates/ssr/functions/_keep.ts +0 -13
  68. /package/templates/{ssr → default}/AGENTS.md +0 -0
  69. /package/templates/{ssr → default}/app/error.tsx +0 -0
  70. /package/templates/{ssr → default}/app/not-found.tsx +0 -0
  71. /package/templates/{ssr → default}/app/robots.ts +0 -0
  72. /package/templates/{ssr → default}/components/ui/button.tsx +0 -0
  73. /package/templates/{ssr → default}/components/ui/card.tsx +0 -0
  74. /package/templates/{ssr → default}/components.json +0 -0
  75. /package/templates/{ssr → default}/gitignore +0 -0
  76. /package/templates/{ssr → default}/lib/utils.ts +0 -0
@@ -1,70 +0,0 @@
1
- import React, { Suspense, use } from "react";
2
- import {
3
- type Metadata,
4
- type PageProps,
5
- type ServerData,
6
- } from "@pylonsync/react";
7
- import { Dashboard, type Note } from "./dashboard-client";
8
-
9
- export const metadata: Metadata = {
10
- title: "Dashboard — __APP_NAME__",
11
- robots: "noindex",
12
- };
13
-
14
- interface User {
15
- id: string;
16
- email: string;
17
- displayName?: string;
18
- }
19
-
20
- // Reads the signed-in user + their notes DURING the server render. The reads
21
- // run through the same policy gate as a client query, so they're owner-scoped
22
- // (User: only your row; Note: only your notes) — see the policies in app.ts.
23
- // React 19 `use()` suspends until they resolve on the server, so the HTML
24
- // arrives with your notes already in it (no empty flash); then the <Dashboard>
25
- // island hydrates and takes over live.
26
- function DashboardBody({
27
- serverData,
28
- userId,
29
- }: {
30
- serverData: ServerData;
31
- userId: string;
32
- }) {
33
- const user = use(serverData.get<User>("User", userId));
34
- const notes = use(serverData.list<Note>("Note"));
35
- return (
36
- <>
37
- <p className="text-sm text-muted-foreground">
38
- Signed in as{" "}
39
- <span className="font-medium text-foreground">
40
- {user?.displayName || user?.email || "you"}
41
- </span>
42
- .
43
- </p>
44
- <Dashboard initial={notes} />
45
- </>
46
- );
47
- }
48
-
49
- // `app/dashboard/page.tsx` → `/dashboard`.
50
- export default function DashboardPage({
51
- auth,
52
- response,
53
- serverData,
54
- }: PageProps) {
55
- // Server-side auth gate: anonymous requests get a 307 to /login before any
56
- // HTML. The redirect MUST fire here in the synchronous shell render — not
57
- // inside the <Suspense> below — or React swallows it. No flash of the
58
- // dashboard, works with JS disabled.
59
- if (!auth.user_id) response.redirect("/login");
60
- return (
61
- <div className="space-y-6">
62
- <h1 className="text-2xl font-semibold tracking-tight">Your notes</h1>
63
- <Suspense
64
- fallback={<p className="text-sm text-muted-foreground">Loading…</p>}
65
- >
66
- <DashboardBody serverData={serverData} userId={auth.user_id!} />
67
- </Suspense>
68
- </div>
69
- );
70
- }
@@ -1,71 +0,0 @@
1
- import React from "react";
2
- import { Link, type PageAuth } from "@pylonsync/react";
3
-
4
- // A layout receives the page props plus `children`. `auth.user_id` is null
5
- // for anonymous visitors and the signed-in user's id otherwise — resolved
6
- // server-side from the session cookie, before any HTML is sent, so the nav
7
- // renders the right links on the first byte (no flash, no client fetch). The
8
- // `PageAuth` type is exported from @pylonsync/react so you never hand-roll it.
9
- interface LayoutProps {
10
- children: React.ReactNode;
11
- url: string;
12
- auth: PageAuth;
13
- }
14
-
15
- // The root layout wraps every page.
16
- export default function RootLayout({ children, auth }: LayoutProps) {
17
- const signedIn = Boolean(auth?.user_id);
18
- // Add `className="dark"` to this <html> to flip every shadcn token to its
19
- // dark value. The classes below use semantic tokens (bg-background,
20
- // text-foreground, …) so the whole UI re-themes from app/globals.css.
21
- return (
22
- <html lang="en">
23
- <head>
24
- <meta charSet="utf-8" />
25
- <meta name="viewport" content="width=device-width, initial-scale=1" />
26
- <title>__APP_NAME__</title>
27
- {/* Tailwind is compiled by Pylon from app/globals.css and the
28
- stylesheet link is injected here automatically — nothing to
29
- wire up. */}
30
- </head>
31
- <body className="min-h-screen bg-background text-foreground antialiased">
32
- <header className="sticky top-0 z-10 border-b bg-background/80 backdrop-blur">
33
- <div className="mx-auto flex max-w-3xl items-center justify-between px-4 py-3">
34
- <Link
35
- href="/"
36
- className="text-sm font-semibold tracking-tight hover:text-muted-foreground"
37
- >
38
- __APP_NAME__
39
- </Link>
40
- <nav className="flex items-center gap-4 text-sm text-muted-foreground">
41
- <Link href="/" className="hover:text-foreground">
42
- Home
43
- </Link>
44
- {signedIn ? (
45
- <Link href="/dashboard" className="hover:text-foreground">
46
- Dashboard
47
- </Link>
48
- ) : (
49
- <>
50
- <Link href="/login" className="hover:text-foreground">
51
- Sign in
52
- </Link>
53
- <Link
54
- href="/signup"
55
- className="rounded-md bg-primary px-3 py-1.5 font-medium text-primary-foreground hover:opacity-90"
56
- >
57
- Sign up
58
- </Link>
59
- </>
60
- )}
61
- </nav>
62
- </div>
63
- </header>
64
- <main className="mx-auto max-w-3xl px-4 py-10">{children}</main>
65
- <footer className="border-t py-6 text-center text-xs text-muted-foreground">
66
- Rendered by Pylon · one server, one port
67
- </footer>
68
- </body>
69
- </html>
70
- );
71
- }
@@ -1,47 +0,0 @@
1
- import React from "react";
2
- import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
- import {
4
- Card,
5
- CardContent,
6
- CardDescription,
7
- CardHeader,
8
- CardTitle,
9
- } from "@/components/ui/card";
10
- import { AuthForm } from "../auth-form";
11
-
12
- export const metadata: Metadata = {
13
- title: "Sign in — __APP_NAME__",
14
- // Auth pages shouldn't be indexed.
15
- robots: "noindex",
16
- };
17
-
18
- // `app/login/page.tsx` → `/login`. A server-rendered shell around the
19
- // client-side <AuthForm> island.
20
- export default function LoginPage({ auth, response }: PageProps) {
21
- // Already signed in? Skip the form. `response.redirect` runs in the
22
- // synchronous shell render, so it's a real 307 before any HTML is sent
23
- // (no flash, works with JS disabled).
24
- if (auth.user_id) response.redirect("/dashboard");
25
- return (
26
- <div className="mx-auto max-w-sm">
27
- <Card>
28
- <CardHeader>
29
- <CardTitle>Sign in</CardTitle>
30
- <CardDescription>Welcome back.</CardDescription>
31
- </CardHeader>
32
- <CardContent className="space-y-4">
33
- <AuthForm mode="login" />
34
- <p className="text-center text-sm text-muted-foreground">
35
- No account?{" "}
36
- <Link
37
- href="/signup"
38
- className="font-medium text-foreground hover:underline"
39
- >
40
- Create one
41
- </Link>
42
- </p>
43
- </CardContent>
44
- </Card>
45
- </div>
46
- );
47
- }
@@ -1,114 +0,0 @@
1
- import React from "react";
2
- import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
- import { Button } from "@/components/ui/button";
4
- import {
5
- Card,
6
- CardContent,
7
- CardDescription,
8
- CardHeader,
9
- CardTitle,
10
- } from "@/components/ui/card";
11
-
12
- // SEO metadata. Export `metadata` (static) or `generateMetadata(props)`
13
- // (dynamic) from any page or layout — Pylon renders the <title>/<meta>
14
- // into <head> server-side. The `Metadata` type is exported from
15
- // @pylonsync/react.
16
- export const metadata: Metadata = {
17
- title: "__APP_NAME__ — full-stack Pylon app",
18
- description:
19
- "A server-rendered homepage, email/password auth, and a live client dashboard over one synced backend — one binary, one port.",
20
- };
21
-
22
- // `app/page.tsx` → `/`. This page is server-rendered: view source and the copy
23
- // is in the HTML, not fetched later — good for SEO and first paint. It reads
24
- // `auth` (resolved from the session cookie during the render) to show the
25
- // right call to action. Every page receives `PageProps` from the SSR runtime:
26
- // `{ url, params, searchParams, auth, response, serverData }` — typed, no
27
- // hand-rolled interface.
28
- export default function IndexPage({ auth }: PageProps) {
29
- const signedIn = Boolean(auth.user_id);
30
- return (
31
- <div className="space-y-12">
32
- <section className="space-y-5">
33
- <span className="inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium text-muted-foreground">
34
- Server-rendered · authenticated · synced · one port
35
- </span>
36
- <h1 className="text-4xl font-semibold tracking-tight">
37
- Full-stack apps, one binary.
38
- </h1>
39
- <p className="max-w-xl text-lg text-muted-foreground">
40
- This homepage is server-rendered React. Sign in and your dashboard
41
- becomes a live, local-first view over the same Pylon backend — writes
42
- appear instantly and sync across tabs. No Next.js, no separate API
43
- server, no realtime sidecar.
44
- </p>
45
- <div className="flex flex-wrap items-center gap-3">
46
- {signedIn ? (
47
- <Button asChild>
48
- <Link href="/dashboard">Go to your dashboard →</Link>
49
- </Button>
50
- ) : (
51
- <>
52
- <Button asChild>
53
- <Link href="/signup">Get started</Link>
54
- </Button>
55
- <Button asChild variant="outline">
56
- <Link href="/login">Sign in</Link>
57
- </Button>
58
- </>
59
- )}
60
- </div>
61
- </section>
62
-
63
- <section className="grid gap-4 sm:grid-cols-3">
64
- <Feature title="Server-rendered">
65
- File-based routes under <Code>app/</Code>. Pages render to HTML on the
66
- server with <Code>metadata</Code> in <Code>{"<head>"}</Code>, then
67
- hydrate. Drop <Code>app/about/page.tsx</Code> to add{" "}
68
- <Code>/about</Code>.
69
- </Feature>
70
- <Feature title="Auth included">
71
- Email/password is built in. <Code>/login</Code> and{" "}
72
- <Code>/signup</Code> hit <Code>/api/auth/password/*</Code>; the server
73
- sets an HttpOnly session cookie. <Code>/dashboard</Code> gates on it
74
- server-side.
75
- </Feature>
76
- <Feature title="Synced database">
77
- Every <Code>entity()</Code> in <Code>app.ts</Code> gets a REST +
78
- realtime API and a typed client. <Code>db.useQuery</Code> is live;{" "}
79
- <Code>db.insert</Code> is optimistic.
80
- </Feature>
81
- </section>
82
-
83
- <p className="text-xs text-muted-foreground">
84
- Edit <Code>app/page.tsx</Code> and save — the page reloads instantly.
85
- The data model and access policies live in <Code>app.ts</Code>.
86
- </p>
87
- </div>
88
- );
89
- }
90
-
91
- function Feature({
92
- title,
93
- children,
94
- }: {
95
- title: string;
96
- children: React.ReactNode;
97
- }) {
98
- return (
99
- <Card>
100
- <CardHeader>
101
- <CardTitle className="text-base">{title}</CardTitle>
102
- </CardHeader>
103
- <CardContent>
104
- <CardDescription className="text-sm leading-relaxed">
105
- {children}
106
- </CardDescription>
107
- </CardContent>
108
- </Card>
109
- );
110
- }
111
-
112
- function Code({ children }: { children: React.ReactNode }) {
113
- return <code className="rounded bg-muted px-1 text-xs">{children}</code>;
114
- }
@@ -1,44 +0,0 @@
1
- import React from "react";
2
- import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
- import {
4
- Card,
5
- CardContent,
6
- CardDescription,
7
- CardHeader,
8
- CardTitle,
9
- } from "@/components/ui/card";
10
- import { AuthForm } from "../auth-form";
11
-
12
- export const metadata: Metadata = {
13
- title: "Create your account — __APP_NAME__",
14
- robots: "noindex",
15
- };
16
-
17
- // `app/signup/page.tsx` → `/signup`. Same shell as /login, register mode.
18
- export default function SignupPage({ auth, response }: PageProps) {
19
- if (auth.user_id) response.redirect("/dashboard");
20
- return (
21
- <div className="mx-auto max-w-sm">
22
- <Card>
23
- <CardHeader>
24
- <CardTitle>Create your account</CardTitle>
25
- <CardDescription>
26
- Email + password. No credit card, no email verification step in dev.
27
- </CardDescription>
28
- </CardHeader>
29
- <CardContent className="space-y-4">
30
- <AuthForm mode="signup" />
31
- <p className="text-center text-sm text-muted-foreground">
32
- Already have an account?{" "}
33
- <Link
34
- href="/login"
35
- className="font-medium text-foreground hover:underline"
36
- >
37
- Sign in
38
- </Link>
39
- </p>
40
- </CardContent>
41
- </Card>
42
- </div>
43
- );
44
- }
@@ -1,27 +0,0 @@
1
- import type { Sitemap } from "@pylonsync/react";
2
-
3
- // app/sitemap.ts → served at /sitemap.xml. The default export can be async, so
4
- // it can enumerate dynamic pages from your database. Point SITE_URL at your
5
- // domain in production.
6
- const SITE = process.env.SITE_URL ?? "http://localhost:4321";
7
-
8
- export default async function sitemap(): Promise<Sitemap> {
9
- // Only public pages belong here — /dashboard is private (and noindex), so
10
- // it's intentionally left out.
11
- const staticRoutes: Sitemap = [
12
- { url: `${SITE}/`, changeFrequency: "weekly", priority: 1 },
13
- { url: `${SITE}/login`, changeFrequency: "yearly", priority: 0.3 },
14
- { url: `${SITE}/signup`, changeFrequency: "yearly", priority: 0.5 },
15
- ];
16
-
17
- // The export is async, so you can enumerate dynamic pages from a DB read:
18
- //
19
- // const posts = await fetchPublishedPosts();
20
- // const postRoutes: Sitemap = posts.map((p) => ({
21
- // url: `${SITE}/blog/${p.slug}`,
22
- // lastModified: p.updatedAt,
23
- // }));
24
- // return [...staticRoutes, ...postRoutes];
25
-
26
- return staticRoutes;
27
- }
@@ -1,94 +0,0 @@
1
- import {
2
- entity,
3
- field,
4
- policy,
5
- auth,
6
- buildManifest,
7
- discoverAppRoutes,
8
- } from "@pylonsync/sdk";
9
-
10
- // Accounts. Email/password auth is built in: POST /api/auth/password/register
11
- // hashes the password and writes this row; /api/auth/password/login mints a
12
- // session and sets an HttpOnly cookie. The framework treats the entity named
13
- // "User" as the account table — `passwordHash` is server-only and never
14
- // serialized to a client.
15
- const User = entity(
16
- "User",
17
- {
18
- email: field.string(),
19
- displayName: field.string().optional(),
20
- passwordHash: field.string().serverOnly().optional(),
21
- // The framework's /api/auth/password/register stamps a generated avatar
22
- // color here, so the User entity must declare it.
23
- avatarColor: field.string().optional(),
24
- createdAt: field.datetime().defaultNow(),
25
- },
26
- { indexes: [{ name: "by_email", fields: ["email"], unique: true }] },
27
- );
28
-
29
- // A note that belongs to one user. `ownerId: field.owner()` is the key move:
30
- // the framework stamps the signed-in user's id server-side on insert and
31
- // rejects any forged value — so the dashboard can do a plain, optimistic
32
- // `db.insert("Note", { body })` (the row shows instantly, no round-trip) while
33
- // the owner stays unspoofable. No createNote function to write.
34
- const Note = entity(
35
- "Note",
36
- {
37
- ownerId: field.string().owner(),
38
- body: field.string(),
39
- done: field.boolean().default(false),
40
- createdAt: field.datetime().defaultNow(),
41
- },
42
- { indexes: [{ name: "by_owner", fields: ["ownerId"], unique: false }] },
43
- );
44
-
45
- // Notes are private — every read and write is gated to the owner. An entity
46
- // with NO policy is denied to clients by default, so this is exactly what
47
- // makes the dashboard's live query + optimistic writes work, and only for
48
- // your own rows. `auth.userId` is the session user; `data.ownerId` is the row.
49
- const notePolicy = policy({
50
- name: "note_access",
51
- entity: "Note",
52
- allowRead: "auth.userId == data.ownerId",
53
- allowInsert: "auth.userId != null",
54
- allowUpdate: "auth.userId == data.ownerId",
55
- allowDelete: "auth.userId == data.ownerId",
56
- });
57
-
58
- // User rows are read-only to clients, and only your own (so the dashboard can
59
- // read your display name). The auth subsystem owns writes — registration and
60
- // login go through /api/auth/password/*, never the entity API.
61
- const userPolicy = policy({
62
- name: "user_access",
63
- entity: "User",
64
- allowRead: "auth.userId == data.id",
65
- allowInsert: "false",
66
- allowUpdate: "false",
67
- allowDelete: "false",
68
- });
69
-
70
- // The manifest is your whole app in one object: data, policies, and the
71
- // file-based routes under `app/`. `pylon dev` reads this, serves the SSR
72
- // frontend and the API from one port, and regenerates a typed client on
73
- // every change.
74
- const manifest = buildManifest({
75
- name: "__APP_NAME__",
76
- version: "0.1.0",
77
- entities: [User, Note],
78
- queries: [],
79
- actions: [],
80
- policies: [userPolicy, notePolicy],
81
- // Email/password is on by default against the User entity above. `auth()`
82
- // is the knob for session lifetime, exposed fields, orgs, and trusted
83
- // origins — `auth({ session: { expiresIn: 60 * 60 * 24 * 7 } })` for a
84
- // 7-day session, etc.
85
- auth: auth(),
86
- // File-based routing: `discoverAppRoutes()` walks `app/**/page.tsx` and
87
- // emits one route per page. Drop `app/about/page.tsx` to add `/about`.
88
- routes: await discoverAppRoutes(),
89
- });
90
-
91
- // Emit canonical manifest JSON to stdout for `pylon codegen`.
92
- console.log(JSON.stringify(manifest, null, 2));
93
-
94
- export default manifest;
@@ -1,13 +0,0 @@
1
- // Server functions go here. Each file in this directory that exports a
2
- // query() or action() becomes a typed RPC endpoint, callable from your
3
- // pages and client with full type inference. Delete this placeholder when
4
- // you add your first one.
5
- //
6
- // Example (functions/notes.ts):
7
- //
8
- // import { query } from "@pylonsync/functions";
9
- //
10
- // export const listNotes = query(async (ctx) => {
11
- // return ctx.db.list("Note");
12
- // });
13
- export {};
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes