@pylonsync/create-pylon 0.3.51 → 0.3.54

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 (180) hide show
  1. package/bin/create-pylon.js +347 -1156
  2. package/package.json +4 -3
  3. package/templates/_root/.env.example +9 -0
  4. package/templates/_root/README.md +43 -0
  5. package/templates/backend/b2b/apps/api/functions/archiveProject.ts +15 -0
  6. package/templates/backend/b2b/apps/api/functions/createOrg.ts +43 -0
  7. package/templates/backend/b2b/apps/api/functions/createProject.ts +25 -0
  8. package/templates/backend/b2b/apps/api/functions/inviteMember.ts +49 -0
  9. package/templates/backend/b2b/apps/api/functions/myOrgs.ts +37 -0
  10. package/templates/backend/b2b/apps/api/functions/orgMembers.ts +13 -0
  11. package/templates/backend/b2b/apps/api/functions/orgProjects.ts +18 -0
  12. package/templates/backend/b2b/apps/api/functions/removeMember.ts +29 -0
  13. package/templates/backend/b2b/apps/api/functions/setMemberRole.ts +38 -0
  14. package/templates/backend/b2b/apps/api/package.json +20 -0
  15. package/templates/backend/b2b/apps/api/schema.ts +171 -0
  16. package/templates/backend/b2b/apps/api/tsconfig.json +13 -0
  17. package/templates/backend/barebones/apps/api/functions/createWidget.ts +22 -0
  18. package/templates/backend/barebones/apps/api/functions/listWidgets.ts +18 -0
  19. package/templates/backend/barebones/apps/api/package.json +20 -0
  20. package/templates/backend/barebones/apps/api/schema.ts +61 -0
  21. package/templates/backend/barebones/apps/api/tsconfig.json +13 -0
  22. package/templates/backend/chat/apps/api/functions/createRoom.ts +32 -0
  23. package/templates/backend/chat/apps/api/functions/listRooms.ts +15 -0
  24. package/templates/backend/chat/apps/api/functions/roomMessages.ts +20 -0
  25. package/templates/backend/chat/apps/api/functions/sendMessage.ts +37 -0
  26. package/templates/backend/chat/apps/api/package.json +20 -0
  27. package/templates/backend/chat/apps/api/schema.ts +93 -0
  28. package/templates/backend/chat/apps/api/tsconfig.json +13 -0
  29. package/templates/backend/consumer/apps/api/functions/createPost.ts +48 -0
  30. package/templates/backend/consumer/apps/api/functions/deletePost.ts +21 -0
  31. package/templates/backend/consumer/apps/api/functions/feed.ts +57 -0
  32. package/templates/backend/consumer/apps/api/functions/myProfile.ts +17 -0
  33. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +17 -0
  34. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +48 -0
  35. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +70 -0
  36. package/templates/backend/consumer/apps/api/package.json +20 -0
  37. package/templates/backend/consumer/apps/api/schema.ts +130 -0
  38. package/templates/backend/consumer/apps/api/tsconfig.json +13 -0
  39. package/templates/backend/todo/apps/api/functions/addTodo.ts +27 -0
  40. package/templates/backend/todo/apps/api/functions/deleteTodo.ts +14 -0
  41. package/templates/backend/todo/apps/api/functions/editTodo.ts +16 -0
  42. package/templates/backend/todo/apps/api/functions/listTodos.ts +24 -0
  43. package/templates/backend/todo/apps/api/functions/reorderTodo.ts +14 -0
  44. package/templates/backend/todo/apps/api/functions/toggleTodo.ts +13 -0
  45. package/templates/backend/todo/apps/api/package.json +20 -0
  46. package/templates/backend/todo/apps/api/schema.ts +85 -0
  47. package/templates/backend/todo/apps/api/tsconfig.json +13 -0
  48. package/templates/expo/barebones/apps/expo/App.tsx +166 -0
  49. package/templates/expo/barebones/apps/expo/app.json +31 -0
  50. package/templates/expo/barebones/apps/expo/babel.config.js +6 -0
  51. package/templates/expo/barebones/apps/expo/package.json +30 -0
  52. package/templates/expo/barebones/apps/expo/tsconfig.json +16 -0
  53. package/templates/expo/chat/apps/expo/App.tsx +414 -0
  54. package/templates/expo/chat/apps/expo/app.json +25 -0
  55. package/templates/expo/chat/apps/expo/babel.config.js +6 -0
  56. package/templates/expo/chat/apps/expo/package.json +30 -0
  57. package/templates/expo/chat/apps/expo/tsconfig.json +16 -0
  58. package/templates/expo/consumer/apps/expo/App.tsx +360 -0
  59. package/templates/expo/consumer/apps/expo/app.json +25 -0
  60. package/templates/expo/consumer/apps/expo/babel.config.js +6 -0
  61. package/templates/expo/consumer/apps/expo/package.json +30 -0
  62. package/templates/expo/consumer/apps/expo/tsconfig.json +16 -0
  63. package/templates/expo/todo/apps/expo/App.tsx +287 -0
  64. package/templates/expo/todo/apps/expo/app.json +25 -0
  65. package/templates/expo/todo/apps/expo/babel.config.js +6 -0
  66. package/templates/expo/todo/apps/expo/package.json +30 -0
  67. package/templates/expo/todo/apps/expo/tsconfig.json +16 -0
  68. package/templates/ios/barebones/apps/ios/Package.swift +34 -0
  69. package/templates/ios/barebones/apps/ios/Sources/__APP_NAME_PASCAL__/ContentView.swift +98 -0
  70. package/templates/ios/barebones/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +17 -0
  71. package/templates/ios/barebones/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +34 -0
  72. package/templates/ios/barebones/apps/ios/project.yml +42 -0
  73. package/templates/ios/chat/apps/ios/Package.swift +34 -0
  74. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +120 -0
  75. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +26 -0
  76. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/RoomView.swift +137 -0
  77. package/templates/ios/chat/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +35 -0
  78. package/templates/ios/chat/apps/ios/project.yml +42 -0
  79. package/templates/ios/consumer/apps/ios/Package.swift +23 -0
  80. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +170 -0
  81. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +42 -0
  82. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +60 -0
  83. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +30 -0
  84. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +29 -0
  85. package/templates/ios/consumer/apps/ios/project.yml +42 -0
  86. package/templates/ios/todo/apps/ios/Package.swift +23 -0
  87. package/templates/ios/todo/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +18 -0
  88. package/templates/ios/todo/apps/ios/Sources/__APP_NAME_PASCAL__/TodoListView.swift +230 -0
  89. package/templates/ios/todo/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +28 -0
  90. package/templates/ios/todo/apps/ios/project.yml +32 -0
  91. package/templates/mac/b2b/apps/mac/Package.swift +22 -0
  92. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +15 -0
  93. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/OrgPickerView.swift +178 -0
  94. package/templates/mac/b2b/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  95. package/templates/mac/b2b/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  96. package/templates/mac/b2b/apps/mac/project.yml +34 -0
  97. package/templates/mac/barebones/apps/mac/Package.swift +33 -0
  98. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/ContentView.swift +104 -0
  99. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +17 -0
  100. package/templates/mac/barebones/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  101. package/templates/mac/barebones/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  102. package/templates/mac/barebones/apps/mac/project.yml +34 -0
  103. package/templates/mac/chat/apps/mac/Package.swift +33 -0
  104. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/ChatRootView.swift +140 -0
  105. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +26 -0
  106. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/RoomView.swift +137 -0
  107. package/templates/mac/chat/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +37 -0
  108. package/templates/mac/chat/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  109. package/templates/mac/chat/apps/mac/project.yml +34 -0
  110. package/templates/mac/consumer/apps/mac/Package.swift +33 -0
  111. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/FeedView.swift +170 -0
  112. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +42 -0
  113. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +60 -0
  114. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/RootView.swift +30 -0
  115. package/templates/mac/consumer/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +31 -0
  116. package/templates/mac/consumer/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  117. package/templates/mac/consumer/apps/mac/project.yml +34 -0
  118. package/templates/mac/todo/apps/mac/Package.swift +33 -0
  119. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/Models.swift +19 -0
  120. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/TodoListView.swift +244 -0
  121. package/templates/mac/todo/apps/mac/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +30 -0
  122. package/templates/mac/todo/apps/mac/__APP_NAME_PASCAL__.entitlements +13 -0
  123. package/templates/mac/todo/apps/mac/project.yml +34 -0
  124. package/templates/ui/packages/ui/package.json +26 -0
  125. package/templates/ui/packages/ui/src/button.tsx +44 -0
  126. package/templates/ui/packages/ui/src/card.tsx +39 -0
  127. package/templates/ui/packages/ui/src/cn.ts +12 -0
  128. package/templates/ui/packages/ui/src/index.ts +4 -0
  129. package/templates/ui/packages/ui/src/input.tsx +19 -0
  130. package/templates/ui/packages/ui/tsconfig.json +15 -0
  131. package/templates/web/b2b/apps/web/next-env.d.ts +2 -0
  132. package/templates/web/b2b/apps/web/next.config.ts +24 -0
  133. package/templates/web/b2b/apps/web/package.json +29 -0
  134. package/templates/web/b2b/apps/web/postcss.config.mjs +3 -0
  135. package/templates/web/b2b/apps/web/src/app/components/OrgPicker.tsx +171 -0
  136. package/templates/web/b2b/apps/web/src/app/globals.css +6 -0
  137. package/templates/web/b2b/apps/web/src/app/layout.tsx +21 -0
  138. package/templates/web/b2b/apps/web/src/app/page.tsx +39 -0
  139. package/templates/web/b2b/apps/web/src/lib/pylon.ts +5 -0
  140. package/templates/web/b2b/apps/web/tsconfig.json +26 -0
  141. package/templates/web/barebones/apps/web/next-env.d.ts +2 -0
  142. package/templates/web/barebones/apps/web/next.config.ts +40 -0
  143. package/templates/web/barebones/apps/web/package.json +29 -0
  144. package/templates/web/barebones/apps/web/postcss.config.mjs +4 -0
  145. package/templates/web/barebones/apps/web/src/app/components/WidgetList.tsx +81 -0
  146. package/templates/web/barebones/apps/web/src/app/globals.css +9 -0
  147. package/templates/web/barebones/apps/web/src/app/layout.tsx +21 -0
  148. package/templates/web/barebones/apps/web/src/app/page.tsx +43 -0
  149. package/templates/web/barebones/apps/web/src/lib/pylon.ts +14 -0
  150. package/templates/web/barebones/apps/web/tsconfig.json +26 -0
  151. package/templates/web/chat/apps/web/next-env.d.ts +2 -0
  152. package/templates/web/chat/apps/web/next.config.ts +24 -0
  153. package/templates/web/chat/apps/web/package.json +29 -0
  154. package/templates/web/chat/apps/web/postcss.config.mjs +3 -0
  155. package/templates/web/chat/apps/web/src/app/components/ChatRoom.tsx +250 -0
  156. package/templates/web/chat/apps/web/src/app/globals.css +6 -0
  157. package/templates/web/chat/apps/web/src/app/layout.tsx +21 -0
  158. package/templates/web/chat/apps/web/src/app/page.tsx +51 -0
  159. package/templates/web/chat/apps/web/src/lib/pylon.ts +5 -0
  160. package/templates/web/chat/apps/web/tsconfig.json +26 -0
  161. package/templates/web/consumer/apps/web/next-env.d.ts +2 -0
  162. package/templates/web/consumer/apps/web/next.config.ts +24 -0
  163. package/templates/web/consumer/apps/web/package.json +29 -0
  164. package/templates/web/consumer/apps/web/postcss.config.mjs +3 -0
  165. package/templates/web/consumer/apps/web/src/app/components/Feed.tsx +295 -0
  166. package/templates/web/consumer/apps/web/src/app/globals.css +6 -0
  167. package/templates/web/consumer/apps/web/src/app/layout.tsx +21 -0
  168. package/templates/web/consumer/apps/web/src/app/page.tsx +55 -0
  169. package/templates/web/consumer/apps/web/src/lib/pylon.ts +5 -0
  170. package/templates/web/consumer/apps/web/tsconfig.json +26 -0
  171. package/templates/web/todo/apps/web/next-env.d.ts +2 -0
  172. package/templates/web/todo/apps/web/next.config.ts +24 -0
  173. package/templates/web/todo/apps/web/package.json +32 -0
  174. package/templates/web/todo/apps/web/postcss.config.mjs +3 -0
  175. package/templates/web/todo/apps/web/src/app/components/TodoList.tsx +310 -0
  176. package/templates/web/todo/apps/web/src/app/globals.css +6 -0
  177. package/templates/web/todo/apps/web/src/app/layout.tsx +21 -0
  178. package/templates/web/todo/apps/web/src/app/page.tsx +36 -0
  179. package/templates/web/todo/apps/web/src/lib/pylon.ts +5 -0
  180. package/templates/web/todo/apps/web/tsconfig.json +26 -0
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ import { useState, useTransition } from "react";
4
+ import { Button, Input, Card, CardHeader, CardContent } from "@__APP_NAME_KEBAB__/ui";
5
+
6
+ type Org = {
7
+ id: string;
8
+ slug: string;
9
+ name: string;
10
+ role: string;
11
+ createdAt: string;
12
+ };
13
+
14
+ export function OrgPicker({ initialOrgs }: { initialOrgs: Org[] }) {
15
+ const [orgs, setOrgs] = useState(initialOrgs);
16
+ const [activeId, setActiveId] = useState<string | null>(
17
+ initialOrgs[0]?.id ?? null,
18
+ );
19
+ const [creating, setCreating] = useState(false);
20
+ const [name, setName] = useState("");
21
+ const [slug, setSlug] = useState("");
22
+ const [pending, startTransition] = useTransition();
23
+ const [error, setError] = useState<string | null>(null);
24
+
25
+ async function create(e: React.FormEvent) {
26
+ e.preventDefault();
27
+ setError(null);
28
+ startTransition(async () => {
29
+ const res = await fetch("/api/fn/createOrg", {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify({ slug, name }),
33
+ });
34
+ if (res.ok) {
35
+ const org = (await res.json()) as Org;
36
+ setOrgs((prev) => [
37
+ { ...org, role: "owner" },
38
+ ...prev,
39
+ ]);
40
+ setActiveId(org.id);
41
+ setName("");
42
+ setSlug("");
43
+ setCreating(false);
44
+ } else {
45
+ const body = await res.json().catch(() => ({}));
46
+ setError(body?.message ?? "create failed");
47
+ }
48
+ });
49
+ }
50
+
51
+ const active = orgs.find((o) => o.id === activeId) ?? null;
52
+
53
+ return (
54
+ <div className="space-y-6">
55
+ <Card>
56
+ <CardHeader>
57
+ <div className="flex items-center justify-between">
58
+ <h2 className="text-sm font-medium">Your organizations</h2>
59
+ <Button
60
+ size="sm"
61
+ variant={creating ? "ghost" : "default"}
62
+ onClick={() => setCreating((v) => !v)}
63
+ >
64
+ {creating ? "Cancel" : "New org"}
65
+ </Button>
66
+ </div>
67
+ </CardHeader>
68
+ <CardContent>
69
+ {creating && (
70
+ <form onSubmit={create} className="grid grid-cols-2 gap-2 mb-4">
71
+ <Input
72
+ placeholder="Org name"
73
+ value={name}
74
+ onChange={(e) => setName(e.target.value)}
75
+ disabled={pending}
76
+ required
77
+ />
78
+ <Input
79
+ placeholder="acme-corp"
80
+ value={slug}
81
+ onChange={(e) => setSlug(e.target.value.toLowerCase())}
82
+ disabled={pending}
83
+ pattern="[a-z0-9][a-z0-9-]{1,30}[a-z0-9]"
84
+ required
85
+ />
86
+ <Button
87
+ type="submit"
88
+ variant="primary"
89
+ disabled={pending || !name.trim() || !slug.trim()}
90
+ className="col-span-2"
91
+ >
92
+ {pending ? "Creating…" : "Create org"}
93
+ </Button>
94
+ {error && (
95
+ <p className="text-xs text-red-500 col-span-2">{error}</p>
96
+ )}
97
+ </form>
98
+ )}
99
+
100
+ {orgs.length === 0 ? (
101
+ <p className="text-sm text-neutral-500 py-2">
102
+ You&apos;re not in any orgs yet. Create one above to get started.
103
+ </p>
104
+ ) : (
105
+ <ul className="divide-y divide-neutral-200 dark:divide-neutral-800 -mx-5">
106
+ {orgs.map((o) => (
107
+ <li
108
+ key={o.id}
109
+ className="flex items-center justify-between px-5 py-2.5 cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-900"
110
+ data-active={o.id === activeId || undefined}
111
+ onClick={() => setActiveId(o.id)}
112
+ >
113
+ <div>
114
+ <div className="text-sm font-medium">{o.name}</div>
115
+ <div className="text-xs text-neutral-400 font-mono">
116
+ {o.slug}
117
+ </div>
118
+ </div>
119
+ <span
120
+ className={`text-xs font-mono uppercase tracking-wide px-2 py-0.5 rounded ${
121
+ o.role === "owner"
122
+ ? "bg-blue-100 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
123
+ : o.role === "admin"
124
+ ? "bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300"
125
+ : "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"
126
+ }`}
127
+ >
128
+ {o.role}
129
+ </span>
130
+ </li>
131
+ ))}
132
+ </ul>
133
+ )}
134
+ </CardContent>
135
+ </Card>
136
+
137
+ {active && <OrgDetail org={active} />}
138
+ </div>
139
+ );
140
+ }
141
+
142
+ function OrgDetail({ org }: { org: Org }) {
143
+ return (
144
+ <Card>
145
+ <CardHeader>
146
+ <div className="flex items-baseline justify-between">
147
+ <h2 className="text-sm font-medium">
148
+ {org.name}{" "}
149
+ <span className="text-xs text-neutral-400 font-mono ml-1">
150
+ /{org.slug}
151
+ </span>
152
+ </h2>
153
+ <span className="text-xs text-neutral-500">your role: {org.role}</span>
154
+ </div>
155
+ </CardHeader>
156
+ <CardContent className="space-y-4">
157
+ <p className="text-sm text-neutral-500">
158
+ Wire member management and project lists into this panel by calling
159
+ <code className="font-mono text-xs mx-1">/api/fn/orgMembers</code>
160
+ and
161
+ <code className="font-mono text-xs mx-1">/api/fn/orgProjects</code>
162
+ with this org&apos;s id. Both queries enforce membership; you only
163
+ see what your role allows.
164
+ </p>
165
+ <div className="text-xs font-mono text-neutral-400">
166
+ orgId: {org.id}
167
+ </div>
168
+ </CardContent>
169
+ </Card>
170
+ );
171
+ }
@@ -0,0 +1,6 @@
1
+ @import "tailwindcss";
2
+ @source "../../../../packages/ui/src/**/*.{ts,tsx}";
3
+
4
+ :root { color-scheme: light dark; }
5
+ html, body { height: 100%; }
6
+ body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "__APP_NAME__",
6
+ description: "Multi-tenant SaaS scaffold powered by Pylon",
7
+ };
8
+
9
+ export default function RootLayout({
10
+ children,
11
+ }: {
12
+ children: React.ReactNode;
13
+ }) {
14
+ return (
15
+ <html lang="en">
16
+ <body className="antialiased min-h-screen bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
17
+ {children}
18
+ </body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,39 @@
1
+ import { pylon } from "@/lib/pylon";
2
+ import { OrgPicker } from "./components/OrgPicker";
3
+
4
+ export const dynamic = "force-dynamic";
5
+
6
+ type Org = {
7
+ id: string;
8
+ slug: string;
9
+ name: string;
10
+ role: string;
11
+ createdAt: string;
12
+ };
13
+
14
+ export default async function HomePage() {
15
+ const orgs = await pylon
16
+ .json<Org[]>("/api/fn/myOrgs", {
17
+ method: "POST",
18
+ body: "{}",
19
+ headers: { "Content-Type": "application/json" },
20
+ })
21
+ .catch(() => [] as Org[]);
22
+
23
+ return (
24
+ <main className="mx-auto max-w-3xl px-6 py-12 space-y-8">
25
+ <header className="space-y-2">
26
+ <h1 className="text-3xl font-semibold tracking-tight">__APP_NAME__</h1>
27
+ <p className="text-sm text-neutral-500 dark:text-neutral-400">
28
+ Multi-tenant SaaS scaffold. Pick an org to manage its members and
29
+ projects, or create a new one. Tenant isolation is enforced
30
+ server-side by Pylon row-level policies — clients can&apos;t leak
31
+ data across orgs even if they fabricate <code className="font-mono text-xs">orgId</code>{" "}
32
+ in a request.
33
+ </p>
34
+ </header>
35
+
36
+ <OrgPicker initialOrgs={orgs} />
37
+ </main>
38
+ );
39
+ }
@@ -0,0 +1,5 @@
1
+ import { createPylonServer } from "@pylonsync/next/server";
2
+
3
+ export const pylon = createPylonServer({
4
+ cookieName: "__APP_NAME_SNAKE___session",
5
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./src/*"] }
18
+ },
19
+ "include": [
20
+ "next-env.d.ts",
21
+ "src/**/*.ts",
22
+ "src/**/*.tsx",
23
+ ".next/types/**/*.ts"
24
+ ],
25
+ "exclude": ["node_modules"]
26
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
@@ -0,0 +1,40 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ /**
4
+ * Pylon's typed client + functions packages re-export across the
5
+ * server/client boundary AND the workspace UI package ships TSX.
6
+ * `transpilePackages` makes Next bundle them cleanly.
7
+ *
8
+ * `rewrites` proxies every Pylon-owned path (`/api/fn/*`,
9
+ * `/api/auth/*`, `/api/sync/*`, …) to the Pylon binary running on
10
+ * `PYLON_API_URL` (default http://localhost:4321). Without this,
11
+ * Next.js sees `/api/fn/createWidget` as a missing route and 404s
12
+ * before the request reaches Pylon.
13
+ *
14
+ * In production set `PYLON_API_URL` to wherever you've deployed the
15
+ * Pylon binary (Fly, Render, Railway, your own box). The browser
16
+ * still hits same-origin paths under your Next deployment, and Next
17
+ * forwards them server-side — no CORS, no extra DNS.
18
+ */
19
+ const PYLON_API_URL = process.env.PYLON_API_URL ?? "http://localhost:4321";
20
+
21
+ const config: NextConfig = {
22
+ transpilePackages: [
23
+ "@__APP_NAME_KEBAB__/ui",
24
+ "@pylonsync/sdk",
25
+ "@pylonsync/react",
26
+ "@pylonsync/next",
27
+ "@pylonsync/functions",
28
+ "@pylonsync/sync",
29
+ ],
30
+ async rewrites() {
31
+ return [
32
+ { source: "/api/fn/:path*", destination: `${PYLON_API_URL}/api/fn/:path*` },
33
+ { source: "/api/auth/:path*", destination: `${PYLON_API_URL}/api/auth/:path*` },
34
+ { source: "/api/sync/:path*", destination: `${PYLON_API_URL}/api/sync/:path*` },
35
+ { source: "/api/:path*", destination: `${PYLON_API_URL}/api/:path*` },
36
+ ];
37
+ },
38
+ };
39
+
40
+ export default config;
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@__APP_NAME_KEBAB__/web",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "next dev --port 3000",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "next lint"
11
+ },
12
+ "dependencies": {
13
+ "@__APP_NAME_KEBAB__/ui": "__WORKSPACE_DEP__",
14
+ "@pylonsync/sdk": "^__PYLON_VERSION__",
15
+ "@pylonsync/react": "^__PYLON_VERSION__",
16
+ "@pylonsync/next": "^__PYLON_VERSION__",
17
+ "next": "^16.0.0",
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0",
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0",
25
+ "@tailwindcss/postcss": "^4.0.0",
26
+ "tailwindcss": "^4.0.0",
27
+ "typescript": "^5.5.0"
28
+ }
29
+ }
@@ -0,0 +1,4 @@
1
+ /** Tailwind v4 PostCSS pipeline. */
2
+ export default {
3
+ plugins: { "@tailwindcss/postcss": {} },
4
+ };
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import { useState, useTransition } from "react";
4
+ import { Button, Input } from "@__APP_NAME_KEBAB__/ui";
5
+
6
+ type Widget = {
7
+ id: string;
8
+ name: string;
9
+ count: number;
10
+ createdAt: string;
11
+ };
12
+
13
+ export function WidgetList({ initialWidgets }: { initialWidgets: Widget[] }) {
14
+ const [widgets, setWidgets] = useState(initialWidgets);
15
+ const [name, setName] = useState("");
16
+ const [pending, startTransition] = useTransition();
17
+
18
+ async function add() {
19
+ const trimmed = name.trim();
20
+ if (!trimmed) return;
21
+ setName("");
22
+ startTransition(async () => {
23
+ const res = await fetch("/api/fn/createWidget", {
24
+ method: "POST",
25
+ headers: { "Content-Type": "application/json" },
26
+ body: JSON.stringify({ name: trimmed }),
27
+ });
28
+ if (res.ok) {
29
+ const widget = (await res.json()) as Widget;
30
+ setWidgets((prev) => [widget, ...prev]);
31
+ }
32
+ });
33
+ }
34
+
35
+ return (
36
+ <div className="space-y-4">
37
+ <form
38
+ onSubmit={(e) => {
39
+ e.preventDefault();
40
+ add();
41
+ }}
42
+ className="flex gap-2"
43
+ >
44
+ <Input
45
+ value={name}
46
+ onChange={(e) => setName(e.target.value)}
47
+ placeholder="Name a widget…"
48
+ disabled={pending}
49
+ className="flex-1"
50
+ />
51
+ <Button
52
+ type="submit"
53
+ variant="primary"
54
+ disabled={pending || !name.trim()}
55
+ >
56
+ Create
57
+ </Button>
58
+ </form>
59
+
60
+ {widgets.length === 0 ? (
61
+ <p className="text-sm text-neutral-500 dark:text-neutral-400 text-center py-8">
62
+ No widgets yet. Create one above.
63
+ </p>
64
+ ) : (
65
+ <ul className="divide-y divide-neutral-200 dark:divide-neutral-800 rounded-md border border-neutral-200 dark:border-neutral-800">
66
+ {widgets.map((w) => (
67
+ <li
68
+ key={w.id}
69
+ className="flex items-center justify-between gap-3 px-4 py-3 text-sm bg-white dark:bg-neutral-950"
70
+ >
71
+ <span className="font-medium">{w.name}</span>
72
+ <span className="font-mono text-xs text-neutral-400">
73
+ count: {w.count}
74
+ </span>
75
+ </li>
76
+ ))}
77
+ </ul>
78
+ )}
79
+ </div>
80
+ );
81
+ }
@@ -0,0 +1,9 @@
1
+ @import "tailwindcss";
2
+ @source "../../../../packages/ui/src/**/*.{ts,tsx}";
3
+
4
+ :root {
5
+ color-scheme: light dark;
6
+ }
7
+
8
+ html, body { height: 100%; }
9
+ body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
@@ -0,0 +1,21 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "__APP_NAME__",
6
+ description: "Realtime app powered by Pylon",
7
+ };
8
+
9
+ export default function RootLayout({
10
+ children,
11
+ }: {
12
+ children: React.ReactNode;
13
+ }) {
14
+ return (
15
+ <html lang="en">
16
+ <body className="antialiased min-h-screen bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
17
+ {children}
18
+ </body>
19
+ </html>
20
+ );
21
+ }
@@ -0,0 +1,43 @@
1
+ import { pylon } from "@/lib/pylon";
2
+ import { WidgetList } from "./components/WidgetList";
3
+
4
+ // Force dynamic — every render reads the live widget list from Pylon.
5
+ export const dynamic = "force-dynamic";
6
+
7
+ type Widget = {
8
+ id: string;
9
+ name: string;
10
+ count: number;
11
+ createdAt: string;
12
+ };
13
+
14
+ export default async function HomePage() {
15
+ const widgets = await pylon
16
+ .json<Widget[]>("/api/fn/listWidgets", {
17
+ method: "POST",
18
+ body: "{}",
19
+ headers: { "Content-Type": "application/json" },
20
+ })
21
+ .catch(() => [] as Widget[]);
22
+
23
+ return (
24
+ <main className="mx-auto max-w-2xl px-6 py-12 space-y-8">
25
+ <header className="space-y-2">
26
+ <h1 className="text-3xl font-semibold tracking-tight">__APP_NAME__</h1>
27
+ <p className="text-sm text-neutral-500 dark:text-neutral-400">
28
+ A bare-bones Pylon app. Edit{" "}
29
+ <code className="font-mono text-xs">apps/api/schema.ts</code> to
30
+ change the data model,{" "}
31
+ <code className="font-mono text-xs">apps/api/functions/</code> to
32
+ add handlers, or{" "}
33
+ <code className="font-mono text-xs">
34
+ apps/web/src/app/components/WidgetList.tsx
35
+ </code>{" "}
36
+ for the UI.
37
+ </p>
38
+ </header>
39
+
40
+ <WidgetList initialWidgets={widgets} />
41
+ </main>
42
+ );
43
+ }
@@ -0,0 +1,14 @@
1
+ import { createPylonServer } from "@pylonsync/next/server";
2
+
3
+ /**
4
+ * Single server-helper instance. Imported by every Server Component
5
+ * and Server Action that needs to talk to the Pylon control plane.
6
+ *
7
+ * `cookieName` MUST match the backend's emitted cookie. Pylon uses
8
+ * `${app_name}_session` from the manifest — for this app that's
9
+ * `__APP_NAME_SNAKE___session`. Pin it in code (NOT env) so a bad
10
+ * deployment env can't silently break auth.
11
+ */
12
+ export const pylon = createPylonServer({
13
+ cookieName: "__APP_NAME_SNAKE___session",
14
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": { "@/*": ["./src/*"] }
18
+ },
19
+ "include": [
20
+ "next-env.d.ts",
21
+ "src/**/*.ts",
22
+ "src/**/*.tsx",
23
+ ".next/types/**/*.ts"
24
+ ],
25
+ "exclude": ["node_modules"]
26
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
@@ -0,0 +1,24 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const PYLON_API_URL = process.env.PYLON_API_URL ?? "http://localhost:4321";
4
+
5
+ const config: NextConfig = {
6
+ transpilePackages: [
7
+ "@__APP_NAME_KEBAB__/ui",
8
+ "@pylonsync/sdk",
9
+ "@pylonsync/react",
10
+ "@pylonsync/next",
11
+ "@pylonsync/functions",
12
+ "@pylonsync/sync",
13
+ ],
14
+ async rewrites() {
15
+ return [
16
+ { source: "/api/fn/:path*", destination: `${PYLON_API_URL}/api/fn/:path*` },
17
+ { source: "/api/auth/:path*", destination: `${PYLON_API_URL}/api/auth/:path*` },
18
+ { source: "/api/sync/:path*", destination: `${PYLON_API_URL}/api/sync/:path*` },
19
+ { source: "/api/:path*", destination: `${PYLON_API_URL}/api/:path*` },
20
+ ];
21
+ },
22
+ };
23
+
24
+ export default config;
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@__APP_NAME_KEBAB__/web",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "next dev --port 3000",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "next lint"
11
+ },
12
+ "dependencies": {
13
+ "@__APP_NAME_KEBAB__/ui": "__WORKSPACE_DEP__",
14
+ "@pylonsync/sdk": "^__PYLON_VERSION__",
15
+ "@pylonsync/react": "^__PYLON_VERSION__",
16
+ "@pylonsync/next": "^__PYLON_VERSION__",
17
+ "next": "^16.0.0",
18
+ "react": "^19.0.0",
19
+ "react-dom": "^19.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0",
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0",
25
+ "@tailwindcss/postcss": "^4.0.0",
26
+ "tailwindcss": "^4.0.0",
27
+ "typescript": "^5.5.0"
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export default {
2
+ plugins: { "@tailwindcss/postcss": {} },
3
+ };