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 +50 -0
- package/README.md +86 -3
- package/index.d.ts +16 -5
- package/index.mjs +21 -2
- package/package.json +2 -1
- package/src/components/AppLanding.astro +445 -0
- package/src/components/Bookmark.astro +37 -0
- package/src/components/CardsHome.astro +108 -0
- package/src/components/CourseCard.astro +97 -0
- package/src/components/CourseDetail.astro +116 -0
- package/src/components/CourseIndex.astro +145 -0
- package/src/components/DifficultyStars.astro +34 -0
- package/src/components/Embed.astro +36 -0
- package/src/components/Lead.astro +8 -0
- package/src/components/RelatedCourses.astro +64 -0
- package/src/content.ts +200 -4
- package/src/i18n/ui.ts +33 -0
- package/src/layouts/BaseLayout.astro +36 -4
- package/src/lib/apps.ts +24 -0
- package/src/lib/courses.ts +26 -0
- package/src/lib/home.ts +61 -0
- package/src/lib/sections.ts +15 -2
- package/src/pages/[...lang]/apps/[...id].astro +67 -0
- package/src/pages/[...lang]/course/[...id].astro +35 -0
- package/src/pages/[...lang]/course/category/[id].astro +25 -0
- package/src/pages/[...lang]/course/index.astro +18 -0
- package/src/pages/[...lang]/index.astro +3 -1
- package/src/pages/[...lang]/rss.xml.ts +40 -0
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
|
-
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `src/data/
|
|
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.
|
|
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",
|