stack-site-builder 1.14.0 → 1.16.0

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/CHANGELOG.md CHANGED
@@ -11,6 +11,54 @@ content schema, while a consuming site supplies only content, taxonomy data and
11
11
  config. Sites track the theme with `pnpm up stack-site-builder`, so each release
12
12
  here is a plain version bump they pull in.
13
13
 
14
+ ## [1.16.0] - 2026-07-22
15
+
16
+ ### Added
17
+
18
+ - **`apps` collection — product landings** — Things-style marketing pages
19
+ driven entirely by frontmatter (`template: 'landing'`): hero with app icon,
20
+ App Store / Google Play buttons (`"#"` renders disabled with a
21
+ "coming soon" label) and a Product Hunt badge; alternating feature rows
22
+ with optional device-frame screenshots and auto-rotating carousels; a
23
+ video "themes" showcase with tab switching; a highlights grid; pricing
24
+ tiers with a featured ribbon; a closing CTA; and legal links. Entries may
25
+ nest — `apps/<lang>/<slug>/privacy.mdx` renders at
26
+ `/apps/<slug>/privacy/` as a plain prose page (`template: 'page'`) — via
27
+ one catch-all route. Landing media (icons, screenshots, videos, frame art)
28
+ are `public/` paths. `nav: true` puts a landing in the header nav. The
29
+ section is on by default; an empty collection builds zero pages.
30
+ - **Data-driven "cards" home** — a site that isn't a catalog can declare
31
+ `home: { template: 'cards', hero, cards, cta }` in `src/data/site.ts` and
32
+ get a hero + wide-card grid + CTA homepage instead of the stack catalog.
33
+ Localized strings use per-locale records (`{ ko: '…', en: '…' }`) with
34
+ default-locale fallback, like category labels. On a cards home the
35
+ header's catalog-anchored Browse link hides itself; the catalog routes
36
+ still build (empty without stacks).
37
+
38
+ ## [1.15.0] - 2026-07-22
39
+
40
+ ### Added
41
+
42
+ - **`courses` collection** — an opt-in section (`sections: { courses: true }`)
43
+ for sites that teach rather than catalog: course cards with difficulty stars
44
+ (`level` 1–5, localized labels), duration (`hours`), a manual sort key
45
+ (`order`, highest first — e.g. `"2601-01"` cohort keys), a `type` tag, linked
46
+ slide decks (`slides`), related courses, and the usual draft/private/teaser
47
+ flags. Routes mirror concepts (`/course/`, `/course/<id>/`,
48
+ `/course/category/<id>/`); an enabling site adds
49
+ `src/data/course-categories.ts` (exporting `courseTree` / `courseCatOf`) and
50
+ may pass `courseCategoryMap` to `defineAasCollections` for build-time
51
+ category validation. Because the section needs that site data, it stays off
52
+ until a site explicitly opts in — a theme upgrade alone changes nothing.
53
+ - **Body components** — `Bookmark` (link-preview card), `Embed` (responsive
54
+ iframe wrapper for demos/videos, with `ratio`/`height`/`sandbox`), and
55
+ `Lead` (intro paragraph), importable from
56
+ `stack-site-builder/components/*` in any MDX body.
57
+ - **RSS feed** — a per-locale feed of the articles collection at `/rss.xml`
58
+ (default locale) and `/<code>/rss.xml`, injected with the `articles` section
59
+ and advertised via `<link rel="alternate">`. Drafts and private entries stay
60
+ out, mirroring the sitemap.
61
+
14
62
  ## [1.14.0] - 2026-07-21
15
63
 
16
64
  ### Added
@@ -138,6 +186,8 @@ catalog sites from a thin content-only repository.
138
186
  - **Standalone development setup** — a devcontainer and a minimal `playground/`
139
187
  consuming site for developing and previewing the theme on its own.
140
188
 
189
+ [1.16.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.15.0...v1.16.0
190
+ [1.15.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.14.0...v1.15.0
141
191
  [1.14.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.13.0...v1.14.0
142
192
  [1.13.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.12.0...v1.13.0
143
193
  [1.12.0]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.11.0...v1.12.0
package/README.md CHANGED
@@ -38,9 +38,9 @@ export const collections = defineAasCollections({ categoryMap });
38
38
  | --- | --- |
39
39
  | `src/data/site.ts` | Site identity: name, repo URL, the `locales` it ships, optional `sections` toggles, per-locale UI string overrides |
40
40
  | `src/data/categories.ts` | The tool-catalog category tree (validated against content) |
41
- | `src/data/concept-categories.ts` · `article-categories.ts` | Taxonomies for concepts / articles |
41
+ | `src/data/concept-categories.ts` · `article-categories.ts` · `course-categories.ts` (opt-in) | Taxonomies for concepts / articles / courses |
42
42
  | `src/data/glossary.mjs` | `[[Term]]` wikilink targets |
43
- | `src/content/{stacks,concepts,articles,slides}/` | The content, one MDX file per locale |
43
+ | `src/content/{stacks,concepts,courses,articles,slides}/` | The content, one MDX file per locale |
44
44
  | `src/content/pages/` | Standalone top-level pages (e.g. an About/소개), rendered at `/<slug>/` and optionally linked in the header nav |
45
45
  | `public/` · `samples/` | Logos/favicons and runnable sample projects |
46
46
 
@@ -72,7 +72,7 @@ The rest are opt-out — **concepts, articles, samples, slides, glossary** and t
72
72
  standalone **pages** collection (About/소개, …) — so a site can ship only what it
73
73
  needs. Turning one off removes both its routes and its header-nav item. (`pages`
74
74
  also has finer control: each page's `nav` / `draft` frontmatter, or simply not
75
- authoring it.)
75
+ authoring it.) **courses** is the one opt-IN section — see below.
76
76
 
77
77
  Declare the toggles once in `src/data/site.ts` and forward them to the theme in
78
78
  astro.config (which needs them to skip route injection). Import `SectionKey` from
@@ -93,6 +93,89 @@ import { site } from './src/data/site';
93
93
  integrations: [aasTheme({ glossary, sections: site.sections })];
94
94
  ```
95
95
 
96
+ ## Courses (opt-in)
97
+
98
+ A course section for sites that teach: cards with difficulty stars and
99
+ duration, cohort ordering, category browse pages, and paid courses gated by
100
+ the same private-content machinery. It stays off until a site opts in, because
101
+ it needs site data:
102
+
103
+ 1. `sections: { courses: true }` in `src/data/site.ts` (forwarded to
104
+ `aasTheme` as above).
105
+ 2. `src/data/course-categories.ts` exporting `courseTree` / `courseCatOf`
106
+ (copy `playground/src/data/course-categories.ts` and edit the tree).
107
+ 3. Optionally pass the map to
108
+ `defineAasCollections({ categoryMap, courseCategoryMap })` so course
109
+ category ids are validated at build time.
110
+
111
+ Then author `src/content/courses/<lang>/<slug>.mdx`:
112
+
113
+ ```yaml
114
+ title: Getting Started with AI Agents
115
+ description: A hands-on introduction.
116
+ date: 2026-06-01
117
+ category: ai-basics # id from course-categories.ts
118
+ level: 2 # difficulty 1–5, shown as stars
119
+ hours: "1:30" # duration, shown verbatim
120
+ order: "2601-01" # manual sort key, highest first (optional)
121
+ slides: [deck-id] # decks in the slides collection (optional)
122
+ private: true # paid course — body ships encrypted
123
+ teaser: A public one-liner for the login gate.
124
+ ```
125
+
126
+ Routes mirror concepts: `/course/`, `/course/<slug>/`, `/course/category/<id>/`.
127
+
128
+ ## Apps (product landings)
129
+
130
+ The `apps` collection renders Things-style marketing pages from frontmatter
131
+ alone (`template: 'landing'`): hero with store buttons and an optional Product
132
+ Hunt badge, alternating feature rows (device-frame screenshots, auto-rotating
133
+ carousels), a video themes showcase, a highlights grid, pricing tiers, a
134
+ closing CTA and legal links. Landing media are `public/` paths. Entries nest:
135
+ `apps/<lang>/flowstate.mdx` → `/apps/flowstate/`, and
136
+ `apps/<lang>/flowstate/privacy.mdx` → `/apps/flowstate/privacy/` (a plain
137
+ prose page). `nav: true` adds a header link. See
138
+ `playground/src/content/apps/` for a complete example.
139
+
140
+ ## Homepage
141
+
142
+ The default home is the stack catalog. A site that isn't a catalog can swap in
143
+ a data-driven home from `src/data/site.ts`:
144
+
145
+ ```ts
146
+ home: {
147
+ template: 'cards',
148
+ hero: { icon: '/img/logo.png', subtitle: { ko: '…', en: '…' } },
149
+ cardsTitle: { ko: '앱', en: 'Apps' },
150
+ cards: [{ href: '/apps/flowstate/', name: 'FlowState', icon: '/img/icon.png',
151
+ rounded: true, description: { ko: '…', en: '…' }, tags: ['iOS'] }],
152
+ cta: { title: { … }, description: { … }, button: { label: { … }, href: '/course/' } },
153
+ },
154
+ ```
155
+
156
+ Localized values are either one string or a per-locale record with
157
+ default-locale fallback. On a cards home the header's Browse link (which
158
+ anchors into the catalog) hides itself.
159
+
160
+ ## Body components
161
+
162
+ Reusable MDX-body components, importable from any collection's content:
163
+ `Bookmark` (link-preview card), `Embed` (responsive iframe for demos/videos —
164
+ `ratio`, `height`, `sandbox`), `Lead` (intro paragraph).
165
+
166
+ ```mdx
167
+ import Bookmark from 'stack-site-builder/components/Bookmark.astro';
168
+
169
+ <Bookmark url="https://…" title="…" description="…" />
170
+ ```
171
+
172
+ ## RSS
173
+
174
+ The articles collection feeds `/rss.xml` (default locale) and
175
+ `/<code>/rss.xml`, advertised with `<link rel="alternate">`. Drafts and
176
+ private entries stay out (mirroring the sitemap); the feed is injected only
177
+ while the `articles` section is on.
178
+
96
179
  ## Private content
97
180
 
98
181
  Any entry (tools, concepts, articles, slides, pages) can require login:
package/index.d.ts CHANGED
@@ -1,16 +1,27 @@
1
1
  import type { AstroIntegration } from 'astro';
2
2
 
3
3
  /** Optional content sections that a site can turn off. `pages` is the
4
- * standalone-pages collection (About/소개, …). */
5
- export type SectionKey = 'concepts' | 'articles' | 'samples' | 'slides' | 'glossary' | 'pages';
4
+ * standalone-pages collection (About/소개, …); `courses` is opt-IN
5
+ * (default off enabling it requires site-side course data). */
6
+ export type SectionKey =
7
+ | 'concepts'
8
+ | 'articles'
9
+ | 'courses'
10
+ | 'apps'
11
+ | 'samples'
12
+ | 'slides'
13
+ | 'glossary'
14
+ | 'pages';
6
15
 
7
16
  export interface AasThemeOptions {
8
17
  /** The site's glossary (`src/data/glossary.mjs`) — `[[wikilink]]` targets. */
9
18
  glossary: Record<string, unknown>;
10
19
  /**
11
- * Turn optional sections off (all on by default), e.g. `{ slides: false }`.
12
- * A disabled section's routes aren't injected; pass the same object to
13
- * `src/data/site.ts` `sections` so its header-nav item is hidden too.
20
+ * Section toggles, e.g. `{ slides: false }`. Every section is on by default
21
+ * except `courses`, which is opt-in (`{ courses: true }`) and additionally
22
+ * requires `src/data/course-categories.ts` on the site. A disabled section's
23
+ * routes aren't injected; pass the same object to `src/data/site.ts`
24
+ * `sections` so its header-nav item is hidden too.
14
25
  */
15
26
  sections?: Partial<Record<SectionKey, boolean>>;
16
27
  }
package/index.mjs CHANGED
@@ -29,6 +29,11 @@ const PAGES = [
29
29
  // Standalone top-level pages (the `pages` collection), e.g. an About/소개
30
30
  // page. A single dynamic route per locale renders every entry at `/<slug>/`.
31
31
  '[page].astro',
32
+ // App/product pages (the `apps` collection): marketing landings and their
33
+ // nested subpages (privacy/terms) via one catch-all.
34
+ 'apps/[...id].astro',
35
+ // Per-locale RSS feed of the articles collection (an endpoint, not a page).
36
+ 'rss.xml.ts',
32
37
  'article/index.astro',
33
38
  'article/[...id].astro',
34
39
  'article/category/[id].astro',
@@ -36,6 +41,9 @@ const PAGES = [
36
41
  'concept/index.astro',
37
42
  'concept/[...id].astro',
38
43
  'concept/category/[id].astro',
44
+ 'course/index.astro',
45
+ 'course/[...id].astro',
46
+ 'course/category/[id].astro',
39
47
  'glossary.astro',
40
48
  'sample/index.astro',
41
49
  'sample/[folder].astro',
@@ -47,10 +55,11 @@ const PAGES = [
47
55
  ];
48
56
 
49
57
  /** `[...lang]/article/[...id].astro` → `/[...lang]/article/[...id]`,
50
- * `[...lang]/index.astro` → `/[...lang]` */
58
+ * `[...lang]/index.astro` → `/[...lang]`,
59
+ * `[...lang]/rss.xml.ts` → `/[...lang]/rss.xml` (endpoints keep their name). */
51
60
  /** @param {string} file */
52
61
  function patternOf(file) {
53
- const p = file.replace(/\.astro$/, '').replace(/\/?index$/, '');
62
+ const p = file.replace(/\.(astro|ts)$/, '').replace(/\/?index$/, '');
54
63
  return `/${p}`.replace(/\/$/, '') || '/';
55
64
  }
56
65
 
@@ -60,19 +69,28 @@ function patternOf(file) {
60
69
  function sectionOf(file) {
61
70
  if (file.startsWith('concept/')) return 'concepts';
62
71
  if (file.startsWith('article/')) return 'articles';
72
+ if (file.startsWith('course/')) return 'courses';
73
+ if (file.startsWith('apps/')) return 'apps';
63
74
  if (file.startsWith('sample/')) return 'samples';
64
75
  if (file.startsWith('slides/')) return 'slides';
65
76
  if (file === 'glossary.astro') return 'glossary';
66
77
  if (file === '[page].astro') return 'pages';
78
+ if (file === 'rss.xml.ts') return 'articles'; // the feed is the blog's
67
79
  return null;
68
80
  }
69
81
 
82
+ // Sections that are opt-IN rather than opt-out: their routes are injected only
83
+ // when the site passes `{ <key>: true }`. `courses` needs site-side data
84
+ // (src/data/course-categories.ts), so a theme upgrade alone must not enable it.
85
+ const OPT_IN_SECTIONS = new Set(['courses']);
86
+
70
87
  /**
71
88
  * @param {object} opts
72
89
  * @param {Record<string, any>} opts.glossary — the site's glossary
73
90
  * (`src/data/glossary.mjs`), used by `[[wikilink]]` resolution.
74
91
  * @param {Partial<Record<string, boolean>>} [opts.sections] — optional-section
75
92
  * toggles (`{ slides: false }`); a disabled section's routes are not injected.
93
+ * `courses` is opt-IN (`{ courses: true }`) — it needs site-side course data.
76
94
  * Keep it in sync with `src/data/site.ts` `sections` (which hides the nav item).
77
95
  * @returns {import('astro').AstroIntegration[]}
78
96
  */
@@ -97,6 +115,7 @@ export default function aasTheme({ glossary, sections = {} }) {
97
115
  for (const file of PAGES) {
98
116
  const section = sectionOf(file);
99
117
  if (section && sections[section] === false) continue;
118
+ if (section && OPT_IN_SECTIONS.has(section) && sections[section] !== true) continue;
100
119
  injectRoute({
101
120
  pattern: patternOf(`[...lang]/${file}`),
102
121
  entrypoint: `stack-site-builder/pages/[...lang]/${file}`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stack-site-builder",
3
3
  "type": "module",
4
- "version": "1.14.0",
4
+ "version": "1.16.0",
5
5
  "license": "MIT",
6
6
  "description": "The engine behind the awesome-*-stack catalog sites: an Astro theme with the catalog/concepts/articles/slides/samples routes, components, styles and markdown pipeline. Sites provide content, taxonomy data and config.",
7
7
  "repository": {
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@astrojs/mdx": "^4.2.0",
38
+ "@astrojs/rss": "^4.0.0",
38
39
  "@astrojs/sitemap": "^3.3.0",
39
40
  "@tailwindcss/vite": "^4.1.0",
40
41
  "markdown-it": "^14.2.0",