@pylonsync/create-pylon 0.3.16

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 (2) hide show
  1. package/bin/create-pylon.js +615 -0
  2. package/package.json +33 -0
@@ -0,0 +1,615 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @pylonsync/create-pylon — scaffold a new Pylon app.
4
+ *
5
+ * Run via `npm create @pylonsync/pylon@latest [name]` (or yarn/pnpm/bun
6
+ * create @pylonsync/pylon).
7
+ *
8
+ * Generates a workspace with two packages:
9
+ * - api/ — Pylon backend (schema + functions; runs `pylon dev` from
10
+ * the @pylonsync/cli npm package, no global binary required)
11
+ * - web/ — Next.js 16 + React 19 frontend wired to @pylonsync/react
12
+ *
13
+ * Node-runnable (no Bun required) so `npm create` works for every
14
+ * package manager. Uses only Node-builtin APIs — no runtime deps.
15
+ */
16
+
17
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
18
+ import { dirname, join, resolve } from "node:path";
19
+ import { createInterface } from "node:readline/promises";
20
+ import { stdin, stdout, exit, argv, cwd } from "node:process";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Version pin — every generated dep references this version of @pylonsync/*.
24
+ // Bumped via the workspace's release-please flow (same version as the rest
25
+ // of the pylon stack).
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const PYLON_VERSION = "0.3.16";
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // CLI args + interactive prompt
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const args = argv.slice(2);
35
+ let projectName = args.find((a) => !a.startsWith("--"));
36
+
37
+ const flags = {
38
+ pm:
39
+ args.find((a) => a === "--bun" || a === "--pnpm" || a === "--yarn" || a === "--npm")?.slice(2) ??
40
+ detectPackageManager(),
41
+ skipInstall: args.includes("--skip-install"),
42
+ help: args.includes("--help") || args.includes("-h"),
43
+ };
44
+
45
+ if (flags.help) {
46
+ process.stdout.write(`\nUsage: npm create @pylonsync/pylon [name] [--bun|--pnpm|--yarn|--npm] [--skip-install]\n\n`);
47
+ exit(0);
48
+ }
49
+
50
+ if (!projectName) {
51
+ const rl = createInterface({ input: stdin, output: stdout });
52
+ projectName = (await rl.question("Project name: ")).trim() || "my-pylon-app";
53
+ rl.close();
54
+ }
55
+
56
+ const root = resolve(cwd(), projectName);
57
+
58
+ if (existsSync(root) && readdirSync(root).length > 0) {
59
+ console.error(`\nError: ${root} already exists and is not empty.\n`);
60
+ exit(1);
61
+ }
62
+
63
+ console.log(`\nCreating ${projectName} in ${root}\n`);
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // File-tree generator — every `write(path, content)` call creates parent
67
+ // dirs as needed and writes UTF-8 text. Keeping the scaffold inline (no
68
+ // template files) means create-pylon stays a single zero-dep file.
69
+ // ---------------------------------------------------------------------------
70
+
71
+ function write(path, content) {
72
+ const full = join(root, path);
73
+ mkdirSync(dirname(full), { recursive: true });
74
+ writeFileSync(full, content);
75
+ }
76
+
77
+ function writeJson(path, value) {
78
+ write(path, JSON.stringify(value, null, 2) + "\n");
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Root workspace
83
+ // ---------------------------------------------------------------------------
84
+
85
+ writeJson("package.json", {
86
+ name: projectName,
87
+ private: true,
88
+ type: "module",
89
+ workspaces: ["api", "web"],
90
+ scripts: {
91
+ dev: "npm-run-all --parallel dev:api dev:web",
92
+ "dev:api": "npm --workspace api run dev",
93
+ "dev:web": "npm --workspace web run dev",
94
+ build: "npm --workspaces run build --if-present",
95
+ },
96
+ devDependencies: {
97
+ "npm-run-all": "^4.1.5",
98
+ },
99
+ });
100
+
101
+ write(".gitignore", `node_modules/
102
+ .next/
103
+ .turbo/
104
+ dist/
105
+ out/
106
+ .env
107
+ .env.local
108
+ *.db
109
+ *.db-journal
110
+ api/pylon.manifest.json
111
+ api/pylon.client.ts
112
+ `);
113
+
114
+ write(".env.example", `# Backend port the Pylon control plane listens on.
115
+ PYLON_PORT=4321
116
+
117
+ # Where the Next.js dev server can reach the control plane.
118
+ PYLON_TARGET=http://localhost:4321
119
+
120
+ # Cookie name the auth helpers look for.
121
+ # Pattern: \`\${app_name}_session\` from the Pylon manifest.
122
+ PYLON_COOKIE_NAME=${projectName}_session
123
+ `);
124
+
125
+ write(
126
+ "README.md",
127
+ `# ${projectName}
128
+
129
+ Realtime backend + Next.js dashboard, scaffolded by [create-pylon](https://npmjs.com/create-pylon).
130
+
131
+ ## Getting started
132
+
133
+ \`\`\`sh
134
+ ${flags.pm === "npm" ? "npm install" : `${flags.pm} install`}
135
+ ${flags.pm === "npm" ? "npm run dev" : `${flags.pm} run dev`}
136
+ \`\`\`
137
+
138
+ That spins up two processes:
139
+
140
+ - **api** on http://localhost:4321 — Pylon control plane (schema, queries,
141
+ mutations, live sync, auth)
142
+ - **web** on http://localhost:3000 — Next.js 16 frontend wired to the API
143
+ via [\`@pylonsync/react\`](https://npmjs.com/package/@pylonsync/react)
144
+
145
+ ## Project layout
146
+
147
+ \`\`\`
148
+ api/
149
+ schema.ts entities + policies + manifest
150
+ functions/ TS query / mutation / action handlers
151
+ pylon.manifest.json (codegen — gitignored)
152
+ pylon.client.ts (typed client codegen — gitignored)
153
+
154
+ web/
155
+ src/app/ Next.js app-router pages
156
+ src/lib/pylon.ts Pylon server helper (cookie-attached fetches)
157
+ \`\`\`
158
+
159
+ ## What to do next
160
+
161
+ - Edit \`api/schema.ts\` to add your entities + policies.
162
+ - Add TS handlers to \`api/functions/\` — they're auto-discovered.
163
+ - Edit \`web/src/app/page.tsx\` — it uses the typed client codegen
164
+ produced from your manifest.
165
+
166
+ ## Docs
167
+
168
+ [pylonsync.com/docs](https://pylonsync.com/docs)
169
+ `,
170
+ );
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // api/ — the Pylon control plane
174
+ // ---------------------------------------------------------------------------
175
+
176
+ writeJson("api/package.json", {
177
+ name: `${projectName}-api`,
178
+ version: "0.0.1",
179
+ private: true,
180
+ type: "module",
181
+ scripts: {
182
+ dev: "pylon dev schema.ts --port 4321",
183
+ build: "pylon codegen schema.ts --out pylon.manifest.json && pylon codegen client pylon.manifest.json --out pylon.client.ts",
184
+ "schema:push": "pylon schema push pylon.manifest.json --sqlite dev.db",
185
+ "schema:inspect": "pylon schema inspect --sqlite dev.db",
186
+ },
187
+ dependencies: {
188
+ "@pylonsync/sdk": `^${PYLON_VERSION}`,
189
+ "@pylonsync/functions": `^${PYLON_VERSION}`,
190
+ },
191
+ devDependencies: {
192
+ "@pylonsync/cli": `^${PYLON_VERSION}`,
193
+ typescript: "^5.5.0",
194
+ },
195
+ });
196
+
197
+ writeJson("api/tsconfig.json", {
198
+ compilerOptions: {
199
+ target: "ES2022",
200
+ module: "ESNext",
201
+ moduleResolution: "Bundler",
202
+ strict: true,
203
+ skipLibCheck: true,
204
+ noEmit: true,
205
+ esModuleInterop: true,
206
+ allowSyntheticDefaultImports: true,
207
+ },
208
+ include: ["schema.ts", "functions/**/*.ts"],
209
+ });
210
+
211
+ write(
212
+ "api/schema.ts",
213
+ `import { entity, field, defineRoute, query, action, policy, buildManifest } from "@pylonsync/sdk";
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Schema
217
+ // ---------------------------------------------------------------------------
218
+
219
+ const Todo = entity("Todo", {
220
+ \ttitle: field.string(),
221
+ \tdone: field.bool().default(false),
222
+ \tcreatedAt: field.datetime().default("now"),
223
+ });
224
+
225
+ // ---------------------------------------------------------------------------
226
+ // Queries / mutations
227
+ // ---------------------------------------------------------------------------
228
+
229
+ const listTodos = query("listTodos", {
230
+ \thandler: \`
231
+ \t\tasync (ctx) => {
232
+ \t\t\treturn await ctx.db.query("Todo", { $order: { createdAt: "desc" } });
233
+ \t\t}
234
+ \t\`,
235
+ });
236
+
237
+ const addTodo = action("addTodo", {
238
+ \targs: { title: { type: "string" } },
239
+ \thandler: \`
240
+ \t\tasync (ctx, args) => {
241
+ \t\t\treturn await ctx.db.insert("Todo", {
242
+ \t\t\t\ttitle: args.title,
243
+ \t\t\t\tdone: false,
244
+ \t\t\t\tcreatedAt: new Date().toISOString(),
245
+ \t\t\t});
246
+ \t\t}
247
+ \t\`,
248
+ });
249
+
250
+ // ---------------------------------------------------------------------------
251
+ // Policies — wide-open by default. Tighten before production.
252
+ // ---------------------------------------------------------------------------
253
+
254
+ const todoPolicy = policy({
255
+ \tname: "todo_open",
256
+ \tentity: "Todo",
257
+ \tallowRead: "true",
258
+ \tallowInsert: "true",
259
+ \tallowUpdate: "true",
260
+ \tallowDelete: "true",
261
+ });
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // Manifest — codegen reads this and emits pylon.manifest.json
265
+ // ---------------------------------------------------------------------------
266
+
267
+ export default buildManifest({
268
+ \tname: "${projectName}",
269
+ \tversion: "0.0.1",
270
+ \tentities: [Todo],
271
+ \tqueries: [listTodos],
272
+ \tactions: [addTodo],
273
+ \tpolicies: [todoPolicy],
274
+ \troutes: [],
275
+ });
276
+ `,
277
+ );
278
+
279
+ // ---------------------------------------------------------------------------
280
+ // web/ — Next.js 16 + React 19 + Tailwind v4 + @pylonsync/react
281
+ // ---------------------------------------------------------------------------
282
+
283
+ writeJson("web/package.json", {
284
+ name: `${projectName}-web`,
285
+ version: "0.0.1",
286
+ private: true,
287
+ type: "module",
288
+ scripts: {
289
+ dev: "next dev --port 3000",
290
+ build: "next build",
291
+ start: "next start",
292
+ lint: "next lint",
293
+ },
294
+ dependencies: {
295
+ "@pylonsync/sdk": `^${PYLON_VERSION}`,
296
+ "@pylonsync/react": `^${PYLON_VERSION}`,
297
+ "@pylonsync/next": `^${PYLON_VERSION}`,
298
+ next: "^16.0.0",
299
+ react: "^19.0.0",
300
+ "react-dom": "^19.0.0",
301
+ },
302
+ devDependencies: {
303
+ "@types/react": "^19.0.0",
304
+ "@types/react-dom": "^19.0.0",
305
+ "@types/node": "^20.0.0",
306
+ "@tailwindcss/postcss": "^4.0.0",
307
+ tailwindcss: "^4.0.0",
308
+ typescript: "^5.5.0",
309
+ },
310
+ });
311
+
312
+ writeJson("web/tsconfig.json", {
313
+ compilerOptions: {
314
+ target: "ES2022",
315
+ lib: ["dom", "dom.iterable", "esnext"],
316
+ allowJs: true,
317
+ skipLibCheck: true,
318
+ strict: true,
319
+ noEmit: true,
320
+ esModuleInterop: true,
321
+ module: "esnext",
322
+ moduleResolution: "bundler",
323
+ resolveJsonModule: true,
324
+ isolatedModules: true,
325
+ jsx: "preserve",
326
+ incremental: true,
327
+ plugins: [{ name: "next" }],
328
+ paths: { "@/*": ["./src/*"] },
329
+ },
330
+ include: ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"],
331
+ exclude: ["node_modules"],
332
+ });
333
+
334
+ write(
335
+ "web/next.config.ts",
336
+ `import type { NextConfig } from "next";
337
+
338
+ /**
339
+ * Pylon's typed client + functions packages re-export across the
340
+ * server/client boundary; \`transpilePackages\` makes Next bundle them
341
+ * cleanly from the workspace.
342
+ */
343
+ const config: NextConfig = {
344
+ \ttranspilePackages: [
345
+ \t\t"@pylonsync/sdk",
346
+ \t\t"@pylonsync/react",
347
+ \t\t"@pylonsync/next",
348
+ \t\t"@pylonsync/functions",
349
+ \t\t"@pylonsync/sync",
350
+ \t],
351
+ };
352
+
353
+ export default config;
354
+ `,
355
+ );
356
+
357
+ write(
358
+ "web/postcss.config.mjs",
359
+ `/** Tailwind v4 PostCSS pipeline. */
360
+ export default {
361
+ \tplugins: { "@tailwindcss/postcss": {} },
362
+ };
363
+ `,
364
+ );
365
+
366
+ write(
367
+ "web/src/app/globals.css",
368
+ `@import "tailwindcss";
369
+
370
+ :root {
371
+ \tcolor-scheme: light dark;
372
+ }
373
+
374
+ html, body { height: 100%; }
375
+ body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; }
376
+ `,
377
+ );
378
+
379
+ write(
380
+ "web/src/app/layout.tsx",
381
+ `import type { Metadata } from "next";
382
+ import "./globals.css";
383
+
384
+ export const metadata: Metadata = {
385
+ \ttitle: "${projectName}",
386
+ \tdescription: "Realtime app powered by Pylon",
387
+ };
388
+
389
+ export default function RootLayout({
390
+ \tchildren,
391
+ }: {
392
+ \tchildren: React.ReactNode;
393
+ }) {
394
+ \treturn (
395
+ \t\t<html lang="en">
396
+ \t\t\t<body className="antialiased min-h-screen bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-100">
397
+ \t\t\t\t{children}
398
+ \t\t\t</body>
399
+ \t\t</html>
400
+ \t);
401
+ }
402
+ `,
403
+ );
404
+
405
+ write(
406
+ "web/src/lib/pylon.ts",
407
+ `import { createPylonServer } from "@pylonsync/next/server";
408
+
409
+ /**
410
+ * Single server-helper instance. Imported by every Server Component
411
+ * and Server Action that needs to talk to the Pylon control plane.
412
+ *
413
+ * \`cookieName\` MUST match the backend's emitted cookie. Pylon uses
414
+ * \`\${app_name}_session\` from the manifest — for this app that's
415
+ * \`${projectName}_session\`. Pin it in code (NOT env) so a bad
416
+ * deployment env can't silently break auth.
417
+ */
418
+ export const pylon = createPylonServer({
419
+ \tcookieName: "${projectName}_session",
420
+ });
421
+ `,
422
+ );
423
+
424
+ write(
425
+ "web/src/app/page.tsx",
426
+ `import { pylon } from "@/lib/pylon";
427
+ import { TodoList } from "./TodoList";
428
+
429
+ // Force dynamic — every render reads the live todo list from Pylon.
430
+ // Without this Next would try to statically generate the page and
431
+ // the cookie-attached fetch in pylon.json would error at build time.
432
+ export const dynamic = "force-dynamic";
433
+
434
+ type Todo = {
435
+ \tid: string;
436
+ \ttitle: string;
437
+ \tdone: boolean;
438
+ \tcreatedAt: string;
439
+ };
440
+
441
+ export default async function HomePage() {
442
+ \tconst todos = await pylon
443
+ \t\t.json<Todo[]>("/api/fn/listTodos", { method: "POST", body: "{}", headers: { "Content-Type": "application/json" } })
444
+ \t\t.catch(() => [] as Todo[]);
445
+
446
+ \treturn (
447
+ \t\t<main className="mx-auto max-w-2xl px-6 py-12 space-y-8">
448
+ \t\t\t<header className="space-y-2">
449
+ \t\t\t\t<h1 className="text-3xl font-semibold tracking-tight">${projectName}</h1>
450
+ \t\t\t\t<p className="text-sm text-neutral-500 dark:text-neutral-400">
451
+ \t\t\t\t\tA Pylon-powered realtime app. Edit{" "}
452
+ \t\t\t\t\t<code className="font-mono text-xs">api/schema.ts</code> to change the
453
+ \t\t\t\t\tdata model or{" "}
454
+ \t\t\t\t\t<code className="font-mono text-xs">web/src/app/page.tsx</code> for
455
+ \t\t\t\t\tthe UI.
456
+ \t\t\t\t</p>
457
+ \t\t\t</header>
458
+
459
+ \t\t\t<TodoList initialTodos={todos} />
460
+ \t\t</main>
461
+ \t);
462
+ }
463
+ `,
464
+ );
465
+
466
+ write(
467
+ "web/src/app/TodoList.tsx",
468
+ `"use client";
469
+
470
+ import { useState, useTransition } from "react";
471
+
472
+ type Todo = {
473
+ \tid: string;
474
+ \ttitle: string;
475
+ \tdone: boolean;
476
+ \tcreatedAt: string;
477
+ };
478
+
479
+ /**
480
+ * Optimistic todo list — local state mirrors the server-fetched
481
+ * initial list and refreshes on every successful add. For full
482
+ * real-time updates wire \`@pylonsync/react\`'s \`useQuery\` hook
483
+ * (see https://pylonsync.com/docs/clients/react).
484
+ */
485
+ export function TodoList({ initialTodos }: { initialTodos: Todo[] }) {
486
+ \tconst [todos, setTodos] = useState(initialTodos);
487
+ \tconst [title, setTitle] = useState("");
488
+ \tconst [pending, startTransition] = useTransition();
489
+
490
+ \tasync function add() {
491
+ \t\tif (!title.trim()) return;
492
+ \t\tconst newTitle = title;
493
+ \t\tsetTitle("");
494
+ \t\tstartTransition(async () => {
495
+ \t\t\tconst res = await fetch("/api/fn/addTodo", {
496
+ \t\t\t\tmethod: "POST",
497
+ \t\t\t\theaders: { "Content-Type": "application/json" },
498
+ \t\t\t\tbody: JSON.stringify({ title: newTitle }),
499
+ \t\t\t});
500
+ \t\t\tif (res.ok) {
501
+ \t\t\t\tconst todo = (await res.json()) as Todo;
502
+ \t\t\t\tsetTodos([todo, ...todos]);
503
+ \t\t\t}
504
+ \t\t});
505
+ \t}
506
+
507
+ \treturn (
508
+ \t\t<div className="space-y-4">
509
+ \t\t\t<form
510
+ \t\t\t\tonSubmit={(e) => {
511
+ \t\t\t\t\te.preventDefault();
512
+ \t\t\t\t\tadd();
513
+ \t\t\t\t}}
514
+ \t\t\t\tclassName="flex gap-2"
515
+ \t\t\t>
516
+ \t\t\t\t<input
517
+ \t\t\t\t\tvalue={title}
518
+ \t\t\t\t\tonChange={(e) => setTitle(e.target.value)}
519
+ \t\t\t\t\tplaceholder="What needs doing?"
520
+ \t\t\t\t\tclassName="flex-1 rounded-md border border-neutral-300 dark:border-neutral-700 bg-white dark:bg-neutral-900 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
521
+ \t\t\t\t\tdisabled={pending}
522
+ \t\t\t\t/>
523
+ \t\t\t\t<button
524
+ \t\t\t\t\ttype="submit"
525
+ \t\t\t\t\tclassName="rounded-md bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 px-4 py-2 text-sm font-medium disabled:opacity-50"
526
+ \t\t\t\t\tdisabled={pending || !title.trim()}
527
+ \t\t\t\t>
528
+ \t\t\t\t\tAdd
529
+ \t\t\t\t</button>
530
+ \t\t\t</form>
531
+
532
+ \t\t\t{todos.length === 0 ? (
533
+ \t\t\t\t<p className="text-sm text-neutral-500 dark:text-neutral-400 text-center py-8">
534
+ \t\t\t\t\tNo todos yet. Add one above.
535
+ \t\t\t\t</p>
536
+ \t\t\t) : (
537
+ \t\t\t\t<ul className="divide-y divide-neutral-200 dark:divide-neutral-800 rounded-md border border-neutral-200 dark:border-neutral-800">
538
+ \t\t\t\t\t{todos.map((t) => (
539
+ \t\t\t\t\t\t<li
540
+ \t\t\t\t\t\t\tkey={t.id}
541
+ \t\t\t\t\t\t\tclassName="flex items-center gap-3 px-4 py-3 text-sm"
542
+ \t\t\t\t\t\t>
543
+ \t\t\t\t\t\t\t<span className={t.done ? "line-through text-neutral-400" : ""}>
544
+ \t\t\t\t\t\t\t\t{t.title}
545
+ \t\t\t\t\t\t\t</span>
546
+ \t\t\t\t\t\t</li>
547
+ \t\t\t\t\t))}
548
+ \t\t\t\t</ul>
549
+ \t\t\t)}
550
+ \t\t</div>
551
+ \t);
552
+ }
553
+ `,
554
+ );
555
+
556
+ write(
557
+ "web/next-env.d.ts",
558
+ `/// <reference types="next" />
559
+ /// <reference types="next/image-types/global" />
560
+ `,
561
+ );
562
+
563
+ // ---------------------------------------------------------------------------
564
+ // Detect package manager — read npm_config_user_agent set by the runner.
565
+ // ---------------------------------------------------------------------------
566
+
567
+ function detectPackageManager() {
568
+ const ua = process.env.npm_config_user_agent ?? "";
569
+ if (ua.startsWith("bun")) return "bun";
570
+ if (ua.startsWith("pnpm")) return "pnpm";
571
+ if (ua.startsWith("yarn")) return "yarn";
572
+ return "npm";
573
+ }
574
+
575
+ // ---------------------------------------------------------------------------
576
+ // Optional: install dependencies
577
+ // ---------------------------------------------------------------------------
578
+
579
+ if (!flags.skipInstall) {
580
+ console.log(`Installing dependencies with ${flags.pm}...`);
581
+ const { spawnSync } = await import("node:child_process");
582
+ const result = spawnSync(flags.pm, ["install"], {
583
+ cwd: root,
584
+ stdio: "inherit",
585
+ });
586
+ if (result.status !== 0) {
587
+ console.warn(
588
+ `\n${flags.pm} install exited with code ${result.status}. Re-run from ${projectName}/.\n`,
589
+ );
590
+ }
591
+ }
592
+
593
+ // ---------------------------------------------------------------------------
594
+ // Final instructions
595
+ // ---------------------------------------------------------------------------
596
+
597
+ const runDev = flags.pm === "npm" ? "npm run dev" : `${flags.pm} run dev`;
598
+
599
+ console.log(`
600
+ ✓ Created ${projectName}
601
+
602
+ cd ${projectName}
603
+ ${runDev}
604
+
605
+ → api http://localhost:4321 (Pylon control plane)
606
+ → web http://localhost:3000 (Next.js dashboard)
607
+
608
+ Next:
609
+ - Edit api/schema.ts to add entities + policies.
610
+ - Drop TypeScript handlers into api/functions/ — auto-discovered.
611
+ - The Next page at web/src/app/page.tsx talks to the API via the
612
+ cookie-attached helper in web/src/lib/pylon.ts.
613
+
614
+ Docs: https://pylonsync.com/docs
615
+ `);
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@pylonsync/create-pylon",
3
+ "version": "0.3.16",
4
+ "description": "Scaffold a new Pylon app — realtime backend + Next.js frontend in one command. Run via `npm create @pylonsync/pylon@latest`.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "bin": {
10
+ "create-pylon": "./bin/create-pylon.js"
11
+ },
12
+ "files": [
13
+ "bin"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "keywords": [
19
+ "pylon",
20
+ "scaffold",
21
+ "create",
22
+ "nextjs",
23
+ "realtime",
24
+ "backend"
25
+ ],
26
+ "homepage": "https://pylonsync.com",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/pylonsync/pylon",
30
+ "directory": "packages/create-pylon"
31
+ },
32
+ "license": "MIT"
33
+ }