@stacksjs/cms 0.74.64 → 0.74.66
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/dist/build.js +20 -271
- 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>© ${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
|
|
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">
|
|
515
|
-
<h2>Get new
|
|
516
|
-
<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="
|
|
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||"
|
|
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 →</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||"
|
|
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}/`}">← Newer</a>`:"",next=page<totalPages?`<a href="/page/${page+1}/">Older →</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
|
-
|
|
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,
|
|
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
|
|
593
|
-
<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
|
|
600
|
+
<link>${origin}/</link>
|
|
602
601
|
<description>${escapeXml(config.description)}</description>
|
|
603
|
-
<atom:link href="
|
|
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,
|
|
609
|
-
<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
|
|
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
|
|
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.
|
|
5
|
+
"version": "0.74.66",
|
|
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.
|
|
62
|
-
"@stacksjs/error-handling": "0.74.
|
|
63
|
-
"@stacksjs/orm": "0.74.
|
|
64
|
-
"@stacksjs/path": "0.74.
|
|
65
|
-
"@stacksjs/sites": "0.74.
|
|
66
|
-
"@stacksjs/slug": "0.74.
|
|
67
|
-
"@stacksjs/stx": "^0.2.
|
|
68
|
-
"@stacksjs/validation": "0.74.
|
|
61
|
+
"@stacksjs/database": "0.74.66",
|
|
62
|
+
"@stacksjs/error-handling": "0.74.66",
|
|
63
|
+
"@stacksjs/orm": "0.74.66",
|
|
64
|
+
"@stacksjs/path": "0.74.66",
|
|
65
|
+
"@stacksjs/sites": "0.74.66",
|
|
66
|
+
"@stacksjs/slug": "0.74.66",
|
|
67
|
+
"@stacksjs/stx": "^0.2.306",
|
|
68
|
+
"@stacksjs/validation": "0.74.66",
|
|
69
69
|
"ts-slug": "^0.1.0",
|
|
70
70
|
"ts-spreadsheets": "^0.2.0"
|
|
71
71
|
},
|