markupress 0.6.4 → 0.7.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/README.md +23 -462
- package/dist/cli.js +3 -10
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/content.d.ts +25 -0
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +49 -3
- package/dist/content.js.map +1 -1
- package/dist/controllers/search.d.ts +7 -0
- package/dist/controllers/search.d.ts.map +1 -0
- package/dist/controllers/search.js +101 -0
- package/dist/controllers/search.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +2 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +57 -5
- package/dist/markdown.js.map +1 -1
- package/dist/site.d.ts +7 -0
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +55 -6
- package/dist/site.js.map +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +2 -1
- package/dist/styles.js.map +1 -1
- package/dist/versions.d.ts +5 -0
- package/dist/versions.d.ts.map +1 -1
- package/dist/versions.js +42 -2
- package/dist/versions.js.map +1 -1
- package/package.json +11 -8
- package/styles/prose.css +6 -0
- package/theme/chrome.html +5 -3
- package/theme/home.html +4 -3
- package/theme/search.html +20 -0
- package/theme/shell.html +5 -4
- package/theme/theme.css +1 -1
package/README.md
CHANGED
|
@@ -1,485 +1,46 @@
|
|
|
1
1
|
# Markupress
|
|
2
2
|
|
|
3
|
-
Build a versioned Markdown documentation site with native HTML Next components, HTMLKit routing and a Looma theme. Markupress provides `
|
|
3
|
+
Build a versioned Markdown documentation site with native HTML Next components, HTMLKit routing and a Looma theme. Markupress provides `dev`, `build`, `preview` and `snapshot` commands, and `build` writes a portable static site.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Markupress is published on npm as `markupress`. Use Node 22.22.2 or later in Node 22, or Node 24.15 or later in Node 24:
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
npm install markupress
|
|
11
|
-
npx markupress dev # serve docs/ with live reload
|
|
12
|
-
npx markupress build # write the static site to site/
|
|
13
|
-
npx markupress preview # serve the built site
|
|
14
|
-
```
|
|
5
|
+
**Documentation: [threadlabs-studio.github.io/markupress](https://threadlabs-studio.github.io/markupress/)**
|
|
15
6
|
|
|
16
|
-
|
|
7
|
+
- **Markdown:** GitHub Flavored Markdown with footnotes, callouts, details, tabs and code groups, shared with Looma's components and LoomaEditor.
|
|
8
|
+
- **Code blocks:** Shiki highlighting at build time in both themes, titles, line highlights and numbers, copy and wrap.
|
|
9
|
+
- **Components:** native HTML Next components in any page, with TypeScript controllers.
|
|
10
|
+
- **Navigation:** sections and sidebars from folders, a top navigation, breadcrumbs, an "On this page" outline, a mobile drawer, edit links and last-updated dates.
|
|
11
|
+
- **Search:** local full-text search of each version, opened with `/` or Ctrl+K.
|
|
12
|
+
- **Versions:** `markupress snapshot v1` records the docs, and readers switch versions on the same document.
|
|
13
|
+
- **Theme:** Looma's light and dark themes, a home layout, a footer, and site layouts that replace the shell.
|
|
17
14
|
|
|
18
|
-
##
|
|
15
|
+
## Get started
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
Use Node 22.22.2 or later in Node 22, or Node 24.15 or later in Node 24. `npm create markupress` starts a site:
|
|
21
18
|
|
|
22
19
|
```sh
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
A site needs no configuration. `docs/index.md` is a complete site, and `markupress dev` serves it. Files in known places shape the rest:
|
|
27
|
-
|
|
28
|
-
| File | Is |
|
|
29
|
-
| --- | --- |
|
|
30
|
-
| `docs/index.md` | The version home, which every version needs. A number prefix such as `01.index.md` is allowed. |
|
|
31
|
-
| `docs/<folder>/index.md` | The folder's page and its metadata (see [Folders](#navigation-logo-and-footer)). Each top-level folder with an index page is a section: a top navigation link with its own sidebar. |
|
|
32
|
-
| `docs/404.md` | The not-found page. |
|
|
33
|
-
| `docs/_footer.md` | The footer: a heading per column, a list of links under each, and a copyright paragraph. |
|
|
34
|
-
| `public/logo.svg`, `public/logo-dark.svg` | The logo before the title, and its dark-theme version. |
|
|
35
|
-
| `public/favicon.svg` | The site icon. |
|
|
36
|
-
| `app/layouts/default.html` | A layout that replaces the Markupress shell (see [Layouts](#layouts)). |
|
|
37
|
-
| `app/layouts/home.html` | A layout that replaces the home layout (see [Home page](#home-page)). |
|
|
38
|
-
| `htmlkit.config.ts` | HTMLKit options and plugins. |
|
|
39
|
-
| `versions/<id>/` | A recorded version, written by `markupress snapshot` (see [Versions](#versions)). |
|
|
40
|
-
|
|
41
|
-
Pages and their front matter come next, and `markupress.config.json` comes last: plain JSON data for the title, the base path and a few more settings (see [Configuration](#configuration)).
|
|
42
|
-
|
|
43
|
-
The `markupress` command and these APIs also load the site's `htmlkit.config.ts` (or `.js`) with HTMLKit's `loadConfig`, and add `markupress()` after its plugins, so HTMLKit plugins and options compose with Markupress:
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
// htmlkit.config.ts
|
|
47
|
-
import { defineConfig } from '@nextwebwg/htmlkit';
|
|
48
|
-
import { feed } from './plugins/feed.js';
|
|
49
|
-
|
|
50
|
-
export default defineConfig({ plugins: [feed()], redirects: { '/start/': '/guide/install/' } });
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
`base` and `outDir` from `markupress.config.json` or the command line win over the HTMLKit config's; without either, they are `/` and `site`. `markupress()` includes the `markdown()` plugin, so the HTMLKit config lists neither.
|
|
54
|
-
|
|
55
|
-
The same pieces work as HTMLKit plugins. `markupress(options)` adds versioned documentation with this theme. `markdown()` alone makes any HTMLKit site's `.md` files into pages, with no versions or theme:
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
import { defineConfig } from '@nextwebwg/htmlkit';
|
|
59
|
-
import { markdown } from 'markupress';
|
|
60
|
-
|
|
61
|
-
export default defineConfig({ plugins: [markdown()] });
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
```markdown
|
|
65
|
-
---
|
|
66
|
-
title: Installation
|
|
67
|
-
description: Install the project and run your first example.
|
|
68
|
-
id: install
|
|
69
|
-
aliases: [/getting-started/]
|
|
70
|
-
---
|
|
71
|
-
# Installation
|
|
72
|
-
|
|
73
|
-
[Next steps](03.usage.md#usage)
|
|
74
|
-
|
|
75
|
-
<link rel="component" href="../../components/counter.html">
|
|
76
|
-
|
|
77
|
-
<demo-counter></demo-counter>
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
`01.guide/02.install.md` becomes `/v/current/guide/install/`. Numeric prefixes order HTMLKit navigation and disappear from URLs. In the default version it is also `/guide/install/`, the page's canonical copy (see [Versions](#versions)). Changing `02.` to `10.` changes ordering without changing the path. Duplicate paths or document IDs fail with both source filenames.
|
|
81
|
-
|
|
82
|
-
Markdown text and fenced examples are literal, including braces and component syntax. Raw HTML outside fences can declare or use native components. Resource links remain relative to the original Markdown file. A component declared in Markdown is hoisted alongside the generated page carrier; its expressions retain HTML Next semantics. Explicit `<title>`, `<meta>` and non-component `<link>` elements become head declarations in the page component's `<defs>`; an explicit `<title>` takes the place of the generated one. A `<link rel="stylesheet">` becomes an `@import` in the page component's `<style>`, so it styles that page as the page's own CSS. Any other `<link>` that is not a head declaration, such as `rel="pingback"`, fails with HTML Next's `HC025`.
|
|
83
|
-
|
|
84
|
-
### Front matter
|
|
85
|
-
|
|
86
|
-
Front matter is a YAML mapping. Each field becomes HTMLKit page metadata or page data, and an unknown or mistyped field fails the build with the Markdown file's path, the field and the expected type.
|
|
87
|
-
|
|
88
|
-
| Field | Type | Effect |
|
|
89
|
-
| --- | --- | --- |
|
|
90
|
-
| `title` | string | The document `<title>`, the navigation, breadcrumb and pager label (HTMLKit `hk:label`), and `page.title`. Defaults to the first heading, then the file name. On a folder's index page it labels the folder. |
|
|
91
|
-
| `description` | string | `<meta name="description">`, and the page's line in its folder's generated index. |
|
|
92
|
-
| `order` | whole number | The page's place among its folder's pages, in place of its file's number prefix (HTMLKit `hk:order`). On a folder's index page it places the whole folder, and on the version home it is an error. |
|
|
93
|
-
| `navigation` | `hidden` or `collapsed` | `hidden` leaves the page out of navigation and keeps its route (HTMLKit `hk:navigation`). On a folder's index page, `collapsed` starts the folder's sidebar group closed. |
|
|
94
|
-
| `id` | string | The document identity across versions, independent of the file name and route. |
|
|
95
|
-
| `aliases` | list of route paths | Further URLs serving the page, relative to its version (HTMLKit `hk:alias`). |
|
|
96
|
-
| `layout` | string or `false` | An HTMLKit layout, `app/layouts/<name>.html`, in place of the site's layout for this page (HTMLKit `hk:layout`). `home` is Markupress's [home page](#home-page). `false` renders the page with no layout. |
|
|
97
|
-
| `outline` | `false` or `[min, max]` | The heading levels in the page's "On this page" outline, `[2, 3]` by default. `false` gives an empty outline. |
|
|
98
|
-
| `head` | mapping | `meta` (name to content), `og` (Open Graph property without `og:`, to content) and `canonical` (a URL), rendered into the document head. |
|
|
99
|
-
| `editLink` | URL or `false` | The page's "Edit this page" URL, in place of the one the [`editLink` option](#edit-link-and-last-updated) gives. `false` shows none. |
|
|
100
|
-
|
|
101
|
-
```yaml
|
|
102
|
-
---
|
|
103
|
-
title: Installation
|
|
104
|
-
outline: [2, 4]
|
|
105
|
-
editLink: https://github.com/org/repo/edit/main/docs/01.guide/02.install.md
|
|
106
|
-
head:
|
|
107
|
-
meta: { keywords: "install, setup" }
|
|
108
|
-
og: { image: https://example.com/social.png, type: article }
|
|
109
|
-
canonical: https://example.com/guide/install/
|
|
110
|
-
---
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Number prefixes order pages, and `order` takes the place of a file's prefix: `order: 2` on `install.md` orders it as `02.install.md` would. Pages with the same number sort by name.
|
|
114
|
-
|
|
115
|
-
Every heading has an anchor, derived by the Looma Markdown rule: the text is normalized to NFC and lowercased, letters, numbers and combining marks in any script stay, and every other run of characters becomes one hyphen. "Über uns" is `über-uns`, "設定 と 使い方" is `設定-と-使い方`, and a duplicate takes the first free suffix (`installation-1`). `## Setup {#install}` sets the ID to `install` and removes the marker from the heading. A custom ID is reserved for its heading, so a generated ID skips it. Relative `.md` links resolve to the corresponding version-specific document, retaining fragments and queries; a link to a missing `.md` document fails when its page compiles, in `dev` as in `build`, naming the Markdown file. A link starting with `/` is a page path in the page's version, as in the footer and the `nav` option, so `[Tokens](/tokens/)` opens `/v/<id>/tokens/` under the deployment base; with `markdown()` alone it is a path under the base. A root-relative link naming a file, such as `/report.pdf`, is a file in the public directory. Relative assets are copied, and image paths starting with `/` resolve from that edition's public directory under the deployment base. Remote links remain unchanged. `navigation: hidden` hides a document from navigation while keeping its route available. Aliases do not add duplicate sidebar entries.
|
|
116
|
-
|
|
117
|
-
Markupress renders [Looma Markdown](https://github.com/threadlabs-studio/looma/blob/main/apps/docs/docs/looma-markdown.md): CommonMark with GitHub Flavored Markdown (tables, task lists, strikethrough and autolinks), plus Looma's blocks and inline elements. It parses with Looma's markdown-it plugin and is tested against Looma Markdown's conformance fixtures, so a document reads the same in Markupress, Looma's components and LoomaEditor. A container's name is matched without regard to case, and the rest of the opening line is its title:
|
|
118
|
-
|
|
119
|
-
````markdown
|
|
120
|
-
::: warning Before you upgrade
|
|
121
|
-
Back up your data first.
|
|
122
|
-
:::
|
|
123
|
-
|
|
124
|
-
> [!TIP]
|
|
125
|
-
> Alerts take the tones NOTE, TIP, IMPORTANT, WARNING and CAUTION.
|
|
126
|
-
|
|
127
|
-
::: details Show the output {open}
|
|
128
|
-
Starts expanded.
|
|
129
|
-
:::
|
|
130
|
-
|
|
131
|
-
::: code-group
|
|
132
|
-
```sh title="pnpm"
|
|
133
|
-
pnpm add markupress
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
```sh title="npm"
|
|
137
|
-
npm install markupress
|
|
138
|
-
```
|
|
139
|
-
:::
|
|
140
|
-
|
|
141
|
-
::: tabs
|
|
142
|
-
== Examples
|
|
143
|
-
Usage examples.
|
|
144
|
-
|
|
145
|
-
== API
|
|
146
|
-
The reference.
|
|
147
|
-
:::
|
|
148
|
-
|
|
149
|
-
::toc{depth=2}
|
|
150
|
-
|
|
151
|
-
Status :chip[Ready]{tone=green}, owned by :mention[Ada Lovelace]{id=u1}.
|
|
152
|
-
|
|
153
|
-
- [x] Read the changelog
|
|
154
|
-
- [ ] Run the migration
|
|
155
|
-
|
|
156
|
-
::: figure The build, from *source* to release
|
|
157
|
-
{width=640 placement=center}
|
|
158
|
-
:::
|
|
159
|
-
````
|
|
160
|
-
|
|
161
|
-
| Markdown | Renders |
|
|
162
|
-
| --- | --- |
|
|
163
|
-
| `::: note`, `tip`, `info`, `important`, `warning`, `danger` or `caution`, with an optional title | `<ui-callout tone="…" heading="Title">`. `caution` is `danger`, and `note` is the muted aside. |
|
|
164
|
-
| `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` alone on a quote's first line | An untitled `<ui-callout>`: NOTE is `note` and CAUTION is `danger`. |
|
|
165
|
-
| `::: details Title`, with `{open}` to start expanded | Native `<details><summary>Title</summary>`, which opens without JavaScript. The title defaults to "Details". |
|
|
166
|
-
| `::: tabs` with `== Label` panels | `<ui-tabs group="tabs">`, one `<section aria-label="Label">` per panel. |
|
|
167
|
-
| `::: code-group` with a fence per panel | `<ui-tabs group="code-group">`, each panel labelled by its fence's `title`, or its language. `== Label` panels also work. |
|
|
168
|
-
| `::: figure Caption` | `<figure>` with the caption, inline Markdown, as its `<figcaption>`. |
|
|
169
|
-
| `::toc{depth=2 format=numbered}` | A table of contents linking the page's headings by their anchors. `depth` is 1 to 3 (default 3), and `format` is `plain`, `bulleted` or `numbered`. |
|
|
170
|
-
| `:chip[Label]{tone=green}` | An inline chip. Its tone is a hue: `neutral` (the default), `blue`, `green`, `yellow`, `red` or `purple`. |
|
|
171
|
-
| `:mention[Name]{id=u1}` | A mention of the entity `u1`, shown as `@Name`. |
|
|
172
|
-
| `- [x] Done` | A task list, when every item of the list starts with `[ ]` or `[x]`. |
|
|
173
|
-
| `{width=640 height=360 placement=wrap-left}` | An image with its size in CSS pixels and a placement: `center`, `wrap-left` or `wrap-right`. |
|
|
174
|
-
|
|
175
|
-
Tabs in one group keep one selection across the page, matched by label, and remember it for the next visit: choosing "npm" in one code group switches every code group. `::: tabs name` or `::: code-group name` sets the group. Without JavaScript every panel shows. Any other container name stays text. A closing `:::` closes the innermost open container, and a fence's content is code, so a `:::` line inside a fence stays code. Give an outer container more colons (`::::` around `:::`) so the nesting shows in the source. Titles and labels are literal text.
|
|
176
|
-
|
|
177
|
-
A page links the Looma components its blocks use by package name, as the theme links `ui-button`, so `@threadlabs/looma` resolves from the site. The Markupress theme spaces the blocks and styles the Markdown inside them.
|
|
178
|
-
|
|
179
|
-
Fenced code is highlighted at build time with [Shiki](https://shiki.style), so pages load no highlighting JavaScript. Any language Shiki bundles, such as `html`, `css`, `less`, `js`, `ts`, `tsx`, `jsx`, `json`, `bash`, `vue`, `svelte`, `md` or `yaml`, is loaded the first time a fence uses it. Unknown languages stay plain text. A `title` in the fence's info string adds a caption:
|
|
180
|
-
|
|
181
|
-
````markdown
|
|
182
|
-
```ts title="app/pages/index.ts"
|
|
183
|
-
export const count = 1;
|
|
20
|
+
npm create markupress@latest docs
|
|
184
21
|
```
|
|
185
|
-
````
|
|
186
22
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
The fence's info string also highlights and numbers lines, at build time with no client JavaScript:
|
|
190
|
-
|
|
191
|
-
````markdown
|
|
192
|
-
```ts {1,3-4} :line-numbers
|
|
193
|
-
const a = 1;
|
|
194
|
-
const b = 2; // [!code highlight]
|
|
195
|
-
const c = 3;
|
|
196
|
-
const d = 4;
|
|
197
|
-
```
|
|
198
|
-
````
|
|
199
|
-
|
|
200
|
-
- `{1,3-4}` highlights lines 1, 3 and 4, with or without a space after the language (```` ```ts{1} ````).
|
|
201
|
-
- A `// [!code highlight]` comment at the end of a line highlights that line, and the comment is removed. `#`, `--`, `/* */` and `<!-- -->` comments work too. `[!code highlight:3]` covers three lines, and a marker alone on its line applies to the lines after it.
|
|
202
|
-
- `:line-numbers` numbers the lines. The `lineNumbers: true` option, in `markupress.config.json`, `markupress()` or `markdown()`, numbers every block, and `:no-line-numbers` opts a block out.
|
|
203
|
-
- Line highlighting and numbers apply to languages Shiki highlights and to plain text (`text`, `txt`, `plain` or `plaintext`). A fence with an unknown language or no language stays escaped text, without its `[!code highlight]` comments.
|
|
204
|
-
|
|
205
|
-
Every code block has Copy and Wrap buttons. Copy puts the code on the clipboard, without line numbers or highlight comments. Wrap toggles line wrapping. They are native buttons, reached with Tab, and a page with code loads a small controller (`markupress/controllers/code.js`) that handles them through one delegated listener. The buttons stay hidden until that controller runs, so without JavaScript a code block is only its code. The theme shows them while the block is hovered or holds focus, and always on touch screens.
|
|
206
|
-
|
|
207
|
-
Highlighting uses the `github-light-default` and `github-dark-default` themes. Each token's color is `light-dark(light, dark)`, so it follows the page's `color-scheme`. The Markupress theme sets the color scheme from its dark-theme button or the reader's system preference through Looma's `[data-theme]` themes, so code switches with the rest of the page. On another HTMLKit site, set `color-scheme: light dark` to follow the system, or set `color-scheme` on `[data-theme]` as Looma does. Each token also carries `--shiki-light` and `--shiki-dark`, so a site can switch with any selector instead, for example `.dark .shiki span { color: var(--shiki-dark) !important; }`. Browsers without `light-dark()` show unhighlighted text. The site's stylesheet owns the code block's background; the Markupress theme uses Looma's sunken surface (`--ui-surface-sunken`). There, comments measure 3.98:1 in the light theme and 4.42:1 in the dark theme, and light-theme function names 4.42:1, below the 4.5:1 the other token colors reach.
|
|
208
|
-
|
|
209
|
-
## TypeScript controllers
|
|
210
|
-
|
|
211
|
-
Maintain controllers in strict ESM TypeScript, with `.js` references in HTML:
|
|
212
|
-
|
|
213
|
-
```html
|
|
214
|
-
<template component="demo-counter" controller="./counter.js">
|
|
215
|
-
<defs><state name="count" type="number" value="0"></state></defs>
|
|
216
|
-
<div><button>Increment</button><output>{$count}</output></div>
|
|
217
|
-
</template>
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
// counter.ts
|
|
222
|
-
import type { ComponentHost } from '@nextwebwg/html-next/runtime';
|
|
223
|
-
|
|
224
|
-
export default function counter(host: ComponentHost): void {
|
|
225
|
-
host.on('connect', () => {
|
|
226
|
-
const button = host.root.querySelector('button')!;
|
|
227
|
-
const click = () => { host.state.count = Number(host.state.count) + 1; };
|
|
228
|
-
button.addEventListener('click', click);
|
|
229
|
-
return () => button.removeEventListener('click', click);
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
HTMLKit resolves `counter.js` to `counter.ts` in development and bundles its JavaScript for production. Vite transpiles; it does not type-check. Install TypeScript as a development dependency when importing HTML Next's controller types, and run your project's strict TypeScript check separately. Markupress maintains its controllers with TypeScript 7. Checked JSDoc is an authoring option for your own controllers.
|
|
235
|
-
|
|
236
|
-
## Versions
|
|
237
|
-
|
|
238
|
-
Record active docs without overwriting an existing edition:
|
|
23
|
+
Or add Markupress to a project:
|
|
239
24
|
|
|
240
25
|
```sh
|
|
241
|
-
|
|
242
|
-
markupress
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
The command validates a build and writes `versions/v1/`, a copy of the project's layout: `versions/v1/docs/` and `versions/v1/public/`. It stages the copy in a `.`-prefixed folder and renames it into place, so a version appears whole. The folders in `versions/` are the version list: the current version first, then each snapshot, highest number first (`v10`, `v2`, `v1`), labelled by its folder name. It mirrors Markdown, public assets and project-local component, controller, stylesheet and asset dependencies, preserving relative imports. Existing snapshots are never replaced. Keep those files in version control. Package imports continue to use the project's installed dependencies and lockfile; snapshots freeze authored sources rather than vendoring npm packages.
|
|
246
|
-
|
|
247
|
-
Use relative project-local imports for snapshot content. Absolute filesystem imports and remote resources retain their external ownership. If a snapshot process crashes, check that it has stopped before removing its `versions/.snapshot-lock` directory; the next snapshot replaces any `.<id>-pending` folder it left.
|
|
248
|
-
|
|
249
|
-
The `versions` option sets the versions, their order and their labels in place of the folder list:
|
|
250
|
-
|
|
251
|
-
```json
|
|
252
|
-
{
|
|
253
|
-
"versions": [
|
|
254
|
-
{ "id": "current", "label": "Next", "directory": "docs" },
|
|
255
|
-
{ "id": "v1", "label": "Version 1", "directory": "versions/v1/docs", "publicDir": "versions/v1/public" }
|
|
256
|
-
],
|
|
257
|
-
"defaultVersion": "v1"
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
A site with several versions shows their links in the sidebar and the selected version's label beside the title. Version IDs are URL slugs; they need not follow SemVer. A document's `id` identifies it across versions independently of its filename, route and generated component name. The version selector links to that identity in the other version, even if its route changed. If it is absent, the selector explains the fallback and links to that version's home. Selection lives in the URL and survives reloads.
|
|
262
|
-
|
|
263
|
-
The site root serves the default version, and its pages are the canonical copies. The same pages under `/v/<default>/` are HTMLKit aliases: each has a `<link rel="canonical">` to its root copy, unless its front matter sets `head.canonical`, and `sitemap.xml` lists only the root copies. A version whose directory an earlier version already serves is an alias of it too.
|
|
264
|
-
|
|
265
|
-
## Theme and migration inputs
|
|
266
|
-
|
|
267
|
-
The default theme imports Looma's button and badge resources and CSS exports selectively. Its shell has a skip link, a top navigation bar, a grouped sidebar per section, native version links, an "on this page" outline, an edit link and last-updated date, a footer and a saved light/dark preference, which an inline head script applies before first paint. It uses the platform's existing runtime.
|
|
268
|
-
|
|
269
|
-
Styles follow the component boundaries. Looma's tokens, its light and dark themes and the document defaults (`styles/base.css`) are page-wide, delivered through HTMLKit's `css` option. Each Markdown page component imports the documentation typography (`styles/prose.css`), so it styles that page and stops at any component the page embeds. The shell's own `<style>` styles the header, sidebar, columns and footer. `theme/theme.css` is page-wide too, because HTMLKit's built-in breadcrumbs and pager have no styles of their own and no component's scoped style reaches them; it also picks the logo for the theme in use, and draws an accent bar along the top of the window while HTMLKit renders a page in place, when `<html>` has `data-hk-navigating`. With `markdown()` alone, `markdown({ styles: [file] })` makes every Markdown page component import the given stylesheet files.
|
|
270
|
-
|
|
271
|
-
### Navigation, logo and footer
|
|
272
|
-
|
|
273
|
-
The shell around the pages comes from the content. **Top navigation.** Each top-level folder with an index page is a section, linked from the top navigation by its index page's `title`, in navigation order. The `nav` option adds links after the sections. A link to the page being read, or to a section holding it, is marked current. **Logo and icon.** `public/logo.svg` is the logo, `public/logo-dark.svg` replaces it in the dark theme, and `public/favicon.svg` is the site icon. **Badge.** A site with several versions shows the selected version's label beside the title. **Footer.** `_footer.md` in a version's content directory is that version's footer. HTMLKit routes no file whose name starts with `_`, so the footer has no URL of its own:
|
|
274
|
-
|
|
275
|
-
```markdown
|
|
276
|
-
## Docs
|
|
277
|
-
|
|
278
|
-
- [Tokens](/tokens/)
|
|
279
|
-
- [Proposal](https://nextwebwg.org/declarative-components/)
|
|
280
|
-
|
|
281
|
-
## Project
|
|
282
|
-
|
|
283
|
-
- [GitHub](https://github.com/threadlabs-studio/looma)
|
|
284
|
-
|
|
285
|
-
Copyright 2026 Threadlabs Studio contributors.
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
Each heading starts a column, and each item in the list below it is a link. A paragraph after the lists is the copyright line. Anything else in the file fails the build with the file's path.
|
|
289
|
-
|
|
290
|
-
In the footer and the `nav` option, an `href` starting with `/` is a page path in the version being read, so `/components/` opens that version's components. Other links stay as written.
|
|
291
|
-
|
|
292
|
-
**Sidebars.** Each top-level folder of a version is a section with its own sidebar, generated from the folder's pages; a page at the version's root shows every page. Folders, `order` and `navigation` front matter shape every sidebar.
|
|
293
|
-
|
|
294
|
-
**Folders.** A folder is a page: its index page (`index.md`, or `01.index.md` with a number prefix) is the folder's entry, at the folder's URL, and its front matter is the folder's metadata. A generated sidebar is HTMLKit's navigation tree, rendered by `<hk-nav>`: each folder below the section is a labelled group, a native `<details>` that collapses without JavaScript, nested as deep as the folders go, and its summary links to the index page.
|
|
295
|
-
|
|
296
|
-
```markdown
|
|
297
|
-
---
|
|
298
|
-
title: Forms # labels the folder in the sidebar, breadcrumbs and pager
|
|
299
|
-
description: Form controls.
|
|
300
|
-
order: 3 # places the folder among its siblings like a 03. folder prefix
|
|
301
|
-
navigation: collapsed # starts the group closed, except on the pages inside it
|
|
302
|
-
---
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
A group starts open unless its index page says `navigation: collapsed`, and a group holding the current page always starts open. A folder without an index page is labelled by its name. To group pages under a label with no folder URL of its own, use an HTMLKit route group: a folder named in parentheses, such as `03.(Getting started)/`, labels and orders its pages and adds no URL segment. Navigation, breadcrumbs and the previous/next links follow the same order.
|
|
306
|
-
|
|
307
|
-
**Generated index.** An index page with front matter and an empty body lists its folder: below its `title` as the heading, each page and folder directly inside it links to that page with its `description`, in navigation order. A route group's pages appear in its place, and hidden pages stay out. The list comes from HTMLKit's navigation tree, so it follows every page added, renamed or reordered.
|
|
308
|
-
|
|
309
|
-
**Outline.** A page with headings in its `outline` range (h2 and h3 by default) shows them in an "On this page" outline beside the content on wide screens. The links are plain fragment links. A small controller (`markupress/controllers/outline.js`, also exported) marks the heading being read with `aria-current="location"`, using an IntersectionObserver; without JavaScript only that marking is missing.
|
|
310
|
-
|
|
311
|
-
**Narrow screens.** Below 769px the sidebar is a drawer: a native popover, closed until the Navigation button opens it, and it opens without JavaScript. Escape or a tap outside closes it, and following a link in it closes it.
|
|
312
|
-
|
|
313
|
-
**Not found.** A `404.md` in the default version's content directory is the site's not-found page (HTMLKit's not-found page, R7). It renders in the shell for every unmatched path, including those under `/v/<id>/`, with status 404 in `dev` and `preview`, and `build` writes it to `404.html`. It is no page of its version, so it has no URL of its own and no sidebar entry.
|
|
314
|
-
|
|
315
|
-
### Home page
|
|
316
|
-
|
|
317
|
-
`layout: home` makes a page a home page: a hero and feature cards, full width between the shell's top navigation and footer. The page's Markdown is the data, so it reads the same anywhere Markdown renders:
|
|
318
|
-
|
|
319
|
-
```markdown
|
|
320
|
-
---
|
|
321
|
-
layout: home
|
|
322
|
-
---
|
|
323
|
-
# Markupress
|
|
324
|
-
|
|
325
|
-
Versioned documentation from Markdown, with native HTML Next components.
|
|
326
|
-
|
|
327
|
-

|
|
328
|
-
|
|
329
|
-
[Get started](01.guide/01.install.md) [GitHub](https://github.com/threadlabs-studio/markupress)
|
|
330
|
-
|
|
331
|
-
## Components in Markdown
|
|
332
|
-
|
|
333
|
-
Declare an HTML Next component in a page and use it on the next line.
|
|
334
|
-
|
|
335
|
-
## Versions
|
|
336
|
-
|
|
337
|
-
`markupress snapshot v1` records the docs as they stand.
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
| Markdown | Is |
|
|
341
|
-
| --- | --- |
|
|
342
|
-
| Everything before the first `##` heading | The hero. |
|
|
343
|
-
| The `#` heading | The hero's title. Without one, the page's `title` is. |
|
|
344
|
-
| A paragraph of only links | The actions: a `ui-button` link each, the first solid and the rest outlined. |
|
|
345
|
-
| An image alone in its paragraph | The hero image, beside the text on wide screens. |
|
|
346
|
-
| Any other paragraph | The tagline. |
|
|
347
|
-
| Each `##` heading and everything up to the next | A feature card, a Looma `ui-card` in a `ui-grid`. A card holds any Markdown: lists, code, links, images. |
|
|
348
|
-
|
|
349
|
-
The hero and cards use Looma's components and `--ui-*` tokens, in both themes. `app/layouts/home.html` replaces the home layout as an ordinary HTMLKit layout, with its own `home.server.ts` loader when it needs data. The page keeps its hero and card structure, so the site's layout supplies only the frame and styles.
|
|
350
|
-
|
|
351
|
-
### Edit link and last updated
|
|
352
|
-
|
|
353
|
-
Each page in the shell ends with an "Edit this page" link and the date its file last changed, above the previous/next links.
|
|
354
|
-
|
|
355
|
-
**Edit link.** The `editLink` option is a URL ending in `/`, and each page's link is that URL followed by the page file's path in the project. For a site at `examples/site/` in a GitHub repository:
|
|
356
|
-
|
|
357
|
-
```json
|
|
358
|
-
{ "editLink": "https://github.com/threadlabs-studio/markupress/edit/main/examples/site/" }
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
`docs/01.guide/01.authoring.md` links to `…/edit/main/examples/site/docs/01.guide/01.authoring.md`, and a snapshot's page to its file in `versions/<id>/`. A page's own `editLink` front matter takes its place, and `editLink: false` shows none.
|
|
362
|
-
|
|
363
|
-
**Last updated.** A page's date is the author date of the last commit that touched its file, as `YYYY-MM-DD` in a `<time>` element. It needs no configuration: one `git log` call reads the dates of every page when a build or development server starts. Outside a git checkout, or for a file with no commit yet, the page shows no date. A shallow clone dates every file by its oldest fetched commit, so a CI build that shows dates fetches the full history (`fetch-depth: 0` in `actions/checkout`).
|
|
364
|
-
|
|
365
|
-
### Layouts
|
|
366
|
-
|
|
367
|
-
`app/layouts/default.html` replaces the Markupress shell with the site's own HTML Next layout for every documentation page, as it is the default layout of the site's other HTMLKit pages. The layout renders `<slot name="page">` where the page goes, and receives Markupress's loader data as props. It receives each prop it declares with `<prop>`, so it declares only the data it uses:
|
|
368
|
-
|
|
369
|
-
```html
|
|
370
|
-
<template component="site-layout"><defs>
|
|
371
|
-
<prop name="navigation" type="list(object({ href: string, label: string, current: string, ... }))" required>Links</prop>
|
|
372
|
-
<prop name="outline" type="list(object({ href: string, label: string, depth: number, ... }))" required>On this page</prop>
|
|
373
|
-
<prop name="page" type="object({ title: string, editLink?: string, ... })" required>Page metadata</prop></defs>
|
|
374
|
-
<div class="site">
|
|
375
|
-
<nav><a $each="item of $navigation" from:href="$item.href" from:aria-current="$item.current">{$item.label}</a></nav>
|
|
376
|
-
<main><slot name="page"></slot></main>
|
|
377
|
-
<aside><a $each="heading of $outline" from:href="$heading.href">{$heading.label}</a></aside>
|
|
378
|
-
</div>
|
|
379
|
-
</template>
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
| Prop | Value |
|
|
383
|
-
| --- | --- |
|
|
384
|
-
| `title` | The site title (`title` option, default "Documentation"). |
|
|
385
|
-
| `home` | The site's base URL. |
|
|
386
|
-
| `navigation` | HTMLKit navigation items for the selected version: `href`, `label`, `current` (`"page"` or `"false"`), `depth` and `pageName`. |
|
|
387
|
-
| `sidebar` | The page's section sidebar, in `<hk-nav>`'s tree shape: entries with `label`, `current`, `open`, `depth` and `children`, and an `href` on a page. A folder with an index page has both `href` and `children`. Pass it to `<hk-nav from:items="$sidebar">`. |
|
|
388
|
-
| `nav` | The sections, then the `nav` option's links: each has `label`, `href`, `current` and `icon` (a URL, or `""`). |
|
|
389
|
-
| `footer` | `_footer.md`'s `columns`, each a `title` and `items` of links with `label`, `href` and `current`, and its `copyright`. |
|
|
390
|
-
| `logo`, `favicon`, `badge` | The logo's `light` and `dark` URLs, the icon URL (both `null` when absent), and the badge text. |
|
|
391
|
-
| `crumbs` | Navigation items from the version home down to this page. |
|
|
392
|
-
| `previous`, `next` | The neighbouring navigation items, absent at either end. |
|
|
393
|
-
| `versions` | Version links: `href`, `label`, `current` and `note`, which explains a fallback to that version's home. |
|
|
394
|
-
| `outline` | The page's headings in its `outline` range: `id`, `href` (`#id`), `label` and `depth`. |
|
|
395
|
-
| `contents` | A generated index's entries, `label`, `href` and `description`, and an empty list on every other page. |
|
|
396
|
-
| `page` | The page's front matter, plus its resolved `title` and document `id`, its `editLink` (absent when it has none), and `lastUpdated`, the `YYYY-MM-DD` date its file last changed in git (absent outside git). |
|
|
397
|
-
|
|
398
|
-
A layout declares its head content in its `<defs>`, as HTMLKit pages and layouts do: `<title>`, `<meta>` and `<link>`, bound as `<title>{$title}</title>`, `from:content` and `from:href`. A page's title, meta names and canonical link win over its layout's. A `<title>`, `<meta>` or `<link>` outside `<defs>` fails the build. The Markupress shell and home layout declare `<title>{$title}</title>` and `<link rel="icon" from:href="$favicon">`, which a null favicon leaves out, so a replacement layout declares the icon it wants.
|
|
399
|
-
|
|
400
|
-
Markupress gives the layout its loader data, so the layout has no `default.server.ts` of its own. A folder's `_layout.html` inside a version's content wraps that folder's pages inside the site layout, as in any HTMLKit site. Its `_layout.server.ts` loader reads all of the data above from `parent.markupress`:
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
export function load({ parent }) { return { props: { section: parent.markupress.page.title } }; }
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
A layout receives exactly the props its component declares, which HTMLKit gives the loader as `propNames`. Declare navigation items with an open object type, which ends in `...`, so items keep passing as HTMLKit adds fields such as a route group's `group`.
|
|
407
|
-
|
|
408
|
-
HTMLKit plugins compose with Markupress through the site's `htmlkit.config.ts`, which the `markupress` command loads (see [Author a site](#author-a-site)).
|
|
409
|
-
|
|
410
|
-
Existing Markdown, HTML/CSS/Less/JavaScript/TypeScript examples, local assets, heading anchors and route aliases are migration inputs. The installed consumer fixture checks those under a nested base. A live Jess/Less documentation migration and visual/content parity comparison remain separate work.
|
|
411
|
-
|
|
412
|
-
## Configuration
|
|
413
|
-
|
|
414
|
-
`markupress.config.json` is plain JSON data, read by the `markupress` command. Every key is optional:
|
|
415
|
-
|
|
416
|
-
```json
|
|
417
|
-
{
|
|
418
|
-
"title": "Looma",
|
|
419
|
-
"base": "/looma/",
|
|
420
|
-
"nav": [
|
|
421
|
-
{ "label": "HTML Next", "href": "https://nextwebwg.org/declarative-components/" },
|
|
422
|
-
{ "label": "GitHub", "href": "https://github.com/threadlabs-studio/looma", "icon": "/github.svg" }
|
|
423
|
-
]
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
| Option | Default | Does |
|
|
428
|
-
| --- | --- | --- |
|
|
429
|
-
| `title` | `"Documentation"` | The site title in the top bar. |
|
|
430
|
-
| `base` | `/` | The URL path the site is served under. It starts and ends with `/`. |
|
|
431
|
-
| `origin` | none | The site's origin, such as `"https://example.com"`. With it, `build` writes `sitemap.xml` at the base, listing every page. |
|
|
432
|
-
| `robots` | none | `build` writes `robots.txt`: `true` allows every crawler, and a string is the file's text. With `origin` set, the file ends with a `Sitemap` line. |
|
|
433
|
-
| `contentDir` | `docs` | The current version's content directory. |
|
|
434
|
-
| `outDir` | `site` | Where `build` writes the static site. |
|
|
435
|
-
| `versions`, `defaultVersion` | the folders in `versions/`, `current` | The versions and their order and labels, and the version the site root serves (see [Versions](#versions)). |
|
|
436
|
-
| `lineNumbers` | `false` | Numbers the lines of every code block. |
|
|
437
|
-
| `nav` | none | Links after the sections in the top navigation: `{ "label", "href" }`, with an optional `icon` image shown in place of the label. |
|
|
438
|
-
| `logo` | `public/logo.svg` and `public/logo-dark.svg` | One image path, or `{ "light", "dark" }`, in place of the logo files. |
|
|
439
|
-
| `favicon` | `public/favicon.svg` | The site icon's path, in place of the icon file. |
|
|
440
|
-
| `editLink` | none | A URL ending in `/`, which each page's file path follows to make its "Edit this page" link (see [Edit link and last updated](#edit-link-and-last-updated)). |
|
|
441
|
-
|
|
442
|
-
Image paths starting with `/` are files in `public/`. `base`, `origin`, `robots` and `outDir` set the HTMLKit options of the same names, in place of the HTMLKit config's. An unknown key fails with the list of options.
|
|
443
|
-
|
|
444
|
-
`build` checks every link in the pages it writes. A link within the site to a page or file missing from the output, or to a `#fragment` naming no heading or element in its page, fails the build with each page and link. A `nav` or footer link starting with `/` is checked in every version, so the page it names must be in each one. A site whose links are not ready yet builds with HTMLKit's `linkCheck` option set to `"warn"` in its `htmlkit.config.ts`, which reports the same links as a warning:
|
|
445
|
-
|
|
446
|
-
```ts
|
|
447
|
-
// htmlkit.config.ts
|
|
448
|
-
import { defineConfig } from '@nextwebwg/htmlkit';
|
|
449
|
-
|
|
450
|
-
export default defineConfig({ linkCheck: 'warn' });
|
|
451
|
-
``` The programmatic `buildSite`, `devSite` and `previewSite` APIs, and the `markupress()` plugin, take the same options plus `root`, `host` and `port`; `siteOptions` returns the HTMLKit options they use.
|
|
452
|
-
|
|
453
|
-
### Content Security Policy
|
|
454
|
-
|
|
455
|
-
HTMLKit gives every page a Content Security Policy it generates from what the site declares: `script-src` with `'self'` for HTMLKit's modules and the controllers, the origin of each declared script, and a hash of each inline head script, plus `object-src 'none'` and `base-uri 'none'`. Built pages carry it as a `<meta http-equiv="Content-Security-Policy">`, and `dev` and `preview` also send it as a header. The Markupress theme's script, which applies the saved theme before first paint, is a head script, so its hash is in the policy. The shell, code and outline controllers are modules from the site, so they run under it unchanged.
|
|
456
|
-
|
|
457
|
-
A script the site adds from another origin, such as analytics, needs that origin. Load it from `app/head.js`, which HTMLKit inlines and hashes too, and add its origin with HTMLKit's `csp` option in `htmlkit.config.ts`. A directive such as `connect-src`, which the generated policy leaves open, needs no entry:
|
|
458
|
-
|
|
459
|
-
```js
|
|
460
|
-
// app/head.js
|
|
461
|
-
document.head.append(Object.assign(document.createElement('script'), { src: 'https://analytics.example/script.js', defer: true }));
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
```ts
|
|
465
|
-
// htmlkit.config.ts
|
|
466
|
-
import { defineConfig } from '@nextwebwg/htmlkit';
|
|
467
|
-
|
|
468
|
-
export default defineConfig({ csp: { 'script-src': ['https://analytics.example'] } });
|
|
26
|
+
npm install markupress
|
|
27
|
+
npx markupress dev # serve docs/ with live reload
|
|
28
|
+
npx markupress build # write the static site to site/
|
|
29
|
+
npx markupress preview # serve the built site
|
|
469
30
|
```
|
|
470
31
|
|
|
471
|
-
|
|
32
|
+
`docs/index.md` is a complete site. The [guide](https://threadlabs-studio.github.io/markupress/guide/getting-started/) covers project structure, writing, navigation, versions, theming and SEO, and the [reference](https://threadlabs-studio.github.io/markupress/reference/) covers configuration, the command line and the API.
|
|
472
33
|
|
|
473
|
-
##
|
|
34
|
+
## Build from source
|
|
474
35
|
|
|
475
|
-
Build and try the source with pnpm through Corepack. The
|
|
36
|
+
Build and try the source with pnpm through Corepack. The documentation site is in `website/`, built by Markupress itself, and the example site in `examples/site/` serves at `/manual/`:
|
|
476
37
|
|
|
477
38
|
```sh
|
|
478
39
|
corepack pnpm install --frozen-lockfile
|
|
479
40
|
corepack pnpm build
|
|
41
|
+
node dist/cli.js dev --root website
|
|
480
42
|
node dist/cli.js dev --root examples/site
|
|
481
|
-
|
|
482
|
-
node dist/cli.js preview --root examples/site
|
|
43
|
+
corepack pnpm build:sites
|
|
483
44
|
```
|
|
484
45
|
|
|
485
46
|
```sh
|
|
@@ -489,7 +50,7 @@ corepack pnpm exec playwright install chromium
|
|
|
489
50
|
MARKUPRESS_BROWSER_TEST=1 corepack pnpm exec vitest run tests/package.test.ts --maxWorkers=1
|
|
490
51
|
```
|
|
491
52
|
|
|
492
|
-
The package contract packs and installs the actual package, runs its CLI, checks frozen imports and checks controller types. The browser proof exercises production and development controllers under HTMLKit's generated Content Security Policy, failing on any violation, keyboard version selection, reloads, mobile navigation, the home page and an axe accessibility audit. CI runs that browser proof separately.
|
|
53
|
+
The package contract packs and installs the actual package, runs its CLI, checks frozen imports and checks controller types. The browser proof exercises production and development controllers under HTMLKit's generated Content Security Policy, failing on any violation, keyboard version selection, reloads, mobile navigation, the home page and an axe accessibility audit. CI runs that browser proof separately, and builds both sites. A push to `main` deploys the documentation site to GitHub Pages.
|
|
493
54
|
|
|
494
55
|
Foundation pins TypeScript, Vitest, Oxlint and pnpm. [AGENTS.md](AGENTS.md) is the contributor contract; [threadlabs.config.json](threadlabs.config.json) records ownership. The [implementation plan](docs/plans/2026-10-04-1830-feat-markupress-platform-plan.md) defines product boundaries. Publishing uses the protected GitHub release workflow and npm environment.
|
|
495
56
|
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
2
|
import { resolve } from 'node:path';
|
|
4
3
|
import { parseArgs } from 'node:util';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
5
|
import { buildSite, devSite, previewSite } from './site.js';
|
|
6
6
|
import { snapshotVersion } from './versions.js';
|
|
7
7
|
async function main() {
|
|
@@ -9,18 +9,11 @@ async function main() {
|
|
|
9
9
|
root: { type: 'string' }, base: { type: 'string' }, port: { type: 'string' }, help: { type: 'boolean', short: 'h' },
|
|
10
10
|
} });
|
|
11
11
|
if (values.help) {
|
|
12
|
-
process.stdout.write('Usage: markupress build|dev|preview [--root directory] [--base /docs/] [--port 3000]\n markupress snapshot <version-id> [--root directory]\nConfiguration: markupress.config.json, and htmlkit.config.ts for HTMLKit options and plugins\n');
|
|
12
|
+
process.stdout.write('Usage: markupress build|dev|preview [--root directory] [--base /docs/] [--port 3000]\n markupress snapshot <version-id> [--root directory]\nConfiguration: markupress.config.ts, .js or .json, and htmlkit.config.ts for HTMLKit options and plugins\n');
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
const root = resolve(values.root ?? process.cwd());
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
config = JSON.parse(await readFile(resolve(root, 'markupress.config.json'), 'utf8'));
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
if (error.code !== 'ENOENT')
|
|
22
|
-
throw error;
|
|
23
|
-
}
|
|
16
|
+
const config = await loadConfig(root);
|
|
24
17
|
const options = { ...config, root, ...(values.base === undefined ? {} : { base: values.base }),
|
|
25
18
|
...(values.port === undefined ? {} : { port: Number(values.port) }) };
|
|
26
19
|
const command = positionals[0] ?? 'dev';
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MarkupressOptions } from './site.js';
|
|
2
|
+
/** Types a `markupress.config.ts`: it returns its options unchanged. */
|
|
3
|
+
export declare function defineConfig(options: MarkupressOptions): MarkupressOptions;
|
|
4
|
+
/**
|
|
5
|
+
* The site's configuration: `markupress.config.ts`, `.js` or `.json` in its root, or none. TypeScript and JavaScript load
|
|
6
|
+
* through Vite, as HTMLKit loads `htmlkit.config.ts`, so a config imports its own files by their `.js` names.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadConfig(root?: string): Promise<MarkupressOptions>;
|
|
9
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAAoB;AAG/F;;;GAGG;AACH,wBAAsB,UAAU,CAAC,IAAI,SAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAejF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
/** Types a `markupress.config.ts`: it returns its options unchanged. */
|
|
5
|
+
export function defineConfig(options) { return options; }
|
|
6
|
+
const names = ['markupress.config.ts', 'markupress.config.js', 'markupress.config.json'];
|
|
7
|
+
/**
|
|
8
|
+
* The site's configuration: `markupress.config.ts`, `.js` or `.json` in its root, or none. TypeScript and JavaScript load
|
|
9
|
+
* through Vite, as HTMLKit loads `htmlkit.config.ts`, so a config imports its own files by their `.js` names.
|
|
10
|
+
*/
|
|
11
|
+
export async function loadConfig(root = process.cwd()) {
|
|
12
|
+
const files = names.map(name => resolve(root, name)).filter(existsSync);
|
|
13
|
+
if (files.length > 1)
|
|
14
|
+
throw new Error(`Choose one of ${names.join(', ')} in ${resolve(root)}.`);
|
|
15
|
+
const file = files[0];
|
|
16
|
+
if (file === undefined)
|
|
17
|
+
return {};
|
|
18
|
+
let options;
|
|
19
|
+
if (file.endsWith('.json'))
|
|
20
|
+
options = JSON.parse(await readFile(file, 'utf8'));
|
|
21
|
+
else {
|
|
22
|
+
const { createServer } = await import('vite');
|
|
23
|
+
const loader = await createServer({ root: resolve(root), configFile: false, appType: 'custom', logLevel: 'silent',
|
|
24
|
+
server: { middlewareMode: true, watch: null, hmr: false }, optimizeDeps: { noDiscovery: true, include: [] } });
|
|
25
|
+
try {
|
|
26
|
+
options = (await loader.ssrLoadModule(file)).default;
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
await loader.close();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options))
|
|
33
|
+
throw new Error(`${file}: the configuration is an object of Markupress options.`);
|
|
34
|
+
return options;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=config.js.map
|