cosmolo 0.3.0 → 0.3.2

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alcogy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,583 @@
1
+ # Cosmolo
2
+
3
+ A SvelteKit-native content management package — add Markdown-based blogging to any SvelteKit project in minutes.
4
+
5
+ **Website:** https://cosmolo.alcogy.dev
6
+
7
+ > **Name origin**: Short for *cosmologist* — a deliberate nod to Astro. Cosmolo occupies
8
+ > a similar content-site niche but stays entirely within the SvelteKit ecosystem.
9
+
10
+ ---
11
+
12
+ ## Why Cosmolo
13
+
14
+ Astro is a great product — but for SvelteKit developers it's a heavy choice.
15
+ Switching frameworks means leaving behind the Svelte component model, the SvelteKit
16
+ router, and all the ecosystem knowledge you've built up. Cosmolo gives SvelteKit
17
+ a canonical "just add Markdown and go" story without asking you to leave.
18
+
19
+ | | Cosmolo | Astro | Nuxt Content | SvelteKit (vanilla) |
20
+ |---|---|---|---|---|
21
+ | Framework | SvelteKit | Astro | Nuxt (Vue) | SvelteKit |
22
+ | Markdown | MDSveX + marked | Built-in | Built-in | Manual |
23
+ | Type-safe frontmatter | Zod | TS inference | Zod (optional) | Manual |
24
+ | Component in Markdown | Yes (.svx) | Yes (.mdx) | Yes | No |
25
+ | Config-driven categories | Yes | No | No | No |
26
+ | Headless CMS (JSON API) | Yes | Manual | Manual | Manual |
27
+ | Learning curve | SvelteKit only | Astro concepts | Vue + Nuxt | SvelteKit only |
28
+
29
+ **Core principles:**
30
+
31
+ 1. **SvelteKit all the way down** — No framework switching. Developers who know SvelteKit already know Cosmolo.
32
+ 2. **Config over convention** — Site identity and taxonomy are JSON files. No source code changes needed to add a category.
33
+ 3. **Type-safe content** — Frontmatter is validated with Zod at build time. Malformed articles fail loudly during `bun build`.
34
+ 4. **MDSveX as a first-class citizen** — `.md` and `.svx` share the same routing and Zod schema; the system auto-detects which to use.
35
+ 5. **Headless-ready** — Cosmolo generates static JSON endpoints alongside your HTML pages, so your content can be consumed by external apps or frontends without any server.
36
+ 6. **Non-invasive** — Cosmolo is an npm package, not a framework. It adds content management to your existing project without owning your routes or components.
37
+
38
+ ---
39
+
40
+ ## Quick Start
41
+
42
+ ```bash
43
+ # In an existing SvelteKit project:
44
+ bun add cosmolo
45
+
46
+ # Scaffold routes and config files interactively
47
+ bunx cosmolo init
48
+
49
+ # Start writing content
50
+ bun generate:article
51
+
52
+ # Run the dev server
53
+ bun dev
54
+ ```
55
+
56
+ `cosmolo init` asks two questions — which mode (full UI or server-only) and which adapter (SSG or serverless) — then copies the appropriate route files into your project.
57
+
58
+ ---
59
+
60
+ ## Configuration
61
+
62
+ ### `config/site.json`
63
+
64
+ Site-wide settings. Created by `cosmolo init`.
65
+
66
+ | Field | Description |
67
+ |---|---|
68
+ | `url` | Production URL (used in sitemap and OGP) |
69
+ | `name` | Site name shown in header and `<title>` |
70
+ | `description` | Default meta description |
71
+ | `twitterHandle` | Twitter/X handle for `twitter:site` meta tag |
72
+ | `fallbackCategoryLabel` | Label shown for the `other` fallback category |
73
+ | `articlesPerPage` | Articles per page for pagination |
74
+
75
+ ```json
76
+ {
77
+ "url": "https://your-site.example.com",
78
+ "name": "Your Site Name",
79
+ "description": "A content site built with Cosmolo.",
80
+ "twitterHandle": "@yourhandle",
81
+ "fallbackCategoryLabel": "Other",
82
+ "articlesPerPage": 10
83
+ }
84
+ ```
85
+
86
+ ### `config/categories.json`
87
+
88
+ Define your content taxonomy. Each key becomes a URL slug at `/categories/<key>`.
89
+
90
+ ```json
91
+ {
92
+ "tech": {
93
+ "label": "Technology",
94
+ "description": "Articles about software, tools, and the web."
95
+ },
96
+ "design": {
97
+ "label": "Design",
98
+ "description": "Articles about UI/UX and visual design."
99
+ }
100
+ }
101
+ ```
102
+
103
+ Articles with an unrecognized category fall back to `/categories/other` automatically.
104
+
105
+ ---
106
+
107
+ ## Content
108
+
109
+ ### Article Frontmatter
110
+
111
+ Every article needs these fields at the top of the file:
112
+
113
+ ```yaml
114
+ ---
115
+ title: "My Article Title"
116
+ category: "tech" # must match a key in config/categories.json
117
+ excerpt: "One sentence shown in article listings."
118
+ sort: 100 # higher number = appears earlier in listings
119
+ date: "2025-01-15" # ISO date string (optional)
120
+ tags: ["svelte", "tutorial"] # optional — tag listing pages at /tags/<tag>
121
+ series: "getting-started" # optional — groups articles into a series
122
+ seriesOrder: 1 # optional — position within the series (1-based)
123
+ draft: true # optional — exclude from build output and listings
124
+ related: ["slug-a", "slug-b"] # optional — override auto-detected related articles
125
+ ---
126
+ ```
127
+
128
+ `sort` gives you manual ordering without relying on file timestamps or alphabetical order.
129
+ Articles with an unknown `category` value are grouped under `/categories/other`.
130
+
131
+ ### Updated date
132
+
133
+ Each article page automatically shows an "Updated:" date derived from the file's
134
+ **last git commit timestamp**. No frontmatter change is needed — the date is resolved
135
+ at build time by running `git log -1` against the article file.
136
+
137
+ The updated date is displayed only when it differs from the `date` field. If the file
138
+ has never been committed, the updated date is omitted silently.
139
+
140
+ > **Note:** file modification times (`mtime`) are intentionally not used. They reset on
141
+ > `git clone`, which makes them unreliable in CI/CD environments.
142
+
143
+ ### Draft mode
144
+
145
+ Add `draft: true` to any article's frontmatter to exclude it from build output and all listings.
146
+ Draft articles are invisible in production but accessible during `bun dev` via their direct URL.
147
+
148
+ ### Tags
149
+
150
+ Articles can have multiple tags. Each tag gets a listing page at `/tags/<tag>`.
151
+
152
+ ```yaml
153
+ tags: ["svelte", "tutorial"]
154
+ ```
155
+
156
+ Tags are case-sensitive. Unused tags produce no page.
157
+
158
+ ### Series
159
+
160
+ Group related articles into an ordered sequence. Prev/next navigation is shown inside each article.
161
+
162
+ ```yaml
163
+ series: "getting-started"
164
+ seriesOrder: 1
165
+ ```
166
+
167
+ All articles sharing the same `series` value are linked together, sorted by `seriesOrder` ascending.
168
+
169
+ ### Manual related articles
170
+
171
+ By default, the "More in this category" panel shows up to 4 articles from the same category.
172
+ Override it by listing slugs explicitly:
173
+
174
+ ```yaml
175
+ related: ["slug-a", "slug-b"]
176
+ ```
177
+
178
+ ### Table of contents
179
+
180
+ For `.md` articles with 2 or more `##` headings, a table of contents is automatically rendered
181
+ above the article body. Heading levels `##` through `######` are included. `.svx` articles do
182
+ not get an auto-generated TOC.
183
+
184
+ ### Supported file formats
185
+
186
+ | Extension | Renderer | Svelte components in body |
187
+ |-----------|----------|---------------------------|
188
+ | `.md` | `marked` | No |
189
+ | `.svx` | MDSveX | Yes |
190
+
191
+ Place files in `src/content/articles/` (or your configured `articlesDir`).
192
+ The filename becomes the URL slug:
193
+
194
+ ```
195
+ src/content/articles/my-post.md → /articles/my-post
196
+ src/content/articles/demo.svx → /articles/demo
197
+ ```
198
+
199
+ ### Markdown extensions
200
+
201
+ **YouTube embed** — renders a responsive 16:9 iframe:
202
+
203
+ ```
204
+ ::youtube[dQw4w9WgXcQ]
205
+ ```
206
+
207
+ **External links** — all `http://` and `https://` links automatically get
208
+ `target="_blank" rel="noopener noreferrer"`.
209
+
210
+ ### Svelte components in `.svx`
211
+
212
+ `.svx` files are Markdown with embedded Svelte components. Use them when articles
213
+ need interactive UI.
214
+
215
+ ```svx
216
+ <script>
217
+ import Callout from '$lib/components/Callout.svelte';
218
+ </script>
219
+
220
+ <Callout type="warning">Watch out for this edge case.</Callout>
221
+ ```
222
+
223
+ ### Static Pages
224
+
225
+ Place `.md` files in `src/content/pages/`. Each file is served at `/<filename>`:
226
+
227
+ ```
228
+ src/content/pages/about.md → /about
229
+ ```
230
+
231
+ Frontmatter:
232
+
233
+ ```yaml
234
+ ---
235
+ title: "About"
236
+ ---
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Generators
242
+
243
+ Create content files without editing them by hand:
244
+
245
+ ```bash
246
+ bunx cosmolo generate # Interactive menu (article / page / category)
247
+ bunx cosmolo generate article # Create an article
248
+ bunx cosmolo generate page # Create a static page
249
+ bunx cosmolo generate category # Add a category to categories.json
250
+ ```
251
+
252
+ `cosmolo init` adds convenience scripts to your `package.json` automatically, so after init you can just run:
253
+
254
+ ```bash
255
+ bun generate:article
256
+ bun generate:page
257
+ bun generate:category
258
+ ```
259
+
260
+ ### Article
261
+
262
+ Prompts for title, slug, category, excerpt, tags, sort, date, draft status, and series.
263
+ Creates `src/content/articles/<slug>.md` with pre-filled frontmatter.
264
+
265
+ ### Page
266
+
267
+ Prompts for title and slug. Creates `src/content/pages/<slug>.md`.
268
+
269
+ ### Category
270
+
271
+ Prompts for key (slug), label, and description. Appends the new entry to `config/categories.json`.
272
+
273
+ ---
274
+
275
+ ## Headless CMS
276
+
277
+ Cosmolo can expose your content as static JSON endpoints, making it usable as a
278
+ headless CMS alongside — or independently of — your rendered pages.
279
+
280
+ All endpoints are **static files** generated at build time. No server or database is required.
281
+
282
+ | Endpoint | Description |
283
+ |---|---|
284
+ | `/api/articles.json` | Slug + title for all non-draft articles |
285
+ | `/api/articles/<slug>.json` | Full metadata and body for a single article |
286
+ | `/api/categories.json` | All categories with slug, label, and description |
287
+ | `/rss.xml` | RSS 2.0 feed |
288
+ | `/sitemap.xml` | XML sitemap including all article, category, and tag URLs |
289
+
290
+ `cosmolo init` scaffolds `rss.xml` and `sitemap.xml` automatically. The JSON API routes
291
+ (`api/articles.json`, `api/articles/[slug].json`, `api/categories.json`) can be added
292
+ manually or customized to return exactly the fields your consumers need.
293
+
294
+ ### Article body format
295
+
296
+ The per-article endpoint supports three body formats, configurable in `config/site.json`:
297
+
298
+ ```json
299
+ "api": { "articleBody": "html" }
300
+ ```
301
+
302
+ | Value | `contentsFormat` | `contents` |
303
+ |---|---|---|
304
+ | `"html"` | `"html"` | Rendered HTML (default) |
305
+ | `"markdown"` | `"markdown"` | Raw Markdown (frontmatter stripped) |
306
+ | `"plaintext"` | `"plaintext"` | Plain text — Markdown syntax removed |
307
+
308
+ > **Note:** All API endpoints are publicly accessible static files. Do not include
309
+ > sensitive or private information in article frontmatter or content.
310
+
311
+ ---
312
+
313
+ ## Package Reference
314
+
315
+ ### Install
316
+
317
+ ```bash
318
+ bun add cosmolo
319
+ # peer deps (if not already installed)
320
+ bun add -D vite @sveltejs/kit
321
+ ```
322
+
323
+ ### Setup
324
+
325
+ **1. Create `cosmolo.config.ts`** in your project root
326
+
327
+ ```typescript
328
+ import { resolveConfig } from 'cosmolo';
329
+
330
+ export default resolveConfig({
331
+ articlesDir: 'src/content/articles', // default
332
+ pagesDir: 'src/content/pages', // default
333
+ siteConfigPath: 'config/site.json', // default
334
+ categoriesConfigPath: 'config/categories.json', // default
335
+ });
336
+ ```
337
+
338
+ All fields are optional. Omitting them uses the defaults shown above.
339
+
340
+ **2. Register the Vite plugin** in `vite.config.ts`
341
+
342
+ ```typescript
343
+ import { sveltekit } from '@sveltejs/kit/vite';
344
+ import { cosmoloPlugin } from 'cosmolo/plugin';
345
+ import config from './cosmolo.config';
346
+
347
+ export default {
348
+ plugins: [sveltekit(), cosmoloPlugin(config)],
349
+ };
350
+ ```
351
+
352
+ The plugin generates a virtual module (`cosmolo:content`) containing `import.meta.glob`
353
+ calls for your configured paths. All content — including `categories.json` and
354
+ `site.json` — is bundled at build time with no `fs` access at runtime, making Cosmolo
355
+ compatible with Cloudflare Workers and other serverless runtimes.
356
+
357
+ ### Scaffolding with `cosmolo init`
358
+
359
+ Instead of writing route files by hand, run:
360
+
361
+ ```bash
362
+ bunx cosmolo init
363
+ ```
364
+
365
+ The command asks two questions:
366
+
367
+ **Mode**
368
+
369
+ | Mode | What gets generated |
370
+ |---|---|
371
+ | **A — Full** | `+page.server.ts` + `+page.svelte` for every route, `sitemap.xml`, `rss.xml`, `Pagination.svelte`, `cosmolo.config.ts`, `vite.config.ts` |
372
+ | **B — Slim** | Server routes only — bring your own Svelte UI |
373
+
374
+ **Adapter**
375
+
376
+ | Adapter | Effect |
377
+ |---|---|
378
+ | **SSG** (`adapter-static`) | Also creates `src/routes/+layout.ts` with `export const prerender = true` |
379
+ | **Serverless / SSR** | No layout file — routes render on demand |
380
+
381
+ If any target file already exists, the command lists every conflict and exits without
382
+ writing anything.
383
+
384
+ **Manual prerender setup**
385
+
386
+ If you chose Serverless but later switch to SSG, add this file:
387
+
388
+ ```typescript
389
+ // src/routes/+layout.ts
390
+ export const prerender = true;
391
+ ```
392
+
393
+ ### Load function factories
394
+
395
+ `+page.server.ts` files can use factory functions instead of writing load boilerplate:
396
+
397
+ ```typescript
398
+ // src/routes/+page.server.ts
399
+ import { createArticlesLoader } from 'cosmolo';
400
+ import config from '../../cosmolo.config';
401
+ export const load = createArticlesLoader(config);
402
+ ```
403
+
404
+ ```typescript
405
+ // src/routes/articles/[slug]/+page.server.ts
406
+ import { createArticleLoader, createArticleEntries } from 'cosmolo';
407
+ import config from '../../../../cosmolo.config';
408
+ export const entries = createArticleEntries(config);
409
+ export const load = createArticleLoader(config);
410
+ ```
411
+
412
+ ```typescript
413
+ // src/routes/categories/[slug]/+page.server.ts
414
+ import { createCategoryLoader, createCategoryEntries } from 'cosmolo';
415
+ import config from '../../../../cosmolo.config';
416
+ export const entries = createCategoryEntries(config);
417
+ export const load = createCategoryLoader(config);
418
+ ```
419
+
420
+ ```typescript
421
+ // src/routes/tags/[tag]/+page.server.ts
422
+ import { createTagLoader, createTagEntries } from 'cosmolo';
423
+ import config from '../../../../cosmolo.config';
424
+ export const entries = createTagEntries(config);
425
+ export const load = createTagLoader(config);
426
+ ```
427
+
428
+ For git-based updated dates on the article loader:
429
+
430
+ ```typescript
431
+ import { execSync } from 'child_process';
432
+
433
+ export const load = createArticleLoader(config, {
434
+ getUpdatedAt(slug) {
435
+ try {
436
+ return execSync(
437
+ `git log -1 --format=%cI -- "src/content/articles/${slug}.md"`,
438
+ { encoding: 'utf-8' }
439
+ ).trim().split('T')[0];
440
+ } catch { return ''; }
441
+ },
442
+ });
443
+ ```
444
+
445
+ ### Using helpers in Svelte components
446
+
447
+ Category labels and SVX components are safe to use directly in `.svelte` files:
448
+
449
+ ```svelte
450
+ <script lang="ts">
451
+ import { getCategoryLabel, getSvxComponent } from 'cosmolo';
452
+ import config from '../../../../cosmolo.config';
453
+ import type { Component } from 'svelte';
454
+
455
+ const { data } = $props();
456
+
457
+ // Category label (works client-side — no fs at runtime)
458
+ const label = getCategoryLabel(config, data.article.category);
459
+
460
+ // SVX component for .svx articles (undefined for .md)
461
+ const SvxComponent = getSvxComponent(config, data.article.slug) as Component | undefined;
462
+ </script>
463
+ ```
464
+
465
+ ### Package exports
466
+
467
+ | Import | Description |
468
+ |---|---|
469
+ | `cosmolo` | Types, config resolver, all content functions |
470
+ | `cosmolo/plugin` | `cosmoloPlugin(config)` — Vite plugin |
471
+
472
+ Key exports from `cosmolo`:
473
+
474
+ | Export | Description |
475
+ |---|---|
476
+ | `resolveConfig(config?)` | Merge user config with defaults |
477
+ | `getArticles(config)` | All non-draft articles, sorted |
478
+ | `getArticle(config, slug)` | Single article with rendered HTML + TOC |
479
+ | `getArticlesByTag(config, tag)` | Articles filtered by tag |
480
+ | `getArticlesBySeries(config, series)` | Articles in a series, sorted by `seriesOrder` |
481
+ | `getSlugs(config)` | All non-draft article slugs |
482
+ | `getTags(config)` | All tags across all articles |
483
+ | `getSvxComponent(config, slug)` | Svelte component for an `.svx` article (client-safe) |
484
+ | `getCategoryLabel(config, key)` | Category label by key (client-safe) |
485
+ | `getAllCategories(config)` | All category entries |
486
+ | `loadSiteConfig(config)` | Site configuration object |
487
+ | `getPage(config, slug)` | Single static page with rendered HTML |
488
+ | `getPageSlugs(config)` | All static page slugs |
489
+ | `createArticlesLoader(config)` | Load factory for article listings |
490
+ | `createArticleLoader(config, opts?)` | Load factory for single article |
491
+ | `createCategoryLoader(config)` | Load factory for category pages |
492
+ | `createTagLoader(config)` | Load factory for tag pages |
493
+ | `createPageLoader(config)` | Load factory for static pages |
494
+ | `createArticleEntries(config)` | `entries()` generator for article routes |
495
+ | `createCategoryEntries(config)` | `entries()` generator for category routes |
496
+ | `createTagEntries(config)` | `entries()` generator for tag routes |
497
+ | `createPageEntries(config)` | `entries()` generator for page routes |
498
+
499
+ ---
500
+
501
+ ## Deployment
502
+
503
+ ### SSG vs Serverless
504
+
505
+ All content loading happens at build time via `import.meta.glob` and the Vite plugin,
506
+ so there are no `fs` calls at runtime. Cosmolo works with any SvelteKit adapter.
507
+
508
+ | Adapter | Notes |
509
+ |---|---|
510
+ | `@sveltejs/adapter-static` | Full SSG — `cosmolo init` sets `prerender = true` automatically for SSG mode |
511
+ | `@sveltejs/adapter-cloudflare` | Cloudflare Workers / Pages (SSR). No extra config needed. |
512
+ | `@sveltejs/adapter-vercel` | Vercel Edge / Node. No extra config needed. |
513
+ | `@sveltejs/adapter-node` | Self-hosted Node server. No extra config needed. |
514
+
515
+ ### Cloudflare Pages (SSG)
516
+
517
+ Cloudflare Pages offers a free tier with global CDN, automatic HTTPS, and Git-based
518
+ deployments.
519
+
520
+ **1. Push your repo to GitHub.**
521
+
522
+ **2. Create a new Pages project**
523
+
524
+ 1. Open the [Cloudflare dashboard](https://dash.cloudflare.com/) and go to **Workers & Pages**
525
+ 2. Click **Create** → **Pages** → **Connect to Git**
526
+ 3. Authorize Cloudflare and select your repository
527
+
528
+ **3. Configure the build settings**
529
+
530
+ | Setting | Value |
531
+ |---------|-------|
532
+ | Framework preset | None |
533
+ | Build command | `npx bun run build` |
534
+ | Build output directory | `build` |
535
+
536
+ > Cloudflare Pages uses Node.js by default. Using `npx bun run build` ensures bun is
537
+ > available without requiring a custom environment. Alternatively, add `BUN_VERSION=latest`
538
+ > as an environment variable to enable native bun support.
539
+
540
+ **4. Deploy**
541
+
542
+ Click **Save and Deploy**. Cloudflare pulls your code, runs the build, and publishes
543
+ the `build/` directory to their global edge network.
544
+
545
+ **Custom domain**
546
+
547
+ Go to your Pages project → **Custom domains** → add your domain. If your domain's DNS
548
+ is managed on Cloudflare, the setup is automatic.
549
+
550
+ ### Vercel
551
+
552
+ ```bash
553
+ bunx vercel --prod
554
+ ```
555
+
556
+ Or connect via the Vercel dashboard. Build command: `bun run build`. Output directory: `build`.
557
+
558
+ ### Netlify
559
+
560
+ ```bash
561
+ bunx netlify deploy --prod --dir build
562
+ ```
563
+
564
+ Or connect via the Netlify dashboard. Build command: `bun run build`. Publish directory: `build`.
565
+
566
+ ---
567
+
568
+ ## Development Commands
569
+
570
+ ```bash
571
+ bun dev # Start dev server at http://localhost:5173
572
+ bun build # Build output
573
+ bun preview # Preview the production build
574
+ bun check # TypeScript type-check
575
+ bun lint # Run Prettier + ESLint checks
576
+ bun format # Auto-format all files
577
+ ```
578
+
579
+ ---
580
+
581
+ ## License
582
+
583
+ MIT
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "cosmolo",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "bin": {
6
- "cosmolo": "./src/cli/index.ts"
6
+ "cosmolo": "src/cli/index.ts"
7
7
  },
8
8
  "exports": {
9
9
  ".": {
@@ -15,7 +15,13 @@
15
15
  "default": "./src/plugin.ts"
16
16
  }
17
17
  },
18
- "files": ["src", "templates", "!src/**/*.test.ts"],
18
+ "files": [
19
+ "src",
20
+ "templates",
21
+ "README.md",
22
+ "LICENSE",
23
+ "!src/**/*.test.ts"
24
+ ],
19
25
  "dependencies": {
20
26
  "gray-matter": "^4.0.3",
21
27
  "marked": "^15.0.0",
package/src/cli/init.ts CHANGED
@@ -78,15 +78,15 @@ export async function main(): Promise<void> {
78
78
 
79
79
  // ── Mode selection ──────────────────────────────────────────────────────
80
80
  console.log('Choose an initialization mode:\n');
81
- console.log(' A) Full — server routes + Svelte page components');
82
- console.log(' B) Slim — server routes only (bring your own Svelte UI)\n');
81
+ console.log(' 1) Full — server routes + Svelte page components');
82
+ console.log(' 2) Slim — server routes only (bring your own Svelte UI)\n');
83
83
 
84
84
  let modeRaw = '';
85
- while (!['a', 'b'].includes(modeRaw)) {
86
- modeRaw = (await ask(rl, 'Mode [A/B]: ')).trim().toLowerCase();
87
- if (!['a', 'b'].includes(modeRaw)) console.log(' Please enter A or B.');
85
+ while (!['1', '2'].includes(modeRaw)) {
86
+ modeRaw = (await ask(rl, 'Mode [1/2]: ')).trim();
87
+ if (!['1', '2'].includes(modeRaw)) console.log(' Please enter 1 or 2.');
88
88
  }
89
- const mode: 'full' | 'slim' = modeRaw === 'a' ? 'full' : 'slim';
89
+ const mode: 'full' | 'slim' = modeRaw === '1' ? 'full' : 'slim';
90
90
 
91
91
  // ── Adapter selection ───────────────────────────────────────────────────
92
92
  console.log('\nChoose your deployment adapter:\n');
@@ -100,8 +100,6 @@ export async function main(): Promise<void> {
100
100
  }
101
101
  const isSSG = adapterRaw === '1';
102
102
 
103
- rl.close();
104
-
105
103
  // ── Collect files ───────────────────────────────────────────────────────
106
104
  const sharedFiles = collectFiles(path.join(TEMPLATE_DIR, 'shared'));
107
105
  const fullFiles = mode === 'full' ? collectFiles(path.join(TEMPLATE_DIR, 'full')) : [];
@@ -126,20 +124,24 @@ export async function main(): Promise<void> {
126
124
  }
127
125
 
128
126
  if (conflicts.length > 0) {
129
- console.error('\nError: The following files already exist and would be overwritten:\n');
130
- for (const f of conflicts) console.error(` ${f}`);
131
- console.error('\nTo resolve, either:');
132
- console.error(' 1. Remove or rename the conflicting files, then run cosmolo init again.');
133
- console.error(' 2. Manually copy the needed templates from the cosmolo package:');
134
- console.error(` node_modules/cosmolo/templates/shared/`);
135
- if (mode === 'full') console.error(` node_modules/cosmolo/templates/full/`);
136
- if (isSSG) {
137
- console.error('\n For SSG prerendering, add this to src/routes/+layout.ts manually:');
138
- console.error(' export const prerender = true;');
127
+ console.log('\nThe following files already exist:\n');
128
+ for (const f of conflicts) console.log(` ${f}`);
129
+
130
+ let overwriteRaw = '';
131
+ while (!['y', 'n'].includes(overwriteRaw)) {
132
+ overwriteRaw = (await ask(rl, '\nOverwrite all? [y/N]: ')).trim().toLowerCase() || 'n';
133
+ if (!['y', 'n'].includes(overwriteRaw)) console.log(' Please enter y or n.');
134
+ }
135
+
136
+ if (overwriteRaw === 'n') {
137
+ console.log('\nAborted. No files were written.\n');
138
+ rl.close();
139
+ process.exit(0);
139
140
  }
140
- process.exit(1);
141
141
  }
142
142
 
143
+ rl.close();
144
+
143
145
  // ── Write files ─────────────────────────────────────────────────────────
144
146
  for (const [src, rel] of allFiles) {
145
147
  const dest = destPath(rel, PROJECT_ROOT);