upcontent 0.1.0 → 0.1.2
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/.github/workflows/ci.yml +3 -3
- package/.github/workflows/publish.yml +75 -0
- package/.upcontent/config.json +6 -1
- package/CONTEXT.md +25 -0
- package/Makefile +6 -2
- package/README.md +57 -39
- package/assets/readme/portal-home.png +0 -0
- package/assets/readme/portal-showcase.png +0 -0
- package/astro.config.mjs +67 -18
- package/customization/config-json.md +9 -1
- package/customization/content.md +14 -0
- package/customization/navigation.md +2 -0
- package/deployment/index.md +1 -0
- package/deployment/npm.md +39 -0
- package/guides/validate-your-site.md +5 -0
- package/index.md +19 -0
- package/package.json +15 -9
- package/scripts/upcontent-cli.mjs +2 -4
- package/scripts/verify-external-build.mjs +9 -1
- package/scripts/verify-golden-build.mjs +13 -0
- package/src/components/MermaidLoader.astro +17 -3
- package/src/content/i18n/en.json +1 -0
- package/src/content.config.test.ts +52 -1
- package/src/content.config.ts +29 -4
- package/src/lib/content-blocklist.ts +2 -2
- package/src/lib/doc-links.test.ts +21 -1
- package/src/lib/doc-links.ts +9 -4
- package/src/lib/homepage.ts +16 -0
- package/src/lib/markdown.ts +6 -0
- package/src/lib/portal-config.test.ts +11 -1
- package/src/lib/portal-config.ts +11 -0
- package/src/lib/portal-routes.test.ts +73 -0
- package/src/lib/portal-routes.ts +65 -0
- package/src/lib/remark-doc-links.test.ts +50 -0
- package/src/lib/remark-doc-links.ts +21 -0
- package/src/lib/remark-wiki-links.test.ts +10 -8
- package/src/lib/remark-wiki-links.ts +17 -8
- package/src/lib/seo-sitemap.test.ts +46 -0
- package/src/lib/seo-sitemap.ts +55 -0
- package/src/lib/sidebar.test.ts +32 -3
- package/src/lib/sidebar.ts +26 -17
- package/src/overrides/Footer.astro +3 -5
- package/src/overrides/Head.astro +86 -3
- package/src/pages/robots.txt.ts +46 -0
- package/src/upcontent-cli.test.ts +36 -0
- package/test-fixtures/external-consumer/.upcontent/config.json +4 -0
- package/test-fixtures/external-consumer/noindex.md +7 -0
- package/test-fixtures/external-consumer/public.md +6 -0
- package/src/pages/index.astro +0 -20
package/.github/workflows/ci.yml
CHANGED
|
@@ -40,10 +40,10 @@ jobs:
|
|
|
40
40
|
- name: Run Astro diagnostics
|
|
41
41
|
run: pnpm check
|
|
42
42
|
|
|
43
|
-
- name: Build golden consumer
|
|
44
|
-
run: make build CONTENT_PATH=.
|
|
45
|
-
|
|
46
43
|
- name: Check golden artifact
|
|
44
|
+
run: make check-golden
|
|
45
|
+
|
|
46
|
+
- name: Check golden runtime assets
|
|
47
47
|
run: |
|
|
48
48
|
test -f dist/index.html
|
|
49
49
|
test -f dist/pagefind/pagefind.js
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Publish npm package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*.*.*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Check out repository
|
|
17
|
+
uses: actions/checkout@v5
|
|
18
|
+
|
|
19
|
+
- name: Set up pnpm
|
|
20
|
+
uses: pnpm/action-setup@v4
|
|
21
|
+
with:
|
|
22
|
+
version: 10.20.0
|
|
23
|
+
|
|
24
|
+
- name: Set up Node.js
|
|
25
|
+
uses: actions/setup-node@v5
|
|
26
|
+
with:
|
|
27
|
+
node-version: 24
|
|
28
|
+
registry-url: https://registry.npmjs.org
|
|
29
|
+
package-manager-cache: false
|
|
30
|
+
|
|
31
|
+
- name: Verify npm version
|
|
32
|
+
run: npm --version
|
|
33
|
+
|
|
34
|
+
- name: Require npm Trusted Publishing support
|
|
35
|
+
run: |
|
|
36
|
+
node -e "const [major, minor, patch] = process.argv[1].split('.').map(Number); if (major < 11 || (major === 11 && (minor < 5 || (minor === 5 && patch < 1)))) process.exit(1)" "$(npm --version)"
|
|
37
|
+
|
|
38
|
+
- name: Verify tag matches package version
|
|
39
|
+
env:
|
|
40
|
+
TAG_VERSION: ${{ github.ref_name }}
|
|
41
|
+
run: |
|
|
42
|
+
test "${TAG_VERSION#v}" = "$(node -p "require('./package.json').version")"
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
run: pnpm install --frozen-lockfile
|
|
46
|
+
|
|
47
|
+
- name: Run tests
|
|
48
|
+
run: pnpm test
|
|
49
|
+
|
|
50
|
+
- name: Run Astro diagnostics
|
|
51
|
+
run: pnpm check
|
|
52
|
+
|
|
53
|
+
- name: Validate golden consumer
|
|
54
|
+
run: make check-golden
|
|
55
|
+
|
|
56
|
+
- name: Validate external consumer
|
|
57
|
+
run: make check-external
|
|
58
|
+
|
|
59
|
+
- name: Inspect package contents
|
|
60
|
+
run: npm pack --dry-run
|
|
61
|
+
|
|
62
|
+
- name: Check diff hygiene
|
|
63
|
+
run: |
|
|
64
|
+
git diff --check
|
|
65
|
+
git diff --exit-code
|
|
66
|
+
test -z "$(git status --porcelain --untracked-files=all)"
|
|
67
|
+
|
|
68
|
+
- name: Check release artifacts
|
|
69
|
+
run: |
|
|
70
|
+
test -f dist/index.html
|
|
71
|
+
test -f dist/pagefind/pagefind.js
|
|
72
|
+
test -f dist/upcontent-assets/favicon.svg
|
|
73
|
+
|
|
74
|
+
- name: Publish package
|
|
75
|
+
run: npm publish --access public
|
package/.upcontent/config.json
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
},
|
|
11
11
|
"favicon": ".upcontent/favicon.svg"
|
|
12
12
|
},
|
|
13
|
+
"seo": {
|
|
14
|
+
"enabled": true
|
|
15
|
+
},
|
|
13
16
|
"repo": {
|
|
14
17
|
"url": "https://github.com/lumamontes/upcontent"
|
|
15
18
|
},
|
|
@@ -38,6 +41,7 @@
|
|
|
38
41
|
},
|
|
39
42
|
"navigation": {
|
|
40
43
|
"roots": [
|
|
44
|
+
"index.md",
|
|
41
45
|
"README.md",
|
|
42
46
|
"getting-started",
|
|
43
47
|
"guides",
|
|
@@ -73,7 +77,8 @@
|
|
|
73
77
|
]
|
|
74
78
|
},
|
|
75
79
|
"labelOverrides": {
|
|
76
|
-
"docs": "Project Docs"
|
|
80
|
+
"docs": "Project Docs",
|
|
81
|
+
"readme": "Repository README"
|
|
77
82
|
}
|
|
78
83
|
},
|
|
79
84
|
"content": {
|
package/CONTEXT.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Upcontent Context
|
|
2
|
+
|
|
3
|
+
Upcontent renders documentation repositories into static portals. This context defines the publication and discoverability terms used by the renderer and its consumer repositories.
|
|
4
|
+
|
|
5
|
+
## Publication And Discoverability
|
|
6
|
+
|
|
7
|
+
**Private source**:
|
|
8
|
+
The Git repository containing the documentation is access-controlled. This says nothing about the visibility of the generated portal.
|
|
9
|
+
_Avoid_: private portal
|
|
10
|
+
|
|
11
|
+
**Public portal**:
|
|
12
|
+
A generated portal that is publicly accessible and has explicitly enabled SEO discoverability.
|
|
13
|
+
_Avoid_: public repository
|
|
14
|
+
|
|
15
|
+
**Unlisted portal**:
|
|
16
|
+
A publicly accessible portal that asks crawlers not to index its pages. It is not access control and must not be described as private.
|
|
17
|
+
_Avoid_: private portal
|
|
18
|
+
|
|
19
|
+
**Private portal**:
|
|
20
|
+
A generated portal protected by the hosting layer so unauthenticated visitors and crawlers cannot access its content.
|
|
21
|
+
_Avoid_: hidden portal, noindex portal
|
|
22
|
+
|
|
23
|
+
**SEO policy**:
|
|
24
|
+
The explicit site-level choice to enable or disable search-engine discoverability for a portal. SEO is opt-in; page-level `noindex` can narrow an enabled policy to individual pages.
|
|
25
|
+
_Avoid_: SEO config, SEO mode
|
package/Makefile
CHANGED
|
@@ -3,7 +3,7 @@ REPO_URL ?=
|
|
|
3
3
|
BASE_PATH ?=
|
|
4
4
|
SITE_URL ?=
|
|
5
5
|
|
|
6
|
-
.PHONY: dev build preview check-external
|
|
6
|
+
.PHONY: dev build preview check-golden check-external
|
|
7
7
|
|
|
8
8
|
define prepare-content
|
|
9
9
|
@test -d "$(CONTENT_PATH)" || (printf 'Content path does not exist: %s\n' "$(CONTENT_PATH)" >&2; exit 1)
|
|
@@ -23,7 +23,11 @@ preview:
|
|
|
23
23
|
$(MAKE) build CONTENT_PATH="$(CONTENT_PATH)" REPO_URL="$(REPO_URL)" BASE_PATH="$(BASE_PATH)" SITE_URL="$(SITE_URL)"
|
|
24
24
|
pnpm exec astro preview
|
|
25
25
|
|
|
26
|
+
check-golden:
|
|
27
|
+
$(MAKE) build CONTENT_PATH="$(CURDIR)"
|
|
28
|
+
node scripts/verify-golden-build.mjs
|
|
29
|
+
|
|
26
30
|
check-external:
|
|
27
31
|
$(MAKE) build CONTENT_PATH="$(CURDIR)/test-fixtures/external-consumer" REPO_URL="https://github.com/example/external-consumer"
|
|
28
|
-
@test -f dist/
|
|
32
|
+
@test -f dist/index.html
|
|
29
33
|
node scripts/verify-external-build.mjs
|
package/README.md
CHANGED
|
@@ -3,53 +3,73 @@ heading: Upcontent
|
|
|
3
3
|
description: Turn a documentation repository into a fast, searchable, customizable static portal.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
<div align="center">
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# Upcontent
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**Turn the documentation repository you already have into a fast, searchable, customizable portal.**
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
[](https://github.com/lumamontes/upcontent/actions/workflows/ci.yml)
|
|
13
|
+
[](https://www.npmjs.com/package/upcontent)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
- A `.upcontent/config.json` file for identity, navigation, rendering, and content boundaries
|
|
16
|
-
- Custom CSS for the consumer's own brand
|
|
17
|
-
- Rendered callouts, Mermaid diagrams, JSON, YAML, CSV, and wiki links
|
|
18
|
-
- A static `dist/` directory that can be deployed to GitHub Pages or any static host
|
|
19
|
-
- Build checks that catch broken wiki links and excluded content before publication
|
|
15
|
+
<a href="https://lumamontes.github.io/upcontent/">See the live showcase</a> · <a href="#get-started">Get started in under a minute</a>
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
</div>
|
|
22
18
|
|
|
23
|
-
##
|
|
19
|
+
## See the result
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
This is the real Upcontent portal generated from this repository and deployed to GitHub Pages.
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- You need more than raw Markdown, but do not need a dynamic application.
|
|
31
|
-
- You want branding and navigation without maintaining a custom docs frontend.
|
|
23
|
+
<p align="center">
|
|
24
|
+
<a href="https://lumamontes.github.io/upcontent/"><img src="assets/readme/portal-home.png" alt="Upcontent documentation portal home page" width="900"></a>
|
|
25
|
+
</p>
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
The same build includes a capability showcase for technical content, structured data, callouts, diagrams, navigation, and search.
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
<p align="center">
|
|
30
|
+
<a href="https://lumamontes.github.io/upcontent/showcase/"><img src="assets/readme/portal-showcase.png" alt="Upcontent capability showcase page" width="900"></a>
|
|
31
|
+
</p>
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
## From repository to portal
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
Upcontent keeps your source of truth in Git and adds the publishing layer: clear navigation, full-text search, technical content rendering, consumer-owned branding, and a repeatable static build.
|
|
36
|
+
|
|
37
|
+
If your Markdown already lives in a GitHub repository, the flow is:
|
|
40
38
|
|
|
41
39
|
```sh
|
|
42
40
|
pnpm dlx upcontent init
|
|
43
41
|
pnpm dlx upcontent dev
|
|
42
|
+
pnpm dlx upcontent check
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
The first command creates `.upcontent/` and a GitHub Pages workflow. The second starts a local portal using the repository you are already in. You do not need to clone the Upcontent renderer into your documentation repository.
|
|
45
|
+
The first command creates `.upcontent/` and a GitHub Pages workflow. The second starts a local portal using the repository you are already in. The third builds the consumer portal and validates the generated site. You do not need to clone the Upcontent renderer into your documentation repository.
|
|
46
|
+
|
|
47
|
+
Push the generated workflow and your documentation is published as a static site. The source repository remains the source of truth; Upcontent does not require a database, a companion server, or a new authoring system.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
## What you get
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
- A responsive [Starlight](https://starlight.astro.build/) portal with navigation, search, themes, and table of contents.
|
|
52
|
+
- A `.upcontent/config.json` file for identity, navigation, rendering, and content boundaries.
|
|
53
|
+
- Consumer-owned logos, favicon, CSS, site identity, and navigation.
|
|
54
|
+
- Rendered callouts, Mermaid diagrams, JSON, YAML, CSV, and wiki links.
|
|
55
|
+
- A static `dist/` directory deployable to GitHub Pages or any static host.
|
|
56
|
+
- Build checks that catch broken wiki links and excluded content before publication.
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
## Is it a good fit?
|
|
59
|
+
|
|
60
|
+
Upcontent is a good fit when:
|
|
61
|
+
|
|
62
|
+
- Your documentation already lives in Markdown or MDX.
|
|
63
|
+
- Your team wants to keep writing in Git.
|
|
64
|
+
- You want the portal to be owned and deployed by the documentation repository.
|
|
65
|
+
- You need more than raw Markdown, but do not need a dynamic application.
|
|
66
|
+
- You want branding and navigation without maintaining a custom docs frontend.
|
|
67
|
+
|
|
68
|
+
It is not the right fit when your site needs runtime authentication, server-rendered personalization, or review comments inside the published portal.
|
|
69
|
+
|
|
70
|
+
## Explore this repository locally
|
|
71
|
+
|
|
72
|
+
Clone this repository, then start the included golden consumer:
|
|
53
73
|
|
|
54
74
|
```sh
|
|
55
75
|
git clone https://github.com/lumamontes/upcontent.git
|
|
@@ -60,19 +80,26 @@ make dev CONTENT_PATH=.
|
|
|
60
80
|
|
|
61
81
|
Then open the local URL printed by Astro. The [capability showcase](showcase/) is the fastest way to see the complete rendering and customization surface.
|
|
62
82
|
|
|
63
|
-
|
|
83
|
+
## Configure your portal
|
|
84
|
+
|
|
85
|
+
The consumer configuration is deliberately small:
|
|
64
86
|
|
|
65
87
|
```json
|
|
66
88
|
{
|
|
67
89
|
"site": {
|
|
68
90
|
"title": "Engineering Docs",
|
|
69
91
|
"description": "Documentation for the engineering team.",
|
|
92
|
+
"socialImage": "https://docs.example.com/social-card.png",
|
|
93
|
+
"locale": "en-US",
|
|
70
94
|
"logo": {
|
|
71
95
|
"src": ".upcontent/logo.svg",
|
|
72
96
|
"alt": "Engineering Docs"
|
|
73
97
|
},
|
|
74
98
|
"favicon": ".upcontent/favicon.svg"
|
|
75
99
|
},
|
|
100
|
+
"seo": {
|
|
101
|
+
"enabled": true
|
|
102
|
+
},
|
|
76
103
|
"repo": {
|
|
77
104
|
"url": "https://github.com/acme/engineering-docs"
|
|
78
105
|
},
|
|
@@ -84,24 +111,13 @@ The generated consumer configuration looks like this:
|
|
|
84
111
|
|
|
85
112
|
Follow [Set up a consumer repository](getting-started/consumer-repository/) for the complete setup, then use [Customization](customization/) to shape the portal.
|
|
86
113
|
|
|
87
|
-
## See the full path
|
|
88
|
-
|
|
89
|
-
- [First build](getting-started/first-build/): run the portal locally and generate static output.
|
|
90
|
-
- [Consumer repository](getting-started/consumer-repository/): prepare content, assets, and configuration.
|
|
91
|
-
- [Customization](customization/): configure identity, theme, navigation, content, JSON, and environment variables.
|
|
92
|
-
- [Authoring content](guides/authoring-content/): write pages that are clear and easy to scan.
|
|
93
|
-
- [Deployment](deployment/): publish the generated files to GitHub Pages or another static host.
|
|
94
|
-
- [Capability showcase](showcase/): inspect all supported content and rendering features in one page.
|
|
95
|
-
|
|
96
114
|
## Build for production
|
|
97
115
|
|
|
98
116
|
```sh
|
|
99
117
|
make build CONTENT_PATH=/path/to/your-consumer-repo
|
|
100
118
|
```
|
|
101
119
|
|
|
102
|
-
The generated site is written to `dist/` and includes the Pagefind search index.
|
|
103
|
-
|
|
104
|
-
Before publishing, run:
|
|
120
|
+
The generated site is written to `dist/` and includes the Pagefind search index. Before publishing, run:
|
|
105
121
|
|
|
106
122
|
```sh
|
|
107
123
|
pnpm test
|
|
@@ -110,6 +126,8 @@ make build CONTENT_PATH=.
|
|
|
110
126
|
make check-external
|
|
111
127
|
```
|
|
112
128
|
|
|
129
|
+
Read the [deployment guide](deployment/) for GitHub Pages and other static hosts.
|
|
130
|
+
|
|
113
131
|
## Built on Astro and Starlight
|
|
114
132
|
|
|
115
133
|
Upcontent uses the official [Astro](https://astro.build/) framework and [Starlight](https://starlight.astro.build/) documentation theme as its rendering foundation. Upcontent adds the consumer-repository contract, content integrity checks, configuration surface, and deployment workflow around them.
|
|
Binary file
|
|
Binary file
|
package/astro.config.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url'
|
|
2
|
-
import { cpSync, existsSync, mkdirSync, statSync } from 'node:fs'
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, rmSync, statSync } from 'node:fs'
|
|
3
3
|
import { basename, resolve, sep } from 'node:path'
|
|
4
4
|
import { unified } from '@astrojs/markdown-remark'
|
|
5
|
+
import sitemap from '@astrojs/sitemap'
|
|
5
6
|
import starlight from '@astrojs/starlight'
|
|
6
7
|
import { defineConfig } from 'astro/config'
|
|
7
8
|
import { visit } from 'unist-util-visit'
|
|
@@ -9,13 +10,37 @@ import { rehypeCallouts } from './src/lib/rehype-callouts.ts'
|
|
|
9
10
|
import { remarkStripDuplicateTitle } from './src/lib/remark-strip-duplicate-title.ts'
|
|
10
11
|
import { remarkStructuredDataPreview } from './src/lib/remark-structured-data-preview.ts'
|
|
11
12
|
import { remarkWikiLinks } from './src/lib/remark-wiki-links.ts'
|
|
13
|
+
import { remarkDocumentLinks } from './src/lib/remark-doc-links.ts'
|
|
12
14
|
import { getPortalConfig } from './src/lib/portal-config.ts'
|
|
13
15
|
import { buildSidebar } from './src/lib/sidebar.ts'
|
|
16
|
+
import { getNoindexRoutes } from './src/lib/seo-sitemap.ts'
|
|
17
|
+
import { hasRootIndex } from './src/lib/homepage.ts'
|
|
14
18
|
import { PRODUCT_NAME, PRODUCT_TAGLINE } from './src/lib/product-identity.ts'
|
|
15
19
|
|
|
16
20
|
const docsRoot = fileURLToPath(new URL('./src/content/docs', import.meta.url))
|
|
17
21
|
const portalConfig = getPortalConfig()
|
|
18
22
|
|
|
23
|
+
function copyContentAssetDirectory(assetPath) {
|
|
24
|
+
const source = resolve(docsRoot, assetPath)
|
|
25
|
+
const target = resolve(process.cwd(), 'public', assetPath)
|
|
26
|
+
const readmeTarget = resolve(process.cwd(), 'public', 'readme', assetPath)
|
|
27
|
+
const hasHomepage = hasRootIndex(docsRoot)
|
|
28
|
+
const targets = hasHomepage ? [target, readmeTarget] : [target]
|
|
29
|
+
if (!hasHomepage) rmSync(readmeTarget, { force: true, recursive: true })
|
|
30
|
+
if (!existsSync(source) || !statSync(source).isDirectory()) {
|
|
31
|
+
for (const target of targets) rmSync(target, { force: true, recursive: true })
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const target of targets) {
|
|
36
|
+
rmSync(target, { force: true, recursive: true })
|
|
37
|
+
mkdirSync(resolve(target, '..'), { recursive: true })
|
|
38
|
+
cpSync(source, target, { recursive: true })
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
copyContentAssetDirectory('assets/readme')
|
|
43
|
+
|
|
19
44
|
function resolvePortalAsset(assetPath) {
|
|
20
45
|
if (!assetPath || assetPath.startsWith('http')) return assetPath
|
|
21
46
|
if (assetPath.startsWith('/')) return assetPath
|
|
@@ -58,6 +83,41 @@ const consumerCss = (portalConfig.theme?.customCss ?? [])
|
|
|
58
83
|
|
|
59
84
|
const customCss = ['./src/styles/callouts.css', './src/styles/structured-data-preview.css', ...consumerCss]
|
|
60
85
|
const starlightOptions = portalConfig.starlight ?? {}
|
|
86
|
+
const noindexRoutes = getNoindexRoutes(docsRoot)
|
|
87
|
+
const configuredSiteUrl = process.env.SITE_URL || portalConfig.site?.url
|
|
88
|
+
let site
|
|
89
|
+
let base = process.env.BASE_PATH || undefined
|
|
90
|
+
if (portalConfig.seo?.enabled === true && configuredSiteUrl) {
|
|
91
|
+
try {
|
|
92
|
+
const parsedSiteUrl = new URL(configuredSiteUrl)
|
|
93
|
+
if (parsedSiteUrl.protocol !== 'http:' && parsedSiteUrl.protocol !== 'https:') throw new Error('unsupported protocol')
|
|
94
|
+
if (parsedSiteUrl.username || parsedSiteUrl.password) throw new Error('userinfo is not allowed')
|
|
95
|
+
site = parsedSiteUrl.origin
|
|
96
|
+
if (!process.env.BASE_PATH) base = parsedSiteUrl.pathname === '/' ? undefined : parsedSiteUrl.pathname
|
|
97
|
+
} catch {
|
|
98
|
+
console.warn(`[${PRODUCT_NAME}] SEO site URL must be an absolute URL: ${configuredSiteUrl}`)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function includeInSitemap(page) {
|
|
103
|
+
const pathname = new URL(page).pathname.replace(/\/+$/, '') || '/'
|
|
104
|
+
const basePath = (base || '').replace(/\/+$/, '')
|
|
105
|
+
const route = pathname === basePath
|
|
106
|
+
? '/'
|
|
107
|
+
: basePath && pathname.startsWith(`${basePath}/`)
|
|
108
|
+
? pathname.slice(basePath.length)
|
|
109
|
+
: pathname
|
|
110
|
+
return !noindexRoutes.has(route)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function serializeSitemapEntry(entry) {
|
|
114
|
+
if (!site) return entry
|
|
115
|
+
const homepageUrl = new URL(`${(base || '').replace(/\/+$/, '')}/`, site).href
|
|
116
|
+
if (entry.url === homepageUrl.replace(/\/$/, '') || entry.url === homepageUrl) {
|
|
117
|
+
return { ...entry, url: homepageUrl }
|
|
118
|
+
}
|
|
119
|
+
return entry
|
|
120
|
+
}
|
|
61
121
|
|
|
62
122
|
// Remark plugin: converts ```mermaid blocks to <div class="mermaid"> BEFORE Shiki runs
|
|
63
123
|
function remarkMermaid() {
|
|
@@ -75,24 +135,12 @@ function remarkMermaid() {
|
|
|
75
135
|
}
|
|
76
136
|
}
|
|
77
137
|
|
|
78
|
-
// Rehype plugin: strip .md suffix from internal hrefs so links resolve correctly
|
|
79
|
-
function rehypeStripMdLinks() {
|
|
80
|
-
return (tree) => {
|
|
81
|
-
visit(tree, 'element', (node) => {
|
|
82
|
-
if (node.tagName !== 'a') return
|
|
83
|
-
const href = node.properties?.href
|
|
84
|
-
if (typeof href !== 'string') return
|
|
85
|
-
if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('#')) return
|
|
86
|
-
if (href.endsWith('.md')) node.properties.href = href.slice(0, -3)
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
138
|
export default defineConfig({
|
|
92
139
|
output: 'static',
|
|
93
|
-
site
|
|
94
|
-
base
|
|
140
|
+
site,
|
|
141
|
+
base,
|
|
95
142
|
integrations: [
|
|
143
|
+
sitemap({ filter: includeInSitemap, serialize: serializeSitemapEntry }),
|
|
96
144
|
starlight({
|
|
97
145
|
title: portalConfig.site?.title ?? PRODUCT_NAME,
|
|
98
146
|
description: portalConfig.site?.description ?? PRODUCT_TAGLINE,
|
|
@@ -115,11 +163,12 @@ export default defineConfig({
|
|
|
115
163
|
processor: unified({
|
|
116
164
|
remarkPlugins: [
|
|
117
165
|
remarkStripDuplicateTitle,
|
|
118
|
-
|
|
166
|
+
[remarkWikiLinks, { contentRoot: docsRoot, basePath: base || '/', failOnBrokenLinks: true }],
|
|
167
|
+
[remarkDocumentLinks, { contentRoot: docsRoot, basePath: base || '/' }],
|
|
119
168
|
remarkMermaid,
|
|
120
169
|
remarkStructuredDataPreview,
|
|
121
170
|
],
|
|
122
|
-
rehypePlugins: [rehypeCallouts
|
|
171
|
+
rehypePlugins: [rehypeCallouts],
|
|
123
172
|
}),
|
|
124
173
|
},
|
|
125
174
|
})
|
|
@@ -38,6 +38,8 @@ The optional fields below cover navigation, Starlight presentation, and content
|
|
|
38
38
|
"title": "Engineering Docs",
|
|
39
39
|
"description": "Documentation for the engineering team.",
|
|
40
40
|
"url": "https://docs.example.com",
|
|
41
|
+
"socialImage": "https://docs.example.com/social-card.png",
|
|
42
|
+
"locale": "en-US",
|
|
41
43
|
"logo": {
|
|
42
44
|
"src": ".upcontent/logo.svg",
|
|
43
45
|
"alt": "Engineering Docs",
|
|
@@ -45,6 +47,9 @@ The optional fields below cover navigation, Starlight presentation, and content
|
|
|
45
47
|
},
|
|
46
48
|
"favicon": ".upcontent/favicon.svg"
|
|
47
49
|
},
|
|
50
|
+
"seo": {
|
|
51
|
+
"enabled": true
|
|
52
|
+
},
|
|
48
53
|
"repo": {
|
|
49
54
|
"url": "https://github.com/acme/engineering-docs"
|
|
50
55
|
},
|
|
@@ -82,7 +87,8 @@ The optional fields below cover navigation, Starlight presentation, and content
|
|
|
82
87
|
|
|
83
88
|
| Group | Controls |
|
|
84
89
|
| --- | --- |
|
|
85
|
-
| `site` | Name, description, URL, logo, and favicon. |
|
|
90
|
+
| `site` | Name, description, URL, social image, locale, logo, and favicon. |
|
|
91
|
+
| `seo` | Explicitly enables search-engine discoverability. Defaults to disabled. |
|
|
86
92
|
| `repo` | Source repository links. |
|
|
87
93
|
| `theme` | Consumer-owned local CSS. |
|
|
88
94
|
| `starlight` | Safe layout, social, table of contents, pagination, and code options. |
|
|
@@ -90,3 +96,5 @@ The optional fields below cover navigation, Starlight presentation, and content
|
|
|
90
96
|
| `content` | Frontmatter title field selection. |
|
|
91
97
|
|
|
92
98
|
Invalid curated values are ignored or fall back safely. Heading levels must be integers from 1 through 6, and the minimum cannot exceed the maximum.
|
|
99
|
+
|
|
100
|
+
SEO is opt-in. With `seo.enabled` omitted or set to `false`, the portal emits `noindex, nofollow` and `robots.txt` disallows crawling. This does not protect the site: use access-controlled hosting for a private portal.
|
package/customization/content.md
CHANGED
|
@@ -35,6 +35,20 @@ sidebar:
|
|
|
35
35
|
|
|
36
36
|
Use `##` for body sections because the page title is rendered as the top-level heading.
|
|
37
37
|
|
|
38
|
+
SEO-specific frontmatter is optional:
|
|
39
|
+
|
|
40
|
+
```md
|
|
41
|
+
---
|
|
42
|
+
title: Configure a consumer repository
|
|
43
|
+
description: Set up a documentation repository and publish it as a searchable portal.
|
|
44
|
+
canonical: https://docs.example.com/getting-started/consumer-repository/
|
|
45
|
+
image: https://docs.example.com/social-card.png
|
|
46
|
+
noindex: false
|
|
47
|
+
---
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`description` is used in search and social metadata. `canonical`, `image`, and `noindex` override the generated defaults for that page. The site-level `socialImage` in `.upcontent/config.json` is used when a page does not define its own image.
|
|
51
|
+
|
|
38
52
|
## Supported content features
|
|
39
53
|
|
|
40
54
|
- Obsidian-style callouts for notes, tips, cautions, and dangers
|
|
@@ -7,6 +7,8 @@ sidebar:
|
|
|
7
7
|
|
|
8
8
|
The sidebar is generated from the consumer file structure. Use configuration when the default filesystem order is not the right experience for readers.
|
|
9
9
|
|
|
10
|
+
If the repository has a root `index.md`, it becomes the website homepage and a root `README.md` remains available at `/readme/`. Repositories without `index.md` keep the backwards-compatible behavior where `README.md` is the homepage.
|
|
11
|
+
|
|
10
12
|
## Select top-level roots
|
|
11
13
|
|
|
12
14
|
```json
|
package/deployment/index.md
CHANGED
|
@@ -11,6 +11,7 @@ Upcontent produces static files. The deployment job only needs to build the cont
|
|
|
11
11
|
|
|
12
12
|
- [GitHub Pages](github-pages/): use the included workflow and repository settings.
|
|
13
13
|
- [Other static hosts](static-hosts/): publish `dist/` to Netlify, Vercel, S3, or another file host.
|
|
14
|
+
- [Release the npm package](npm/): publish versioned package releases through GitHub Actions.
|
|
14
15
|
- [Environment variables](../customization/environment/): set the URL and base path for the host.
|
|
15
16
|
|
|
16
17
|
## Before publishing
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Release the npm package
|
|
3
|
+
description: Publish versioned Upcontent releases through GitHub Actions and npm Trusted Publishing.
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 3
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Upcontent publishes the `upcontent` package from version tags. Ordinary pushes to `main` run validation and deploy the documentation portal, but do not publish to npm.
|
|
9
|
+
|
|
10
|
+
## Configure npm once
|
|
11
|
+
|
|
12
|
+
On the [upcontent npm package](https://www.npmjs.com/package/upcontent), add a GitHub Actions Trusted Publisher with:
|
|
13
|
+
|
|
14
|
+
- Organization or user: `lumamontes`
|
|
15
|
+
- Repository: `upcontent`
|
|
16
|
+
- Workflow filename: `publish.yml`
|
|
17
|
+
- Allow direct `npm publish`
|
|
18
|
+
|
|
19
|
+
Trusted Publishing uses short-lived GitHub OIDC credentials. No npm token is stored in GitHub Actions.
|
|
20
|
+
|
|
21
|
+
## Release a version
|
|
22
|
+
|
|
23
|
+
Choose the next semantic version, update `package.json`, and commit the change:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm version patch --no-git-tag-version
|
|
27
|
+
git add package.json
|
|
28
|
+
git commit -m "release: v$(node -p "require('./package.json').version")"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Create and push the matching tag:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
35
|
+
git tag -a "v$VERSION" -m "Release v$VERSION"
|
|
36
|
+
git push origin main --follow-tags
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The publish workflow verifies that the tag and package version match, runs the full validation contract, inspects the package contents, and publishes the package with npm provenance.
|
|
@@ -13,6 +13,7 @@ Use this checklist before opening a pull request or publishing a consumer reposi
|
|
|
13
13
|
pnpm test
|
|
14
14
|
pnpm check
|
|
15
15
|
make build CONTENT_PATH=.
|
|
16
|
+
make check-golden
|
|
16
17
|
make check-external
|
|
17
18
|
git diff --check
|
|
18
19
|
```
|
|
@@ -36,6 +37,10 @@ Open the pages listed in the [capability showcase](../showcase/) and check:
|
|
|
36
37
|
- Callouts, Mermaid, JSON, YAML, and CSV examples render correctly.
|
|
37
38
|
- The layout works at desktop and mobile widths.
|
|
38
39
|
- The browser console has no required-asset errors.
|
|
40
|
+
- Each indexable page has a useful title, description, canonical URL, and one top-level heading.
|
|
41
|
+
- `dist/robots.txt` points to the generated sitemap.
|
|
42
|
+
- The page source contains JSON-LD and uses the intended `SITE_URL` and `BASE_PATH`.
|
|
43
|
+
- `seo.enabled` matches the intended portal policy; disabled SEO must produce `noindex, nofollow` and `Disallow: /`.
|
|
39
44
|
|
|
40
45
|
## Check content boundaries
|
|
41
46
|
|
package/index.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
heading: Upcontent
|
|
3
|
+
description: Turn a documentation repository into a fast, searchable, customizable static portal.
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 1
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Upcontent turns a Markdown repository into a documentation site that is ready to share.
|
|
9
|
+
|
|
10
|
+
It adds clear navigation, full-text search, technical content rendering, consumer-owned branding, and a repeatable static build while keeping the source repository as the source of truth.
|
|
11
|
+
|
|
12
|
+
## Start here
|
|
13
|
+
|
|
14
|
+
- [Set up a consumer repository](getting-started/consumer-repository/)
|
|
15
|
+
- [Run your first build](getting-started/first-build/)
|
|
16
|
+
- [Customize the portal](customization/)
|
|
17
|
+
- [Validate before publishing](guides/validate-your-site/)
|
|
18
|
+
|
|
19
|
+
The [repository README](readme/) contains the project and development details for Upcontent itself.
|