@stacksjs/cms 0.74.64 → 0.74.65

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/dist/build.js +20 -271
  2. package/package.json +9 -9
package/dist/build.js CHANGED
@@ -497,7 +497,6 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
497
497
  </a>
498
498
  <nav>
499
499
  <a href="/">Posts</a>
500
- <a href="https://stacksjs.com/docs">Docs</a>
501
500
  ${config.enableRss?'<a href="/feed.xml">RSS</a>':""}
502
501
  ${config.social.github?`<a href="https://github.com/${config.social.github}" target="_blank" rel="noopener">GitHub</a>`:""}
503
502
  </nav>
@@ -509,18 +508,18 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
509
508
  <p>&copy; ${new Date().getFullYear()} ${escapeHtml(config.title)}. Built with <a href="https://stacksjs.com">Stacks</a>.</p>
510
509
  </footer>
511
510
  </body>
512
- </html>`}function generateNewsletterCapture(source="blog-static"){return`<section id="newsletter" class="newsletter-card" aria-label="Subscribe to Stacks updates">
511
+ </html>`}function generateNewsletterCapture(config,source="blog-static"){return`<section id="newsletter" class="newsletter-card" aria-label="Subscribe by email">
513
512
  <div class="newsletter-content">
514
- <div class="newsletter-eyebrow">Trail dispatch</div>
515
- <h2>Get new Stacks notes by email</h2>
516
- <p>Short framework updates, release notes, and field guides for building with Stacks.</p>
513
+ <div class="newsletter-eyebrow">Newsletter</div>
514
+ <h2>Get new posts by email</h2>
515
+ <p>New posts from ${escapeHtml(config.title)}, sent when they are published.</p>
517
516
  </div>
518
- <form class="newsletter-form" action="https://stacksjs.com/api/email/subscribe" method="POST">
517
+ <form class="newsletter-form" action="/api/email/subscribe" method="POST">
519
518
  <input type="hidden" name="source" value="${escapeHtml(source)}">
520
519
  <input type="email" name="email" placeholder="you@example.com" autocomplete="email" required>
521
520
  <button type="submit">Subscribe</button>
522
521
  </form>
523
- </section>`}function generatePostCard(post,author){const slug=getSlug(post),date=post.published_at?formatDate(post.published_at):"",authorName=author?.name||"Stacks Team",excerpt=(post.excerpt||(post.body||post.content||"").slice(0,220)).replace(/[#*`\[\]]/g,"").trim(),featured=post.is_featured?'<span class="featured-badge">Featured</span>':"",readTime=estimateReadingTime(post.body||post.content||"");return`<li class="post-item">
522
+ </section>`}function generatePostCard(post,config,author){const slug=getSlug(post),date=post.published_at?formatDate(post.published_at):"",authorName=author?.name||config.author||"",excerpt=(post.excerpt||(post.body||post.content||"").slice(0,220)).replace(/[#*`\[\]]/g,"").trim(),featured=post.is_featured?'<span class="featured-badge">Featured</span>':"",readTime=estimateReadingTime(post.body||post.content||"");return`<li class="post-item">
524
523
  <h2 class="post-title"><a href="/posts/${escapeHtml(slug)}/">${escapeHtml(post.title)}${featured}</a></h2>
525
524
  <div class="post-meta">
526
525
  <span>${escapeHtml(authorName)}</span>
@@ -531,7 +530,7 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
531
530
  </div>
532
531
  <p class="post-excerpt">${escapeHtml(excerpt)}${excerpt.length>=200?"...":""}</p>
533
532
  <a href="/posts/${escapeHtml(slug)}/" class="read-more">Read more &rarr;</a>
534
- </li>`}function generatePostPage(post,config,author){const date=post.published_at?formatDate(post.published_at):"",bodyContent=post.body||post.content||"",authorName=author?.name||"Stacks Team",readTime=estimateReadingTime(bodyContent),authorHtml=author?`<div class="author-info">
533
+ </li>`}function generatePostPage(post,config,author){const date=post.published_at?formatDate(post.published_at):"",bodyContent=post.body||post.content||"",authorName=author?.name||config.author||"",readTime=estimateReadingTime(bodyContent),authorHtml=author?`<div class="author-info">
535
534
  ${author.avatar?`<img src="${escapeHtml(author.avatar)}" alt="${escapeHtml(author.name)}" class="author-avatar" />`:""}
536
535
  <div>
537
536
  <div class="author-name">${escapeHtml(author.name)}</div>
@@ -567,7 +566,7 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
567
566
  ${renderMarkdownish(bodyContent)}
568
567
  </div>
569
568
  ${authorFooter}
570
- </article>`;return generateLayout(config,post.title,content,{isPost:!0})}function generateIndexPage(posts,config,authors,page,totalPages){const postCards=posts.map((post)=>{const author=post.author_id?authors.get(post.author_id):void 0;return generatePostCard(post,author)}).join(`
569
+ </article>`;return generateLayout(config,post.title,content,{isPost:!0})}function generateIndexPage(posts,config,authors,page,totalPages){const postCards=posts.map((post)=>{const author=post.author_id?authors.get(post.author_id):void 0;return generatePostCard(post,config,author)}).join(`
571
570
  `);let pagination="";if(totalPages>1){const prev=page>1?`<a href="${page===2?"/":`/page/${page-1}/`}">&larr; Newer</a>`:"",next=page<totalPages?`<a href="/page/${page+1}/">Older &rarr;</a>`:"";pagination=`<div class="pagination">${prev} ${next}</div>`}const content=`
572
571
  ${page===1?`
573
572
  <div class="hero">
@@ -583,14 +582,14 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
583
582
  <h1>${escapeHtml(config.title)}</h1>
584
583
  <p>${escapeHtml(config.description)}</p>
585
584
  </div>`:""}
586
- ${page===1?generateNewsletterCapture():""}
587
- <ul class="post-list">
585
+ ${page===1?generateNewsletterCapture(config):""}
586
+ ${posts.length>0?`<ul class="post-list">
588
587
  ${postCards}
589
- </ul>
590
- ${pagination}`;return generateLayout(config,config.title,content)}function generateRssFeed(posts,config,domain){const items=posts.map((post)=>{const slug=getSlug(post),pubDate=post.published_at?formatRssDate(post.published_at):"",description=post.excerpt||(post.body||post.content||"").slice(0,300);return` <item>
588
+ </ul>`:'<p class="post-excerpt">No posts yet.</p>'}
589
+ ${pagination}`;return generateLayout(config,config.title,content)}function generateRssFeed(posts,config,origin){const items=posts.map((post)=>{const slug=getSlug(post),pubDate=post.published_at?formatRssDate(post.published_at):"",description=post.excerpt||(post.body||post.content||"").slice(0,300);return` <item>
591
590
  <title>${escapeXml(post.title)}</title>
592
- <link>https://${domain}/posts/${escapeXml(slug)}/</link>
593
- <guid>https://${domain}/posts/${escapeXml(slug)}/</guid>
591
+ <link>${origin}/posts/${escapeXml(slug)}/</link>
592
+ <guid>${origin}/posts/${escapeXml(slug)}/</guid>
594
593
  <description>${escapeXml(description)}</description>
595
594
  ${pubDate?`<pubDate>${pubDate}</pubDate>`:""}
596
595
  </item>`}).join(`
@@ -598,277 +597,27 @@ import{copyFileSync,existsSync,mkdirSync,readdirSync,writeFileSync}from"node:fs"
598
597
  <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
599
598
  <channel>
600
599
  <title>${escapeXml(config.title)}</title>
601
- <link>https://${domain}/</link>
600
+ <link>${origin}/</link>
602
601
  <description>${escapeXml(config.description)}</description>
603
- <atom:link href="https://${domain}/feed.xml" rel="self" type="application/rss+xml"/>
602
+ <atom:link href="${origin}/feed.xml" rel="self" type="application/rss+xml"/>
604
603
  <language>en-us</language>
605
604
  <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
606
605
  ${items}
607
606
  </channel>
608
- </rss>`}function generateSitemap(posts,_config,domain){const urls=posts.map((post)=>{const slug=getSlug(post),lastmod=post.updated_at||post.published_at||"";return` <url>
609
- <loc>https://${domain}/posts/${escapeXml(slug)}/</loc>
607
+ </rss>`}function generateSitemap(posts,_config,origin){const urls=posts.map((post)=>{const slug=getSlug(post),lastmod=post.updated_at||post.published_at||"";return` <url>
608
+ <loc>${origin}/posts/${escapeXml(slug)}/</loc>
610
609
  ${lastmod?`<lastmod>${new Date(lastmod).toISOString().split("T")[0]}</lastmod>`:""}
611
610
  <changefreq>weekly</changefreq>
612
611
  </url>`}).join(`
613
612
  `);return`<?xml version="1.0" encoding="UTF-8"?>
614
613
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
615
614
  <url>
616
- <loc>https://${domain}/</loc>
615
+ <loc>${origin}/</loc>
617
616
  <changefreq>daily</changefreq>
618
617
  <priority>1.0</priority>
619
618
  </url>
620
619
  ${urls}
621
- </urlset>`}function ensureDir(dir){mkdirSync(dir,{recursive:!0})}function getDefaultBlogPosts(){return[{id:1,title:"Introducing Stacks: A Full-Stack Framework for the Modern Web",slug:"introducing-stacks",content:"We are thrilled to announce the official launch of Stacks, a full-stack framework.",body:`We are thrilled to announce the official launch of **Stacks**, a full-stack framework designed to make building web applications, APIs, cloud infrastructure, and libraries a delightful experience.
622
-
623
- ## Why Stacks?
624
-
625
- The JavaScript ecosystem is incredibly rich, but building a production-ready application still requires gluing together dozens of tools, configurations, and deployment pipelines. Stacks changes that.
626
-
627
- With Stacks, you get a **unified, batteries-included framework** that handles everything from your database models and API routes to cloud infrastructure and documentation sites \u2014 all from a single, cohesive project.
628
-
629
- ## What Makes Stacks Different
630
-
631
- - **Model-View-Action (MVA)**: A fresh take on MVC that emphasizes clarity and simplicity
632
- - **Type-Safe by Default**: Built on TypeScript with deep type inference throughout
633
- - **Cloud-Native**: Define your AWS infrastructure in \`config/cloud.ts\` and deploy with \`./buddy deploy\`
634
- - **Zero-Config DX**: Linting, testing, CI/CD pipelines, and documentation generation \u2014 all preconfigured
635
- - **Library Extraction**: Build your app, then extract reusable STX components and TypeScript functions as publishable packages
636
-
637
- ## Getting Started
638
-
639
- Getting started with Stacks is simple:
640
-
641
- \`\`\`bash
642
- panx @stacksjs/buddy new my-app
643
- cd my-app
644
- ./buddy dev
645
- \`\`\`
646
-
647
- This gives you a fully configured project with a dev server, database, API routes, and more \u2014 all ready to go.
648
-
649
- ## What's Next
650
-
651
- We are actively working on expanding Stacks with more features, better documentation, and a growing ecosystem of plugins. Follow us on [GitHub](https://github.com/stacksjs/stacks) and join the conversation.
652
-
653
- The future of full-stack development is here. Let's build something great together.`,excerpt:"We are thrilled to announce the official launch of Stacks, a full-stack framework designed to make building web applications, APIs, and cloud infrastructure a delightful experience.",status:"published",published_at:"2026-02-20T10:00:00.000Z",views:1250,is_featured:1,created_at:"2026-02-20T10:00:00.000Z",updated_at:"2026-02-20T10:00:00.000Z"},{id:2,title:"Deploying to AWS with a Single Command",slug:"deploying-to-aws",content:"Learn how Stacks makes cloud deployment as simple as running ./buddy deploy.",body:`One of the most powerful features of Stacks is its built-in cloud deployment pipeline. Instead of wrestling with Terraform, CDK, or manual AWS console clicks, you can deploy your entire application - API, frontend, docs, and blog - with a single command.
654
-
655
- ## The Problem with Cloud Deployment
656
-
657
- Most teams spend weeks setting up their deployment pipelines. You need to configure CloudFormation or Terraform templates, set up CI/CD, manage SSL certificates, configure CloudFront distributions, and handle database migrations. It's tedious and error-prone.
658
-
659
- ## How Stacks Solves It
660
-
661
- With Stacks, your cloud infrastructure is defined in a simple TypeScript configuration file:
662
-
663
- \`\`\`typescript
664
- // config/cloud.ts
665
- export const tsCloud = {
666
- project: { name: 'my-app', region: 'us-east-1' },
667
- infrastructure: {
668
- compute: { instances: 1, size: 'small' },
669
- storage: {
670
- public: { website: { indexDocument: 'index.html' } },
671
- docs: { website: { indexDocument: 'index.html' } },
672
- },
673
- ssl: { enabled: true, domains: ['myapp.com', 'docs.myapp.com'] },
674
- dns: { domain: 'myapp.com' },
675
- },
676
- }
677
- \`\`\`
678
-
679
- Then deploy everything:
680
-
681
- \`\`\`bash
682
- ./buddy deploy --yes
683
- \`\`\`
684
-
685
- ## What Happens Under the Hood
686
-
687
- When you run \`./buddy deploy\`, Stacks:
688
-
689
- - **Generates a CloudFormation template** from your config
690
- - **Provisions EC2 instances** with your Bun application
691
- - **Creates S3 buckets** for static sites (frontend, docs, blog)
692
- - **Sets up CloudFront** distributions with SSL certificates
693
- - **Configures Route53** DNS records
694
- - **Runs database migrations** on the remote server
695
- - **Uploads static assets** to S3 with proper cache headers
696
- - **Invalidates CloudFront** caches for instant updates
697
-
698
- All of this happens automatically, with progress output so you know exactly what's happening.
699
-
700
- ## Zero-Downtime Updates
701
-
702
- Subsequent deployments are incremental. Stacks detects what changed and only updates the necessary resources. Your users never experience downtime.
703
-
704
- ## Try It Yourself
705
-
706
- If you have an AWS account, you can deploy a Stacks app in under 10 minutes. Check out our [deployment guide](https://stacksjs.com/docs/bootcamp/deploy) to get started.`,excerpt:"Learn how Stacks makes cloud deployment as simple as running a single command. No Terraform, no CDK - just ./buddy deploy.",status:"published",published_at:"2026-02-18T14:00:00.000Z",views:840,is_featured:0,created_at:"2026-02-18T14:00:00.000Z",updated_at:"2026-02-18T14:00:00.000Z"},{id:3,title:"Building Type-Safe APIs with the Stacks ORM",slug:"type-safe-apis",content:"Discover how Stacks models auto-generate fully typed API endpoints.",body:`One of the most tedious parts of building a web application is writing CRUD boilerplate. With Stacks, your models automatically generate fully typed API endpoints, database migrations, factories, and seeders.
707
-
708
- ## Define a Model, Get an API
709
-
710
- Here's what a typical Stacks model looks like:
711
-
712
- \`\`\`typescript
713
- // app/Models/Post.ts
714
- export default defineModel({
715
- name: 'Post',
716
- table: 'posts',
717
- traits: {
718
- useTimestamps: true,
719
- useApi: {
720
- uri: 'posts',
721
- routes: ['index', 'store', 'show', 'update', 'destroy'],
722
- },
723
- },
724
- attributes: {
725
- title: {
726
- validation: { rule: schema.string().min(3).max(255) },
727
- factory: faker => faker.lorem.sentence(),
728
- },
729
- slug: {
730
- unique: true,
731
- validation: { rule: schema.string().min(3).max(255) },
732
- },
733
- body: {
734
- validation: { rule: schema.string() },
735
- },
736
- },
737
- })
738
- \`\`\`
739
-
740
- From this single file, Stacks generates:
741
-
742
- - **API routes**: \`GET /api/posts\`, \`POST /api/posts\`, \`GET /api/posts/:id\`, \`PATCH /api/posts/:id\`, \`DELETE /api/posts/:id\`
743
- - **Database migration**: Creates the \`posts\` table with all columns
744
- - **Factory**: Generates realistic test data using Faker
745
- - **Seeder**: Populates your database with sample data
746
- - **TypeScript types**: Full type inference for queries and responses
747
-
748
- ## Type-Safe Queries
749
-
750
- The Stacks ORM is built on Kysely, giving you fully type-safe database queries:
751
-
752
- \`\`\`typescript
753
- const posts = await db
754
- .selectFrom('posts')
755
- .where('status', '=', 'published')
756
- .orderBy('published_at', 'desc')
757
- .selectAll()
758
- .execute()
759
- \`\`\`
760
-
761
- Every column name, operator, and value is type-checked at compile time. Typos become compile errors, not runtime bugs.
762
-
763
- ## Relationships
764
-
765
- Stacks supports all common relationship types with a clean, declarative syntax:
766
-
767
- \`\`\`typescript
768
- export default defineModel({
769
- name: 'Post',
770
- belongsTo: ['Author'],
771
- traits: { taggable: true, categorizable: true, commentables: true },
772
- })
773
- \`\`\`
774
-
775
- ## What's Next
776
-
777
- We are working on even more ORM features \u2014 real-time subscriptions, full-text search integration, and automatic OpenAPI documentation generation. Stay tuned.`,excerpt:"Discover how Stacks models auto-generate fully typed API endpoints, database migrations, and more from a single model definition.",status:"published",published_at:"2026-02-15T09:00:00.000Z",views:620,is_featured:0,created_at:"2026-02-15T09:00:00.000Z",updated_at:"2026-02-15T09:00:00.000Z"},{id:4,title:"Meet Buddy: Your CLI Companion for Stacks Development",slug:"meet-buddy-cli",content:"Buddy is the CLI tool that powers your entire Stacks development workflow.",body:`Every great framework needs a great CLI. In Stacks, that CLI is called **Buddy** - your companion for development, testing, deployment, and everything in between.
778
-
779
- ## What Can Buddy Do?
780
-
781
- Buddy is the single entry point for every task in your Stacks project:
782
-
783
- \`\`\`bash
784
- ./buddy dev # Start the development server
785
- ./buddy build # Build for production
786
- ./buddy test # Run your test suite
787
- ./buddy deploy # Deploy to the cloud
788
- ./buddy generate # Generate models, migrations, and more
789
- ./buddy lint # Lint and format your code
790
- ./buddy key:generate # Generate a new application key
791
- \`\`\`
792
-
793
- ## Developer Experience First
794
-
795
- Buddy is designed to feel fast and intuitive. Some highlights:
796
-
797
- - **Lazy-loaded commands**: Only the command you run is loaded, keeping startup under 100ms
798
- - **Interactive mode**: Run \`./buddy\` with no arguments for a guided menu
799
- - **Verbose mode**: Add \`--verbose\` to any command for detailed output
800
- - **Tab completion**: Full shell completion support for bash and zsh
801
-
802
- ## Extensible
803
-
804
- You can add your own commands by creating files in \`app/Commands/\`:
805
-
806
- \`\`\`typescript
807
- // app/Commands/Greet.ts
808
- export default function (buddy) {
809
- buddy
810
- .command('greet <name>', 'Greet someone')
811
- .action((name) => {
812
- console.log('Hello, ' + name + '!')
813
- })
814
- }
815
- \`\`\`
816
-
817
- Then run it:
818
-
819
- \`\`\`bash
820
- ./buddy greet World
821
- # Hello, World!
822
- \`\`\`
823
-
824
- ## Built on Bun
825
-
826
- Buddy runs on [Bun](https://bun.sh), giving it near-instant startup times and excellent TypeScript support without a build step. Your commands are executed directly from TypeScript source.
827
-
828
- ## Try It
829
-
830
- Start a new Stacks project and explore what Buddy can do. You might be surprised how much a good CLI can improve your workflow.`,excerpt:"Buddy is the CLI tool that powers your entire Stacks development workflow - from dev server to deployment, all in one place.",status:"published",published_at:"2026-02-12T11:30:00.000Z",views:450,is_featured:0,created_at:"2026-02-12T11:30:00.000Z",updated_at:"2026-02-12T11:30:00.000Z"},{id:5,title:"Documentation as a First-Class Citizen",slug:"documentation-first-class",content:"How Stacks makes writing and deploying documentation effortless.",body:`Good documentation is the difference between a framework people try and a framework people adopt. That's why Stacks treats documentation as a **first-class feature**, not an afterthought.
831
-
832
- ## Write Docs, Deploy Docs
833
-
834
- Every Stacks project comes with a \`docs/\` directory preconfigured. Write your documentation in Markdown, and Stacks builds it into a beautiful static site using [BunPress](https://github.com/stacksjs/bunpress).
835
-
836
- \`\`\`bash
837
- ./buddy dev:docs # Preview docs locally
838
- ./buddy deploy # Docs deploy automatically to docs.yoursite.com
839
- \`\`\`
840
-
841
- ## What You Get
842
-
843
- - **Beautiful defaults**: Clean, responsive design with dark mode support
844
- - **Sidebar navigation**: Automatically generated from your file structure
845
- - **Syntax highlighting**: Code blocks with proper language highlighting
846
- - **Search**: Built-in search functionality
847
- - **Automatic deployment**: Docs deploy to a dedicated S3 bucket + CloudFront CDN
848
-
849
- ## Powered by BunPress
850
-
851
- Under the hood, Stacks uses BunPress \u2014 a fast, minimal static site generator built on Bun. It takes your Markdown files and produces optimized HTML with:
852
-
853
- - Table of contents generation
854
- - Anchor links for headings
855
- - Responsive images
856
- - SEO-friendly output with sitemaps and meta tags
857
-
858
- ## Documentation is Part of Your Deploy
859
-
860
- When you run \`./buddy deploy\`, your documentation is automatically:
861
-
862
- - Built from the \`docs/\` directory
863
- - Uploaded to an S3 bucket
864
- - Served via CloudFront at \`docs.yourdomain.com\`
865
- - Cache-invalidated for instant updates
866
-
867
- No separate CI/CD pipeline needed. No extra configuration. It just works.
868
-
869
- ## Start Documenting
870
-
871
- Great software deserves great documentation. With Stacks, there's no excuse not to write it.`,excerpt:"How Stacks makes writing and deploying documentation effortless - from Markdown to a deployed docs site in seconds.",status:"published",published_at:"2026-02-10T08:00:00.000Z",views:380,is_featured:0,created_at:"2026-02-10T08:00:00.000Z",updated_at:"2026-02-10T08:00:00.000Z"}]}export async function buildBlogSite(options){const{config,outDir}=options,domain=`${config.subdomain}.stacksjs.com`;ensureDir(outDir);ensureDir(join(outDir,"posts"));copyBlogFonts(outDir);copyBlogImages(outDir);let posts,usedDefaults=!1;try{const dbPosts=await fetchPublishedPosts(),hasRealContent=dbPosts.some((p)=>p.slug&&p.slug!=="null"&&!p.title.endsWith("."));if(dbPosts.length===0||!hasRealContent){posts=getDefaultBlogPosts();usedDefaults=!0}else posts=dbPosts}catch{console.log(" Database not available, using default blog posts");posts=getDefaultBlogPosts();usedDefaults=!0}if(usedDefaults)console.log(" Using built-in blog posts (seed your database for custom content)");const authors=new Map;try{const authorRows=await fetchAuthors();for(const author of authorRows)authors.set(author.id,author)}catch{}const postsPerPage=config.postsPerPage||10,totalPages=Math.max(1,Math.ceil(posts.length/postsPerPage));for(let page=1;page<=totalPages;page++){const start=(page-1)*postsPerPage,pagePosts=posts.slice(start,start+postsPerPage),html=generateIndexPage(pagePosts,config,authors,page,totalPages);if(page===1)writeFileSync(join(outDir,"index.html"),html);if(totalPages>1){const pageDir=join(outDir,"page",String(page));ensureDir(pageDir);writeFileSync(join(pageDir,"index.html"),html)}}const generatedSlugs=new Set;for(const post of posts){const slug=getSlug(post);if(generatedSlugs.has(slug))continue;generatedSlugs.add(slug);const postDir=join(outDir,"posts",slug);ensureDir(postDir);const author=post.author_id?authors.get(post.author_id):void 0,html=generatePostPage(post,config,author);writeFileSync(join(postDir,"index.html"),html)}if(config.enableRss){const rss=generateRssFeed(posts.slice(0,20),config,domain);writeFileSync(join(outDir,"feed.xml"),rss)}if(config.enableSitemap){const sitemap=generateSitemap(posts,config,domain);writeFileSync(join(outDir,"sitemap.xml"),sitemap)}const notFoundContent=`
620
+ </urlset>`}function ensureDir(dir){mkdirSync(dir,{recursive:!0})}function siteOrigin(config){const raw=String(config.url||process.env.APP_URL||"").trim().replace(/\/+$/,"");if(!raw)return"";return/^https?:\/\//.test(raw)?raw:`https://${raw}`}export async function buildBlogSite(options){const{config,outDir}=options,origin=siteOrigin(config);ensureDir(outDir);ensureDir(join(outDir,"posts"));copyBlogFonts(outDir);copyBlogImages(outDir);let posts=[];try{const dbPosts=await fetchPublishedPosts();if(dbPosts.some((post)=>post.slug&&post.slug!=="null"&&!post.title.endsWith(".")))posts=dbPosts}catch{console.log(" Database not available, building an empty blog")}const authors=new Map;try{const authorRows=await fetchAuthors();for(const author of authorRows)authors.set(author.id,author)}catch{}const postsPerPage=config.postsPerPage||10,totalPages=Math.max(1,Math.ceil(posts.length/postsPerPage));for(let page=1;page<=totalPages;page++){const start=(page-1)*postsPerPage,pagePosts=posts.slice(start,start+postsPerPage),html=generateIndexPage(pagePosts,config,authors,page,totalPages);if(page===1)writeFileSync(join(outDir,"index.html"),html);if(totalPages>1){const pageDir=join(outDir,"page",String(page));ensureDir(pageDir);writeFileSync(join(pageDir,"index.html"),html)}}const generatedSlugs=new Set;for(const post of posts){const slug=getSlug(post);if(generatedSlugs.has(slug))continue;generatedSlugs.add(slug);const postDir=join(outDir,"posts",slug);ensureDir(postDir);const author=post.author_id?authors.get(post.author_id):void 0,html=generatePostPage(post,config,author);writeFileSync(join(postDir,"index.html"),html)}if(config.enableRss){const rss=generateRssFeed(posts.slice(0,20),config,origin);writeFileSync(join(outDir,"feed.xml"),rss)}if(config.enableSitemap){const sitemap=generateSitemap(posts,config,origin);writeFileSync(join(outDir,"sitemap.xml"),sitemap)}const notFoundContent=`
872
621
  <div style="text-align: center; padding: 6rem 0;">
873
622
  <p style="font-size: 5rem; font-weight: 800; letter-spacing: -0.03em; margin-bottom: 0.5rem;">404</p>
874
623
  <p style="color: var(--text-light); margin-bottom: 1.5rem; font-size: 1.125rem;">This page could not be found.</p>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/cms",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.64",
5
+ "version": "0.74.65",
6
6
  "description": "Stacks cms utilities.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -58,14 +58,14 @@
58
58
  "prepublishOnly": "bun run build"
59
59
  },
60
60
  "dependencies": {
61
- "@stacksjs/database": "0.74.64",
62
- "@stacksjs/error-handling": "0.74.64",
63
- "@stacksjs/orm": "0.74.64",
64
- "@stacksjs/path": "0.74.64",
65
- "@stacksjs/sites": "0.74.64",
66
- "@stacksjs/slug": "0.74.64",
67
- "@stacksjs/stx": "^0.2.304",
68
- "@stacksjs/validation": "0.74.64",
61
+ "@stacksjs/database": "0.74.65",
62
+ "@stacksjs/error-handling": "0.74.65",
63
+ "@stacksjs/orm": "0.74.65",
64
+ "@stacksjs/path": "0.74.65",
65
+ "@stacksjs/sites": "0.74.65",
66
+ "@stacksjs/slug": "0.74.65",
67
+ "@stacksjs/stx": "^0.2.306",
68
+ "@stacksjs/validation": "0.74.65",
69
69
  "ts-slug": "^0.1.0",
70
70
  "ts-spreadsheets": "^0.2.0"
71
71
  },