@rettangoli/sites 0.2.7 → 1.0.0-rc2
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/README.md +127 -96
- package/package.json +6 -7
- package/src/builtinTemplateFunctions.js +85 -0
- package/src/cli/build.js +16 -9
- package/src/cli/init.js +5 -5
- package/src/cli/watch.js +154 -180
- package/src/createSiteBuilder.js +107 -91
- package/src/markdownItAsync.js +104 -0
- package/src/rtglMarkdown.js +162 -13
- package/src/screenshotRunner.js +5 -169
- package/src/utils/loadSiteConfig.js +262 -36
- package/templates/default/README.md +53 -25
- package/templates/default/data/site.yaml +3 -0
- package/templates/default/pages/blog.yaml +1 -1
- package/templates/default/pages/index.yaml +1 -1
- package/templates/default/partials/footer.yaml +1 -1
- package/templates/default/partials/header.yaml +1 -1
- package/templates/default/sites.config.yaml +24 -0
- package/templates/default/templates/base.yaml +5 -3
- package/templates/default/templates/post.yaml +6 -4
- package/src/screenshot.js +0 -250
- package/templates/default/package.json +0 -14
- package/templates/default/sites.config.js +0 -9
package/README.md
CHANGED
|
@@ -1,133 +1,164 @@
|
|
|
1
1
|
# Rettangoli Sites
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@rettangoli/sites` is the static-site engine used by Rettangoli. It renders pages from YAML and Markdown into `_site/`, with support for templates, partials, global data, collections, and watch mode.
|
|
4
|
+
|
|
5
|
+
It can run directly with `bunx rtgl`, so a site-level `package.json` is optional.
|
|
4
6
|
|
|
5
7
|
## Quick Start
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
#
|
|
9
|
-
bunx rtgl sites init my-site
|
|
10
|
-
bunx rtgl sites init my-site -t default # Explicit template name
|
|
10
|
+
# scaffold
|
|
11
|
+
bunx rtgl sites init my-site
|
|
11
12
|
|
|
12
|
-
#
|
|
13
|
+
# run
|
|
13
14
|
cd my-site
|
|
14
|
-
|
|
15
|
-
bun run build
|
|
15
|
+
bunx rtgl sites build
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## Package Contract
|
|
19
19
|
|
|
20
|
-
```
|
|
20
|
+
```text
|
|
21
21
|
my-site/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
└── _site/ # Generated output
|
|
22
|
+
pages/ # YAML or Markdown pages (with optional frontmatter)
|
|
23
|
+
templates/ # YAML templates
|
|
24
|
+
partials/ # YAML partials
|
|
25
|
+
data/ # Global YAML data
|
|
26
|
+
static/ # Static assets copied to _site/
|
|
27
|
+
sites.config.yaml # Optional site settings
|
|
28
|
+
_site/ # Generated output
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
##
|
|
31
|
+
## What It Supports
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
```
|
|
33
|
+
- YAML pages rendered through `jempl` + `yahtml`
|
|
34
|
+
- Markdown pages rendered through `markdown-it` + Shiki (default `rtglMarkdown`)
|
|
35
|
+
- Frontmatter (`template`, `tags`, arbitrary page metadata)
|
|
36
|
+
- Global data (`data/*.yaml`) merged with page frontmatter
|
|
37
|
+
- Collections built from page tags
|
|
38
|
+
- `$if`, `$for`, `$partial`, template functions
|
|
39
|
+
- Static file copying from `static/` to `_site/`
|
|
40
|
+
- Watch mode with local dev server + websocket reload
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
```markdown
|
|
46
|
-
---
|
|
47
|
-
template: base
|
|
48
|
-
title: About
|
|
49
|
-
---
|
|
50
|
-
# About Us
|
|
42
|
+
## Site Config
|
|
51
43
|
|
|
52
|
-
|
|
44
|
+
Use `sites.config.yaml` (or `sites.config.yml`) with top-level `markdownit` for supported settings.
|
|
45
|
+
Legacy key `markdown` is still accepted as an alias.
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
markdownit:
|
|
49
|
+
preset: default
|
|
50
|
+
html: true
|
|
51
|
+
xhtmlOut: false
|
|
52
|
+
linkify: true
|
|
53
|
+
typographer: false
|
|
54
|
+
breaks: false
|
|
55
|
+
langPrefix: language-
|
|
56
|
+
quotes: "\u201c\u201d\u2018\u2019"
|
|
57
|
+
maxNesting: 100
|
|
58
|
+
shiki:
|
|
59
|
+
enabled: true
|
|
60
|
+
theme: slack-dark
|
|
61
|
+
codePreview:
|
|
62
|
+
enabled: false
|
|
63
|
+
showSource: true
|
|
64
|
+
theme: slack-dark
|
|
65
|
+
headingAnchors:
|
|
66
|
+
enabled: true
|
|
67
|
+
slugMode: unicode
|
|
68
|
+
wrap: true
|
|
69
|
+
fallback: section
|
|
70
|
+
build:
|
|
71
|
+
keepMarkdownFiles: false
|
|
53
72
|
```
|
|
54
73
|
|
|
55
|
-
|
|
74
|
+
In the default starter template, CDN runtime scripts are controlled via `data/site.yaml`:
|
|
56
75
|
|
|
57
76
|
```yaml
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# Attributes
|
|
63
|
-
- 'a href="/about"': About Us
|
|
64
|
-
- 'img src="/logo.png" alt="Logo"':
|
|
65
|
-
|
|
66
|
-
# Variables
|
|
67
|
-
- h1: ${title}
|
|
68
|
-
- p: ${site.description}
|
|
69
|
-
|
|
70
|
-
# Conditionals
|
|
71
|
-
- $if showBanner:
|
|
72
|
-
- div.banner: Hello!
|
|
73
|
-
|
|
74
|
-
# Loops
|
|
75
|
-
- $for item in items:
|
|
76
|
-
- li: ${item.name}
|
|
77
|
-
|
|
78
|
-
# Partials
|
|
79
|
-
- $partial: header
|
|
80
|
-
- $partial: card
|
|
81
|
-
title: My Card
|
|
82
|
-
description: Card content
|
|
77
|
+
assets:
|
|
78
|
+
loadUiFromCdn: true
|
|
79
|
+
loadConstructStyleSheetsPolyfill: true
|
|
83
80
|
```
|
|
84
81
|
|
|
85
|
-
|
|
82
|
+
Enable `codePreview` if you want fenced blocks like ```` ```html codePreview ```` to render a live preview panel.
|
|
83
|
+
Use `showSource` to show/hide the source pane and `theme` to override the highlight theme for preview blocks.
|
|
86
84
|
|
|
87
|
-
`
|
|
85
|
+
Set `build.keepMarkdownFiles: true` to keep source Markdown files in output in addition to generated HTML.
|
|
86
|
+
Example mappings:
|
|
87
|
+
- `pages/index.md` -> `_site/index.html` and `_site/index.md`
|
|
88
|
+
- `pages/docs/intro.md` -> `_site/docs/intro/index.html` and `_site/docs/intro.md`
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
If you want to publish a manual `llms.txt`, place it in `static/llms.txt`; it will be copied to `_site/llms.txt`.
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
## Commands
|
|
92
93
|
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
```bash
|
|
95
|
+
bunx rtgl sites build
|
|
96
|
+
bunx rtgl sites watch
|
|
97
|
+
bunx rtgl sites build --quiet
|
|
98
|
+
bunx rtgl sites watch --quiet
|
|
99
|
+
bunx rtgl sites watch --reload-mode full
|
|
100
|
+
bunx rtgl sites build --root-dir . --output-path dist
|
|
101
|
+
bunx rtgl sites watch --root-dir . --output-path dist --reload-mode full
|
|
98
102
|
```
|
|
99
103
|
|
|
104
|
+
`--reload-mode body` (default) does fast body replacement; `--reload-mode full` forces full page refresh.
|
|
105
|
+
`--root-dir`/`--output-path` are the preferred option names (`--rootDir`/`--outputPath` remain as legacy aliases).
|
|
106
|
+
|
|
107
|
+
## Built-in Template Functions
|
|
108
|
+
|
|
109
|
+
Available in YAML templates/pages without extra setup:
|
|
110
|
+
|
|
111
|
+
- `encodeURI(value)`
|
|
112
|
+
- `encodeURIComponent(value)`
|
|
113
|
+
- `decodeURI(value)`
|
|
114
|
+
- `decodeURIComponent(value)`
|
|
115
|
+
- `jsonStringify(value, space = 0)`
|
|
116
|
+
- `formatDate(value, format = "YYYYMMDDHHmmss", useUtc = true)`
|
|
117
|
+
- `now(format = "YYYYMMDDHHmmss", useUtc = true)`
|
|
118
|
+
- `toQueryString(object)`
|
|
119
|
+
|
|
120
|
+
`formatDate` tokens: `YYYY`, `MM`, `DD`, `HH`, `mm`, `ss`.
|
|
121
|
+
`decodeURI`/`decodeURIComponent` return the original input when decoding fails.
|
|
122
|
+
|
|
123
|
+
## Screenshots
|
|
124
|
+
|
|
125
|
+
`@rettangoli/sites` builds pages; screenshot capture is handled by `@rettangoli/vt`.
|
|
126
|
+
|
|
127
|
+
Use VT against your generated site:
|
|
128
|
+
|
|
129
|
+
1. Add `vt/specs/*.html` specs (use frontmatter `url` for the page to capture).
|
|
130
|
+
2. Add `vt` config in `rettangoli.config.yaml`.
|
|
131
|
+
3. Run `rtgl vt generate`, `rtgl vt report`, and `rtgl vt accept`.
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
|
|
100
135
|
```yaml
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
136
|
+
vt:
|
|
137
|
+
path: ./vt
|
|
138
|
+
url: http://127.0.0.1:4173
|
|
139
|
+
service:
|
|
140
|
+
start: bun run preview
|
|
141
|
+
sections:
|
|
142
|
+
- title: pages
|
|
143
|
+
files: .
|
|
104
144
|
```
|
|
105
145
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
functions: {
|
|
113
|
-
sortDate: (list) => list.sort((a, b) =>
|
|
114
|
-
new Date(b.data.date) - new Date(a.data.date)
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
146
|
+
```html
|
|
147
|
+
---
|
|
148
|
+
title: home
|
|
149
|
+
url: /
|
|
150
|
+
---
|
|
151
|
+
<div></div>
|
|
118
152
|
```
|
|
119
153
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
bun run build # Build site to _site/
|
|
124
|
-
bun run watch # Build + watch for changes
|
|
125
|
-
bun run serve # Serve _site/
|
|
126
|
-
bun run screenshot # Generate page screenshots
|
|
127
|
-
```
|
|
154
|
+
`bun run preview` (or any equivalent local server command) must serve your built site on `vt.url` (for example serving `_site/` on port `4173`).
|
|
128
155
|
|
|
129
|
-
##
|
|
156
|
+
## Full Architecture And Analysis
|
|
130
157
|
|
|
131
|
-
|
|
158
|
+
See `docs/architecture-and-analysis.md` for:
|
|
132
159
|
|
|
133
|
-
-
|
|
160
|
+
- End-to-end rendering flow
|
|
161
|
+
- Data/context model used during render
|
|
162
|
+
- URL/output mapping rules
|
|
163
|
+
- Config contract details
|
|
164
|
+
- Full robustness analysis and prioritized improvements
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rettangoli/sites",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Generate static sites using Markdown and YAML
|
|
3
|
+
"version": "1.0.0-rc2",
|
|
4
|
+
"description": "Generate static sites using Markdown and YAML for docs, blogs, and marketing sites.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Luciano Hanyon Wu",
|
|
7
7
|
"email": "han4wluc@yuusoft.com"
|
|
@@ -17,12 +17,11 @@
|
|
|
17
17
|
"templates"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"gray-matter": "^4.0.3",
|
|
20
21
|
"jempl": "^0.3.1-rc1",
|
|
21
22
|
"js-yaml": "^4.1.0",
|
|
22
23
|
"markdown-it": "^14.1.0",
|
|
23
|
-
"
|
|
24
|
-
"playwright": "^1.55.0",
|
|
25
|
-
"sharp": "^0.34.3",
|
|
24
|
+
"shiki": "^3.13.0",
|
|
26
25
|
"ws": "^8.18.0",
|
|
27
26
|
"yahtml": "0.0.4"
|
|
28
27
|
},
|
|
@@ -50,7 +49,7 @@
|
|
|
50
49
|
"dashboard"
|
|
51
50
|
],
|
|
52
51
|
"bugs": {
|
|
53
|
-
"url": "https://github.com/yuusoft-org/
|
|
52
|
+
"url": "https://github.com/yuusoft-org/rettangoli/issues"
|
|
54
53
|
},
|
|
55
|
-
"homepage": "https://github.com/yuusoft-org/
|
|
54
|
+
"homepage": "https://github.com/yuusoft-org/rettangoli/tree/main/packages/rettangoli-sites#readme"
|
|
56
55
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
function toDate(value) {
|
|
2
|
+
if (value instanceof Date) {
|
|
3
|
+
return value;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (value === undefined || value === null || value === '') {
|
|
7
|
+
return new Date();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return new Date(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function pad2(value) {
|
|
14
|
+
return String(value).padStart(2, '0');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatDateImpl(value, format = 'YYYYMMDDHHmmss', useUtc = true) {
|
|
18
|
+
const date = toDate(value);
|
|
19
|
+
if (Number.isNaN(date.getTime())) {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const read = (localGetter, utcGetter) => (useUtc ? utcGetter.call(date) : localGetter.call(date));
|
|
24
|
+
const tokens = {
|
|
25
|
+
YYYY: String(read(date.getFullYear, date.getUTCFullYear)),
|
|
26
|
+
MM: pad2(read(date.getMonth, date.getUTCMonth) + 1),
|
|
27
|
+
DD: pad2(read(date.getDate, date.getUTCDate)),
|
|
28
|
+
HH: pad2(read(date.getHours, date.getUTCHours)),
|
|
29
|
+
mm: pad2(read(date.getMinutes, date.getUTCMinutes)),
|
|
30
|
+
ss: pad2(read(date.getSeconds, date.getUTCSeconds)),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return String(format).replace(/YYYY|MM|DD|HH|mm|ss/g, (token) => tokens[token]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function jsonStringify(value, space = 0) {
|
|
37
|
+
const indent = Number.isFinite(Number(space))
|
|
38
|
+
? Math.max(0, Math.min(10, Math.trunc(Number(space))))
|
|
39
|
+
: 0;
|
|
40
|
+
const result = JSON.stringify(value, null, indent);
|
|
41
|
+
return result === undefined ? '' : result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function toQueryString(value) {
|
|
45
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const params = new URLSearchParams();
|
|
50
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
51
|
+
if (raw === undefined || raw === null) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(raw)) {
|
|
55
|
+
for (const item of raw) {
|
|
56
|
+
params.append(key, String(item));
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
params.set(key, String(raw));
|
|
61
|
+
}
|
|
62
|
+
return params.toString();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function safeDecode(value, decoder) {
|
|
66
|
+
const input = String(value ?? '');
|
|
67
|
+
try {
|
|
68
|
+
return decoder(input);
|
|
69
|
+
} catch {
|
|
70
|
+
return input;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const builtinTemplateFunctions = {
|
|
75
|
+
encodeURI: (value) => encodeURI(String(value ?? '')),
|
|
76
|
+
encodeURIComponent: (value) => encodeURIComponent(String(value ?? '')),
|
|
77
|
+
decodeURI: (value) => safeDecode(value, decodeURI),
|
|
78
|
+
decodeURIComponent: (value) => safeDecode(value, decodeURIComponent),
|
|
79
|
+
jsonStringify,
|
|
80
|
+
formatDate: formatDateImpl,
|
|
81
|
+
now: (format = 'YYYYMMDDHHmmss', useUtc = true) => formatDateImpl(new Date(), format, useUtc),
|
|
82
|
+
toQueryString,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default builtinTemplateFunctions;
|
package/src/cli/build.js
CHANGED
|
@@ -6,24 +6,31 @@ import { loadSiteConfig } from '../utils/loadSiteConfig.js';
|
|
|
6
6
|
* Build the static site
|
|
7
7
|
* @param {Object} options - Options for building the site
|
|
8
8
|
* @param {string} options.rootDir - Root directory of the site (defaults to cwd)
|
|
9
|
+
* @param {string} options.outputPath - Output directory path (relative to rootDir by default)
|
|
9
10
|
* @param {Object} options.md - Optional markdown renderer
|
|
10
11
|
* @param {boolean} options.quiet - Suppress build output logs
|
|
11
|
-
* @param {boolean} options.isScreenshotMode -
|
|
12
|
+
* @param {boolean} options.isScreenshotMode - Optional build flag exposed to templates via build.isScreenshotMode
|
|
12
13
|
*/
|
|
13
14
|
export const buildSite = async (options = {}) => {
|
|
14
|
-
const {
|
|
15
|
+
const {
|
|
16
|
+
rootDir = process.cwd(),
|
|
17
|
+
outputPath = '_site',
|
|
18
|
+
md,
|
|
19
|
+
functions,
|
|
20
|
+
quiet = false,
|
|
21
|
+
isScreenshotMode = false
|
|
22
|
+
} = options;
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
let config = {};
|
|
18
|
-
if (!md || !functions) {
|
|
19
|
-
config = await loadSiteConfig(rootDir);
|
|
20
|
-
}
|
|
24
|
+
const config = await loadSiteConfig(rootDir);
|
|
21
25
|
|
|
22
26
|
const build = createSiteBuilder({
|
|
23
27
|
fs,
|
|
24
28
|
rootDir,
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
outputPath,
|
|
30
|
+
md,
|
|
31
|
+
markdown: config.markdown || {},
|
|
32
|
+
keepMarkdownFiles: config.build?.keepMarkdownFiles === true,
|
|
33
|
+
functions: functions || {},
|
|
27
34
|
quiet,
|
|
28
35
|
isScreenshotMode
|
|
29
36
|
});
|
package/src/cli/init.js
CHANGED
|
@@ -11,9 +11,9 @@ export function initSite({ projectName, template = 'default' }) {
|
|
|
11
11
|
|
|
12
12
|
// Check if template exists
|
|
13
13
|
if (!existsSync(templatePath)) {
|
|
14
|
-
const available = readdirSync(templatesDir)
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const available = readdirSync(templatesDir, { withFileTypes: true })
|
|
15
|
+
.filter((entry) => entry.isDirectory())
|
|
16
|
+
.map((entry) => entry.name);
|
|
17
17
|
console.error(`Template "${template}" not found.`);
|
|
18
18
|
console.error(`Available templates: ${available.join(', ')}`);
|
|
19
19
|
process.exit(1);
|
|
@@ -33,6 +33,6 @@ export function initSite({ projectName, template = 'default' }) {
|
|
|
33
33
|
console.log('');
|
|
34
34
|
console.log('Next steps:');
|
|
35
35
|
console.log(` cd ${projectName}`);
|
|
36
|
-
console.log('
|
|
37
|
-
console.log('
|
|
36
|
+
console.log(' bunx rtgl sites build');
|
|
37
|
+
console.log(' bunx rtgl sites watch');
|
|
38
38
|
}
|