create-pyric 0.1.0-alpha.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.
@@ -0,0 +1,611 @@
1
+ /**
2
+ * Scaffold templates for `pyric init` (engine in `./init.js`).
3
+ *
4
+ * `web` (the default) scaffolds a **Vite app** wired to the `@pyric/cli/vite`
5
+ * plugin: `vite dev` runs the app's CANONICAL `firebase/*` imports against the
6
+ * in-process sandbox; `vite build` ships the real `firebase` package. One
7
+ * toolchain, no graduation cliff — the sandbox↔Firebase swap is environmental
8
+ * (dev vs build), never a code edit (the design rationale section 9).
9
+ *
10
+ * `static` is the serve-era scaffold (no bundler): a static app `pyric dev`
11
+ * runs against the in-page sandbox via a runtime import map. For pre-built /
12
+ * retrofit apps, or anyone who wants zero build step.
13
+ *
14
+ * `node` is the script-style scaffold (backend fixtures, agent loops). Its
15
+ * canonical imports are swapped by the dev command and remain Firebase under
16
+ * the production command.
17
+ */
18
+ // ─── web template ─────────────────────────────────────────────────────
19
+ const WEB_INDEX_HTML = (name) => `<!doctype html>
20
+ <html>
21
+ <head>
22
+ <meta charset="utf-8" />
23
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
24
+ <title>${name}</title>
25
+ <style>
26
+ body { font: 16px/1.5 system-ui, sans-serif; max-width: 640px; margin: 3rem auto; padding: 0 1rem; }
27
+ button { padding: 0.4rem 0.9rem; cursor: pointer; }
28
+ form { display: flex; gap: 0.5rem; margin: 1rem 0; }
29
+ input { flex: 1; padding: 0.4rem 0.6rem; }
30
+ ul { padding-left: 1.2rem; }
31
+ .status { color: #666; }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <main>
36
+ <h1>${name}</h1>
37
+ <p class="status" id="auth-status">Signed out</p>
38
+ <button id="sign-in">Sign in with Google</button>
39
+ <button id="sign-out" hidden>Sign out</button>
40
+ <form id="add-post" hidden>
41
+ <input id="post-title" placeholder="Post title" required />
42
+ <button type="submit">Add post</button>
43
+ </form>
44
+ <ul id="posts"></ul>
45
+ </main>
46
+ <script type="module" src="/app.js"></script>
47
+ </body>
48
+ </html>
49
+ `;
50
+ const WEB_APP_JS = `// Canonical firebase/* imports. Under \`pyric dev\` they are served by the
51
+ // in-page pyric sandbox (the config below is ignored); under any standard
52
+ // bundler/pipeline the same imports resolve to the real \`firebase\` package.
53
+ // Graduation changes where you run this code, never the code itself.
54
+ import { initializeApp } from 'firebase/app';
55
+ import {
56
+ getAuth,
57
+ onAuthStateChanged,
58
+ signInWithPopup,
59
+ signOut,
60
+ GoogleAuthProvider,
61
+ } from 'firebase/auth';
62
+ import {
63
+ getFirestore,
64
+ collection,
65
+ onSnapshot,
66
+ addDoc,
67
+ serverTimestamp,
68
+ } from 'firebase/firestore';
69
+
70
+ const app = initializeApp({
71
+ // Graduation: your real web-app config from the Firebase console
72
+ // (see .env.example). Unused while developing under \`pyric dev\`.
73
+ apiKey: 'demo',
74
+ authDomain: 'demo.firebaseapp.com',
75
+ projectId: 'demo',
76
+ });
77
+ const auth = getAuth(app);
78
+ const db = getFirestore(app);
79
+
80
+ const els = {
81
+ status: document.getElementById('auth-status'),
82
+ signIn: document.getElementById('sign-in'),
83
+ signOut: document.getElementById('sign-out'),
84
+ form: document.getElementById('add-post'),
85
+ title: document.getElementById('post-title'),
86
+ posts: document.getElementById('posts'),
87
+ };
88
+
89
+ els.signIn.addEventListener('click', () => signInWithPopup(auth, new GoogleAuthProvider()));
90
+ els.signOut.addEventListener('click', () => signOut(auth));
91
+
92
+ onAuthStateChanged(auth, (user) => {
93
+ els.status.textContent = user
94
+ ? 'Signed in as ' + (user.displayName ?? user.email)
95
+ : 'Signed out';
96
+ els.signIn.hidden = !!user;
97
+ els.signOut.hidden = !user;
98
+ els.form.hidden = !user;
99
+ });
100
+
101
+ els.form.addEventListener('submit', async (e) => {
102
+ e.preventDefault();
103
+ // The owner-based rules require uid == request.auth.uid on create.
104
+ await addDoc(collection(db, 'posts'), {
105
+ title: els.title.value.trim(),
106
+ uid: auth.currentUser.uid,
107
+ createdAt: serverTimestamp(),
108
+ });
109
+ els.title.value = '';
110
+ });
111
+
112
+ onSnapshot(collection(db, 'posts'), (snap) => {
113
+ els.posts.replaceChildren(
114
+ ...snap.docs.map((d) => {
115
+ const li = document.createElement('li');
116
+ li.textContent = d.data().title;
117
+ return li;
118
+ }),
119
+ );
120
+ });
121
+ `;
122
+ const WEB_RULES = `rules_version = '2';
123
+ service cloud.firestore {
124
+ match /databases/{database}/documents {
125
+ // Owner-based from line 1 — \`pyric dev\` hot-reloads this file and
126
+ // ships a sign-in helper, so safe rules are cheap to iterate. These
127
+ // deploy as-is.
128
+ match /posts/{postId} {
129
+ allow read: if true;
130
+ allow create: if request.auth != null
131
+ && request.resource.data.uid == request.auth.uid;
132
+ allow update, delete: if request.auth != null
133
+ && resource.data.uid == request.auth.uid;
134
+ }
135
+
136
+ // Default deny — opt in per collection.
137
+ match /{document=**} {
138
+ allow read, write: if false;
139
+ }
140
+ }
141
+ }
142
+ `;
143
+ const WEB_FIREBASE_JSON = `{
144
+ "firestore": {
145
+ "rules": "firestore.rules",
146
+ "indexes": "firestore.indexes.json"
147
+ },
148
+ "hosting": {
149
+ "public": "public",
150
+ "rewrites": [{ "source": "**", "destination": "/index.html" }]
151
+ }
152
+ }
153
+ `;
154
+ const WEB_SEED_JSON = `{
155
+ "posts/welcome": { "title": "Welcome to pyric", "uid": "seed" },
156
+ "posts/sandboxed": { "title": "This page runs on the in-page sandbox", "uid": "seed" }
157
+ }
158
+ `;
159
+ const WEB_ENV_EXAMPLE = `# Graduation config — your real Firebase web app (console → project settings).
160
+ # Unused under \`pyric dev\`; wire it into public/app.js (or a bundler env)
161
+ # when you deploy against the real backend.
162
+ FIREBASE_API_KEY=
163
+ FIREBASE_AUTH_DOMAIN=
164
+ FIREBASE_PROJECT_ID=
165
+ FIREBASE_STORAGE_BUCKET=
166
+ FIREBASE_MESSAGING_SENDER_ID=
167
+ FIREBASE_APP_ID=
168
+ `;
169
+ const webReadme = (name) => `# ${name}
170
+
171
+ A Firebase web app. In development it runs entirely on pyric's in-page
172
+ sandbox — no Firebase project, credentials, or emulators.
173
+
174
+ - **Develop:** \`bun install && bun run dev\` — serves \`public/\` with the
175
+ sandbox standing in for Firebase: seeded data, rules enforced + hot-reloaded,
176
+ popup sign-in via the helper dialog.
177
+ - **Agent:** \`bun run dev:agent\` — same, plus the MCP bridge on the dev-server
178
+ origin (\`/__pyric/mcp\`).
179
+ - **Persist (optional):** \`pyric dev --persist --seed seed.json\` — data and
180
+ test users survive reloads and restarts in \`.pyric/state/state.json\`
181
+ (plain JSON; gitignored). Promote lived state to a committable fixture
182
+ with \`pyric snapshot\`, then re-serve it: \`pyric dev --seed pyric-state.json\`.
183
+ - **Graduate:** fill \`.env\` from the Firebase console and point the config in
184
+ \`public/app.js\` at it, then run \`npx firebase-tools deploy\`. Bare
185
+ \`firebase/*\` imports need a bundler
186
+ (e.g. \`vite build\`) or an import map in production — \`pyric dev\`
187
+ provides the map in dev.
188
+
189
+ The app code uses canonical \`firebase/*\` imports everywhere. Switching
190
+ between sandbox and real Firebase is about **where you run it**, never what
191
+ you wrote.
192
+ `;
193
+ const GITIGNORE = `node_modules/
194
+ dist/
195
+ .env
196
+ .firebaserc
197
+ .pyric/
198
+ *.log
199
+ `;
200
+ const FIRESTORE_INDEXES = `{
201
+ "indexes": [],
202
+ "fieldOverrides": []
203
+ }
204
+ `;
205
+ // ─── node template (init v1 scaffold, carried over) ───────────────────
206
+ const NODE_APP_TS = `// Canonical Firebase imports stay unchanged between sandbox and production.
207
+ // \`bun run dev\` activates @pyric/cli/register; \`bun start\` loads Firebase.
208
+ import { initializeApp } from 'firebase/app';
209
+ import { getFirestore, collection, getDocs } from 'firebase/firestore';
210
+ import { seed } from './seed.ts';
211
+
212
+ const app = initializeApp({
213
+ apiKey: process.env.FIREBASE_API_KEY ?? 'pyric-local',
214
+ authDomain: process.env.FIREBASE_AUTH_DOMAIN,
215
+ projectId: process.env.FIREBASE_PROJECT_ID ?? 'pyric-local',
216
+ appId: process.env.FIREBASE_APP_ID ?? 'pyric-local',
217
+ });
218
+ const db = getFirestore(app);
219
+
220
+ if (process.env.PYRIC_SANDBOX) {
221
+ await seed(db);
222
+ }
223
+
224
+ const snap = await getDocs(collection(db, 'posts'));
225
+ console.log(\`\${snap.size} posts:\`);
226
+ snap.forEach((doc) => console.log(\` \${doc.id}:\`, doc.data()));
227
+
228
+ // Production: fill .env, deploy firestore.rules, then \`bun start\`.
229
+ `;
230
+ const NODE_SEED_TS = `import { collection, addDoc, type Firestore } from 'firebase/firestore';
231
+
232
+ export async function seed(db: Firestore): Promise<void> {
233
+ await addDoc(collection(db, 'posts'), {
234
+ title: 'Hello, Pyric',
235
+ author: 'sandbox',
236
+ createdAt: new Date(),
237
+ });
238
+ await addDoc(collection(db, 'posts'), {
239
+ title: 'Local-first by default',
240
+ author: 'sandbox',
241
+ createdAt: new Date(),
242
+ });
243
+ }
244
+ `;
245
+ const NODE_ENV_EXAMPLE = `# Production Firebase config (Firebase console -> project settings).
246
+ # Sandbox development uses the fallback values in src/app.ts.
247
+ FIREBASE_API_KEY=
248
+ FIREBASE_AUTH_DOMAIN=
249
+ FIREBASE_PROJECT_ID=
250
+ FIREBASE_APP_ID=
251
+ `;
252
+ const NODE_RULES = `rules_version = '2';
253
+ service cloud.firestore {
254
+ match /databases/{database}/documents {
255
+ // Local-first defaults: open in the sandbox so the quickstart
256
+ // works out of the box. **Tighten these before deploying with firebase-tools**
257
+ // — anonymous read+write is not what you want
258
+ // in the wild.
259
+ match /posts/{postId} {
260
+ allow read: if true;
261
+ allow write: if true;
262
+ }
263
+
264
+ // Default deny for everything else — opt in per collection.
265
+ match /{document=**} {
266
+ allow read, write: if false;
267
+ }
268
+ }
269
+ }
270
+ `;
271
+ const NODE_FIREBASE_JSON = `{
272
+ "firestore": {
273
+ "rules": "firestore.rules",
274
+ "indexes": "firestore.indexes.json"
275
+ }
276
+ }
277
+ `;
278
+ const nodeReadme = (name) => `# ${name}
279
+
280
+ A Firebase app whose canonical imports run against Pyric in development and
281
+ real Firebase in production. No application-code switch is required.
282
+
283
+ ## Quick start
284
+
285
+ \`\`\`bash
286
+ bun install
287
+ bun run dev # Pyric sandbox through the Node package swap
288
+ bun start # production: real Firebase
289
+ \`\`\`
290
+
291
+ ## Use with an MCP-connected agent (Claude Code)
292
+
293
+ Install the pyric Claude Code plugin once. It auto-connects through a bundled
294
+ stdio proxy that discovers the running bridge from \`.pyric/serve.json\` and probes
295
+ both IPv4 + IPv6, so there is NO \`claude mcp add\` step and no hand-written URL (a
296
+ static \`127.0.0.1\` URL hits the loopback-family trap). Just start the bridge:
297
+
298
+ \`\`\`bash
299
+ pyric bridge # default port 5174
300
+ \`\`\`
301
+
302
+ and the agent's pyric tools attach automatically.
303
+
304
+ ## Graduating to a real Firebase project
305
+
306
+ Graduation is a command change, not a code edit:
307
+
308
+ 1. Create a project at https://console.firebase.google.com and fill \`.env\`
309
+ (see \`.env.example\`).
310
+ 2. **Tighten \`firestore.rules\`** — the scaffolded rules are open for
311
+ sandbox convenience.
312
+ 3. Deploy them with the Firebase CLI: add a \`.firebaserc\`
313
+ (\`{ "projects": { "default": "your-project-id" } }\`), then run
314
+ \`npx firebase-tools deploy --only firestore:rules\`.
315
+ 4. Run the same canonical-import code against the real backend: \`bun start\`.
316
+ `;
317
+ // ─── web template (Vite + @pyric/cli/vite) ───────────────────────────
318
+ const VITE_INDEX_HTML = (name) => `<!doctype html>
319
+ <html>
320
+ <head>
321
+ <meta charset="utf-8" />
322
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
323
+ <title>${name}</title>
324
+ <style>
325
+ body { font: 16px/1.5 system-ui, sans-serif; max-width: 640px; margin: 3rem auto; padding: 0 1rem; }
326
+ button { padding: 0.4rem 0.9rem; cursor: pointer; }
327
+ form { display: flex; gap: 0.5rem; margin: 1rem 0; }
328
+ input { flex: 1; padding: 0.4rem 0.6rem; }
329
+ ul { padding-left: 1.2rem; }
330
+ .status { color: #666; }
331
+ </style>
332
+ </head>
333
+ <body>
334
+ <main>
335
+ <h1>${name}</h1>
336
+ <p class="status" id="auth-status">Signed out</p>
337
+ <button id="sign-in">Sign in with Google</button>
338
+ <button id="sign-out" hidden>Sign out</button>
339
+ <form id="add-post" hidden>
340
+ <input id="post-title" placeholder="Post title" required />
341
+ <button type="submit">Add post</button>
342
+ </form>
343
+ <ul id="posts"></ul>
344
+ </main>
345
+ <script type="module" src="/src/main.ts"></script>
346
+ </body>
347
+ </html>
348
+ `;
349
+ const VITE_MAIN_TS = `// Canonical firebase/* imports — UNCHANGED between dev and prod.
350
+ // In \`vite dev\` the \`@pyric/cli/vite\` plugin swaps these to an in-process
351
+ // sandbox (the config below is accepted but ignored). \`vite build\` ships the
352
+ // real \`firebase\` package and uses the SAME config. Graduation is a build, not
353
+ // a code edit.
354
+ import { initializeApp } from 'firebase/app';
355
+ import {
356
+ getAuth,
357
+ onAuthStateChanged,
358
+ signInWithPopup,
359
+ signOut,
360
+ GoogleAuthProvider,
361
+ } from 'firebase/auth';
362
+ import {
363
+ getFirestore,
364
+ collection,
365
+ onSnapshot,
366
+ addDoc,
367
+ serverTimestamp,
368
+ } from 'firebase/firestore';
369
+
370
+ const app = initializeApp({
371
+ // Filled from .env (see .env.example) at \`vite build\` time for production.
372
+ // Ignored in \`vite dev\` — the pyric sandbox stands in for Firebase.
373
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY ?? 'demo',
374
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN ?? 'demo.firebaseapp.com',
375
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID ?? 'demo',
376
+ });
377
+ const auth = getAuth(app);
378
+ const db = getFirestore(app);
379
+
380
+ const $ = <T extends HTMLElement>(id: string): T => document.getElementById(id) as T;
381
+ const els = {
382
+ status: $('auth-status'),
383
+ signIn: $<HTMLButtonElement>('sign-in'),
384
+ signOut: $<HTMLButtonElement>('sign-out'),
385
+ form: $<HTMLFormElement>('add-post'),
386
+ title: $<HTMLInputElement>('post-title'),
387
+ posts: $('posts'),
388
+ };
389
+
390
+ els.signIn.addEventListener('click', () => signInWithPopup(auth, new GoogleAuthProvider()));
391
+ els.signOut.addEventListener('click', () => signOut(auth));
392
+
393
+ onAuthStateChanged(auth, (user) => {
394
+ els.status.textContent = user
395
+ ? 'Signed in as ' + (user.displayName ?? user.email)
396
+ : 'Signed out';
397
+ els.signIn.hidden = !!user;
398
+ els.signOut.hidden = !user;
399
+ });
400
+
401
+ els.form.addEventListener('submit', async (e) => {
402
+ e.preventDefault();
403
+ // The form stays visible while signed out ON PURPOSE: submitting then
404
+ // ATTEMPTS the write, the owner-based rules deny it (create requires
405
+ // uid == request.auth.uid), and the denial shows up in Pyric Studio's
406
+ // Traffic tab — the rules-teaching loop this demo exists for.
407
+ const user = auth.currentUser;
408
+ try {
409
+ await addDoc(collection(db, 'posts'), {
410
+ title: els.title.value.trim(),
411
+ uid: user?.uid ?? 'anonymous',
412
+ createdAt: serverTimestamp(),
413
+ });
414
+ els.title.value = '';
415
+ } catch (err) {
416
+ els.status.textContent = user
417
+ ? \`Write failed: \${(err as { code?: string }).code ?? String(err)}\`
418
+ : 'Denied by rules (signed out) — see the Traffic tab in Pyric Studio.';
419
+ }
420
+ });
421
+
422
+ onSnapshot(collection(db, 'posts'), (snap) => {
423
+ els.posts.replaceChildren(
424
+ ...snap.docs.map((d) => {
425
+ const li = document.createElement('li');
426
+ li.textContent = (d.data() as { title?: string }).title ?? '';
427
+ return li;
428
+ }),
429
+ );
430
+ });
431
+ `;
432
+ const VITE_CONFIG = `import { defineConfig } from 'vite';
433
+ import { pyricSandbox } from '@pyric/cli/vite';
434
+
435
+ // Under \`vite dev\` pyricSandbox() swaps firebase/* to the in-process pyric
436
+ // sandbox and deploys + hot-reloads firestore.rules — no Firebase project,
437
+ // credentials, or emulators. \`vite build\` (mode production) ships the real
438
+ // firebase package; the swap never reaches the deployed artifact. For a
439
+ // self-contained sandbox preview you can serve under \`pyric dev\`, build with a
440
+ // non-production mode: \`vite build --mode development\` (see the \`build:sandbox\`
441
+ // script). That output is marked and can never be deployed.
442
+ export default defineConfig({
443
+ plugins: [pyricSandbox()],
444
+ });
445
+ `;
446
+ const VITE_TSCONFIG = `{
447
+ "compilerOptions": {
448
+ "target": "ES2022",
449
+ "module": "ESNext",
450
+ "moduleResolution": "bundler",
451
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
452
+ "strict": true,
453
+ "skipLibCheck": true,
454
+ "noEmit": true,
455
+ "types": ["vite/client"]
456
+ },
457
+ "include": ["src"]
458
+ }
459
+ `;
460
+ // Owner-based rules for the Vite web template. Same shape as the static
461
+ // template's WEB_RULES, but the comment reflects the plugin (not pyric dev) —
462
+ // keep this in lockstep with examples/vite-sandbox-app/firestore.rules.
463
+ const VITE_RULES = `rules_version = '2';
464
+ service cloud.firestore {
465
+ match /databases/{database}/documents {
466
+ // Owner-based from line 1 — the Vite plugin deploys + hot-reloads this file
467
+ // into the sandbox, so safe rules are cheap to iterate. These deploy as-is.
468
+ match /posts/{postId} {
469
+ allow read: if true;
470
+ allow create: if request.auth != null
471
+ && request.resource.data.uid == request.auth.uid;
472
+ allow update, delete: if request.auth != null
473
+ && resource.data.uid == request.auth.uid;
474
+ }
475
+
476
+ // Default deny — opt in per collection.
477
+ match /{document=**} {
478
+ allow read, write: if false;
479
+ }
480
+ }
481
+ }
482
+ `;
483
+ const VITE_ENV_DTS = `/// <reference types="vite/client" />
484
+ `;
485
+ const VITE_FIREBASE_JSON = `{
486
+ "firestore": {
487
+ "rules": "firestore.rules",
488
+ "indexes": "firestore.indexes.json"
489
+ },
490
+ "hosting": {
491
+ "public": "dist",
492
+ "rewrites": [{ "source": "**", "destination": "/index.html" }]
493
+ }
494
+ }
495
+ `;
496
+ const VITE_ENV_EXAMPLE = `# Your real Firebase web-app config (Firebase console -> project settings).
497
+ # UNUSED in \`vite dev\` (the pyric sandbox stands in); USED by \`vite build\` for
498
+ # production. Vite only exposes \`VITE_\`-prefixed vars to client code.
499
+ VITE_FIREBASE_API_KEY=
500
+ VITE_FIREBASE_AUTH_DOMAIN=
501
+ VITE_FIREBASE_PROJECT_ID=
502
+ VITE_FIREBASE_STORAGE_BUCKET=
503
+ VITE_FIREBASE_MESSAGING_SENDER_ID=
504
+ VITE_FIREBASE_APP_ID=
505
+ `;
506
+ const viteReadme = (name) => `# ${name}
507
+
508
+ A Firebase web app built with Vite. In development it runs entirely on pyric's
509
+ in-process sandbox — no Firebase project, credentials, or emulators.
510
+
511
+ - **Develop:** \`bun install && bun run dev\` — \`vite dev\` with the
512
+ \`@pyric/cli/vite\` plugin swapping \`firebase/*\` to the sandbox: seeded data,
513
+ your \`firestore.rules\` deployed + hot-reloaded, popup sign-in.
514
+ - **Build for production:** \`bun run build\` — \`vite build\` ships the real
515
+ \`firebase\` package. Fill \`.env\` from the Firebase console (see
516
+ \`.env.example\`); the SAME config you wrote runs against real Firebase. There
517
+ is no separate "graduation" step — dev and prod are one toolchain.
518
+ - **Deploy:** \`npx firebase-tools deploy\` after the production build
519
+ (\`hosting.public\` is \`dist/\`, Vite's build output).
520
+
521
+ > Your app code uses canonical \`firebase/*\` imports everywhere. Switching
522
+ > between the sandbox and real Firebase is \`vite dev\` vs \`vite build\`, never
523
+ > what you wrote.
524
+
525
+ The plugin is dev-only: SharedWorker multi-tab sync, \`--persist\`, capture, and
526
+ the MCP bridge (all available today under \`pyric dev\`) arrive in the plugin in
527
+ later releases. For a pre-built / no-build app, use \`pyric init --template static\`
528
+ + \`pyric dev\`.
529
+ `;
530
+ // ─── the registry ─────────────────────────────────────────────────────
531
+ export const TEMPLATES = {
532
+ // web (default) — a Vite app on the @pyric/cli/vite plugin. `vite dev` runs
533
+ // on the sandbox; `vite build` ships real firebase. One toolchain.
534
+ web: {
535
+ scripts: {
536
+ dev: 'vite',
537
+ build: 'vite build',
538
+ 'build:sandbox': 'vite build --mode development',
539
+ preview: 'vite preview',
540
+ },
541
+ // The real firebase package ships day one so the production `vite build`
542
+ // resolves the same canonical imports against it — no code edit at graduation.
543
+ dependencies: { firebase: '^12.12.0' },
544
+ devDependencies: { '@pyric/cli': '*', vite: '^6.0.0', typescript: '^5.7.0' },
545
+ dirs: ['src'],
546
+ files: (name) => [
547
+ { name: 'index.html', content: VITE_INDEX_HTML(name) },
548
+ { name: 'vite.config.ts', content: VITE_CONFIG },
549
+ { name: 'tsconfig.json', content: VITE_TSCONFIG },
550
+ { name: 'src/main.ts', content: VITE_MAIN_TS },
551
+ { name: 'src/vite-env.d.ts', content: VITE_ENV_DTS },
552
+ { name: 'firestore.rules', content: VITE_RULES },
553
+ { name: 'firebase.json', content: VITE_FIREBASE_JSON },
554
+ { name: 'firestore.indexes.json', content: FIRESTORE_INDEXES },
555
+ { name: '.env.example', content: VITE_ENV_EXAMPLE },
556
+ { name: 'README.md', content: viteReadme(name) },
557
+ { name: '.gitignore', content: GITIGNORE },
558
+ ],
559
+ nextSteps: [
560
+ 'bun install',
561
+ 'bun run dev # vite dev on the pyric sandbox',
562
+ 'bun run build # production build against real Firebase',
563
+ ],
564
+ },
565
+ node: {
566
+ scripts: {
567
+ start: 'node --env-file-if-exists=.env --experimental-strip-types src/app.ts',
568
+ dev: 'pyric dev --no-open -- node --env-file-if-exists=.env --experimental-strip-types src/app.ts',
569
+ bridge: 'pyric bridge',
570
+ },
571
+ dependencies: { firebase: '^12.12.0' },
572
+ devDependencies: { '@pyric/cli': '*', '@types/node': '^22.0.0', typescript: '^5.7.0' },
573
+ dirs: ['src'],
574
+ files: (name) => [
575
+ { name: 'src/app.ts', content: NODE_APP_TS },
576
+ { name: '.env.example', content: NODE_ENV_EXAMPLE },
577
+ { name: 'src/seed.ts', content: NODE_SEED_TS },
578
+ { name: 'firestore.rules', content: NODE_RULES },
579
+ { name: 'firebase.json', content: NODE_FIREBASE_JSON },
580
+ { name: 'firestore.indexes.json', content: FIRESTORE_INDEXES },
581
+ { name: 'README.md', content: nodeReadme(name) },
582
+ { name: '.gitignore', content: GITIGNORE },
583
+ ],
584
+ nextSteps: ['bun install', 'bun run dev', 'bun start # production: real Firebase'],
585
+ },
586
+ // static — the serve-era, no-bundler scaffold: a static app `pyric dev`
587
+ // runs against the in-page sandbox via a runtime import map. For pre-built /
588
+ // retrofit apps, or anyone who wants zero build step.
589
+ static: {
590
+ scripts: {
591
+ dev: 'pyric dev --seed seed.json',
592
+ 'dev:agent': 'pyric dev --bridge --seed seed.json',
593
+ },
594
+ dependencies: { firebase: '^12.12.0' },
595
+ devDependencies: { '@pyric/cli': '*' },
596
+ dirs: ['public'],
597
+ files: (name) => [
598
+ { name: 'public/index.html', content: WEB_INDEX_HTML(name) },
599
+ { name: 'public/app.js', content: WEB_APP_JS },
600
+ { name: 'firestore.rules', content: WEB_RULES },
601
+ { name: 'firebase.json', content: WEB_FIREBASE_JSON },
602
+ { name: 'firestore.indexes.json', content: FIRESTORE_INDEXES },
603
+ { name: 'seed.json', content: WEB_SEED_JSON },
604
+ { name: '.env.example', content: WEB_ENV_EXAMPLE },
605
+ { name: 'README.md', content: webReadme(name) },
606
+ { name: '.gitignore', content: GITIGNORE },
607
+ ],
608
+ nextSteps: ['bun install', 'bun run dev', 'bun run dev:agent # agents: MCP at /__pyric/mcp'],
609
+ },
610
+ };
611
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "create-pyric",
3
+ "version": "0.1.0-alpha.9",
4
+ "license": "Apache-2.0",
5
+ "homepage": "https://pyric.dev",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/davideast/pyric.git",
9
+ "directory": "packages/create-pyric"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/davideast/pyric/issues"
13
+ },
14
+ "description": "Scaffold a Pyric + Vite app — `npm create pyric`.",
15
+ "type": "module",
16
+ "bin": {
17
+ "create-pyric": "./dist/bin.js"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc && chmod +x dist/bin.js",
32
+ "test": "bun test",
33
+ "typecheck": "bun x tsc -p tsconfig.json --noEmit"
34
+ },
35
+ "devDependencies": {
36
+ "@types/bun": "latest",
37
+ "typescript": "^5.7.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=22.15"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }