upcontent 0.0.0-stage → 0.1.1

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.
Files changed (86) hide show
  1. package/.github/workflows/ci.yml +61 -0
  2. package/.github/workflows/deploy.yml +59 -0
  3. package/.github/workflows/publish.yml +75 -0
  4. package/.github/workflows/reusable-pages.yml +74 -0
  5. package/.upcontent/config.json +85 -0
  6. package/.upcontent/favicon.svg +4 -0
  7. package/.upcontent/logo.svg +6 -0
  8. package/.upcontent/portal.css +139 -0
  9. package/CONTEXT.md +25 -0
  10. package/Makefile +33 -0
  11. package/README.md +132 -2
  12. package/SHOWCASE.mdx +163 -0
  13. package/assets/readme/portal-home.png +0 -0
  14. package/assets/readme/portal-showcase.png +0 -0
  15. package/astro.config.mjs +167 -0
  16. package/customization/config-json.md +100 -0
  17. package/customization/content.md +62 -0
  18. package/customization/environment.md +43 -0
  19. package/customization/index.md +33 -0
  20. package/customization/navigation.md +57 -0
  21. package/customization/site-identity.md +43 -0
  22. package/customization/theme.md +47 -0
  23. package/deployment/github-pages.md +46 -0
  24. package/deployment/index.md +25 -0
  25. package/deployment/npm.md +39 -0
  26. package/deployment/static-hosts.md +34 -0
  27. package/getting-started/consumer-repository.md +156 -0
  28. package/getting-started/first-build.md +78 -0
  29. package/guides/authoring-content.md +105 -0
  30. package/guides/validate-your-site.md +58 -0
  31. package/package.json +40 -4
  32. package/scripts/upcontent-cli.mjs +111 -0
  33. package/scripts/verify-external-build.mjs +32 -0
  34. package/scripts/verify-golden-build.mjs +9 -0
  35. package/src/components/MermaidLoader.astro +304 -0
  36. package/src/components/PaletteShowcase.astro +122 -0
  37. package/src/components/StructuredDataCopy.astro +22 -0
  38. package/src/content/__mocks__/astro-content.ts +7 -0
  39. package/src/content/__mocks__/astro-loaders.ts +3 -0
  40. package/src/content/i18n/en.json +1 -0
  41. package/src/content.config.test.ts +209 -0
  42. package/src/content.config.ts +113 -0
  43. package/src/lib/content-blocklist.test.ts +47 -0
  44. package/src/lib/content-blocklist.ts +55 -0
  45. package/src/lib/doc-links.test.ts +62 -0
  46. package/src/lib/doc-links.ts +31 -0
  47. package/src/lib/mermaid-render.test.ts +42 -0
  48. package/src/lib/mermaid-render.ts +24 -0
  49. package/src/lib/portal-config.test.ts +153 -0
  50. package/src/lib/portal-config.ts +187 -0
  51. package/src/lib/portal-routes.test.ts +62 -0
  52. package/src/lib/portal-routes.ts +59 -0
  53. package/src/lib/product-identity.ts +2 -0
  54. package/src/lib/rehype-callouts.test.ts +69 -0
  55. package/src/lib/rehype-callouts.ts +61 -0
  56. package/src/lib/remark-doc-links.test.ts +50 -0
  57. package/src/lib/remark-doc-links.ts +21 -0
  58. package/src/lib/remark-strip-duplicate-title.test.ts +73 -0
  59. package/src/lib/remark-strip-duplicate-title.ts +37 -0
  60. package/src/lib/remark-structured-data-preview.test.ts +104 -0
  61. package/src/lib/remark-structured-data-preview.ts +66 -0
  62. package/src/lib/remark-wiki-links.test.ts +102 -0
  63. package/src/lib/remark-wiki-links.ts +112 -0
  64. package/src/lib/seo-sitemap.test.ts +37 -0
  65. package/src/lib/seo-sitemap.ts +54 -0
  66. package/src/lib/sidebar.test.ts +142 -0
  67. package/src/lib/sidebar.ts +114 -0
  68. package/src/lib/structured-data-tree.test.ts +75 -0
  69. package/src/lib/structured-data-tree.ts +55 -0
  70. package/src/overrides/Footer.astro +114 -0
  71. package/src/overrides/Head.astro +103 -0
  72. package/src/pages/robots.txt.ts +46 -0
  73. package/src/styles/callouts.css +29 -0
  74. package/src/styles/structured-data-preview.css +95 -0
  75. package/src/upcontent-cli.test.ts +36 -0
  76. package/test-fixtures/external-consumer/.upcontent/config.json +30 -0
  77. package/test-fixtures/external-consumer/.upcontent/favicon.svg +4 -0
  78. package/test-fixtures/external-consumer/.upcontent/logo.svg +4 -0
  79. package/test-fixtures/external-consumer/.upcontent/theme.css +4 -0
  80. package/test-fixtures/external-consumer/.upcontent-renderer/README.md +3 -0
  81. package/test-fixtures/external-consumer/README.md +6 -0
  82. package/test-fixtures/external-consumer/forbidden.md +5 -0
  83. package/test-fixtures/external-consumer/noindex.md +7 -0
  84. package/test-fixtures/external-consumer/public.md +6 -0
  85. package/tsconfig.json +7 -0
  86. package/vitest.config.ts +18 -0
package/README.md CHANGED
@@ -1,3 +1,133 @@
1
- # Temporary Holding Version
1
+ ---
2
+ heading: Upcontent
3
+ description: Turn a documentation repository into a fast, searchable, customizable static portal.
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ <div align="center">
7
+
8
+ # Upcontent
9
+
10
+ **Turn the documentation repository you already have into a fast, searchable, customizable portal.**
11
+
12
+ [![CI](https://github.com/lumamontes/upcontent/actions/workflows/ci.yml/badge.svg)](https://github.com/lumamontes/upcontent/actions/workflows/ci.yml)
13
+ [![npm](https://img.shields.io/npm/v/upcontent?color=0f766e&label=npm)](https://www.npmjs.com/package/upcontent)
14
+
15
+ <a href="https://lumamontes.github.io/upcontent/">See the live showcase</a> · <a href="#get-started">Get started in under a minute</a>
16
+
17
+ </div>
18
+
19
+ ## See the result
20
+
21
+ This is the real Upcontent portal generated from this repository and deployed to GitHub Pages.
22
+
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>
26
+
27
+ The same build includes a capability showcase for technical content, structured data, callouts, diagrams, navigation, and search.
28
+
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>
32
+
33
+ ## From repository to portal
34
+
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:
38
+
39
+ ```sh
40
+ pnpm dlx upcontent init
41
+ pnpm dlx upcontent dev
42
+ pnpm dlx upcontent check
43
+ ```
44
+
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.
48
+
49
+ ## What you get
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.
57
+
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:
73
+
74
+ ```sh
75
+ git clone https://github.com/lumamontes/upcontent.git
76
+ cd upcontent
77
+ pnpm install
78
+ make dev CONTENT_PATH=.
79
+ ```
80
+
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.
82
+
83
+ ## Configure your portal
84
+
85
+ The consumer configuration is deliberately small:
86
+
87
+ ```json
88
+ {
89
+ "site": {
90
+ "title": "Engineering Docs",
91
+ "description": "Documentation for the engineering team.",
92
+ "socialImage": "https://docs.example.com/social-card.png",
93
+ "locale": "en-US",
94
+ "logo": {
95
+ "src": ".upcontent/logo.svg",
96
+ "alt": "Engineering Docs"
97
+ },
98
+ "favicon": ".upcontent/favicon.svg"
99
+ },
100
+ "seo": {
101
+ "enabled": true
102
+ },
103
+ "repo": {
104
+ "url": "https://github.com/acme/engineering-docs"
105
+ },
106
+ "theme": {
107
+ "customCss": [".upcontent/theme.css"]
108
+ }
109
+ }
110
+ ```
111
+
112
+ Follow [Set up a consumer repository](getting-started/consumer-repository/) for the complete setup, then use [Customization](customization/) to shape the portal.
113
+
114
+ ## Build for production
115
+
116
+ ```sh
117
+ make build CONTENT_PATH=/path/to/your-consumer-repo
118
+ ```
119
+
120
+ The generated site is written to `dist/` and includes the Pagefind search index. Before publishing, run:
121
+
122
+ ```sh
123
+ pnpm test
124
+ pnpm check
125
+ make build CONTENT_PATH=.
126
+ make check-external
127
+ ```
128
+
129
+ Read the [deployment guide](deployment/) for GitHub Pages and other static hosts.
130
+
131
+ ## Built on Astro and Starlight
132
+
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.
package/SHOWCASE.mdx ADDED
@@ -0,0 +1,163 @@
1
+ ---
2
+ heading: Capability showcase
3
+ description: One practical page showing what a consumer repository can render, configure, and publish with Upcontent.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ import PaletteShowcase from './src/components/PaletteShowcase.astro';
9
+
10
+ Upcontent turns Markdown into a searchable static documentation site. Configure the consumer repository, add the content formats your team needs, and publish the generated `dist/` directory to a static host.
11
+
12
+ ## Rendered content
13
+
14
+ The portal accepts ordinary Markdown and adds a small set of useful content features.
15
+
16
+ ### Callouts
17
+
18
+ :::tip[Use callouts for decisions]
19
+ Keep callouts short. Use them for a warning, a recommendation, or a next action that should not disappear in the surrounding text.
20
+ :::
21
+
22
+ :::caution
23
+ Static output is public wherever the selected host is public. A private source repository does not automatically make the published site private.
24
+ :::
25
+
26
+ ### Code and diagrams
27
+
28
+ ```ts
29
+ export function buildPortal(contentPath: string) {
30
+ return `make build CONTENT_PATH=${contentPath}`
31
+ }
32
+ ```
33
+
34
+ ```mermaid
35
+ flowchart LR
36
+ Content[Consumer repository] --> Config[.upcontent/config.json]
37
+ Config --> Build[Static build]
38
+ Content --> Build
39
+ Build --> Output[Searchable portal]
40
+ ```
41
+
42
+ Mermaid diagrams support theme synchronization, fullscreen, zoom, pan, source copying, and an invalid-source fallback.
43
+
44
+ ### Structured examples
45
+
46
+ JSON and YAML blocks remain readable as collapsible data:
47
+
48
+ ```json
49
+ {
50
+ "portal": "static",
51
+ "search": "pagefind",
52
+ "themes": ["light", "dark"]
53
+ }
54
+ ```
55
+
56
+ ```yaml
57
+ contentPath: ./docs
58
+ publish: github-pages
59
+ sourceOfTruth: consumer-repository
60
+ ```
61
+
62
+ CSV becomes a table when the content is tabular:
63
+
64
+ ```csv
65
+ capability,status
66
+ custom-css,available
67
+ blocklist,available
68
+ pagefind,available
69
+ ```
70
+
71
+ ### Links and navigation
72
+
73
+ Regular Markdown links are best for stable routes, such as [site validation](../guides/validate-your-site/). Wiki links are useful when the source repository refers to files by name, such as `[[README]]`. Missing wiki-link targets fail the build instead of producing broken navigation.
74
+
75
+ ## Consumer customization
76
+
77
+ The consumer changes identity and presentation in `.upcontent/`, without editing the portal renderer:
78
+
79
+ ```text
80
+ .upcontent/
81
+ ├── config.json
82
+ ├── favicon.svg
83
+ ├── logo.svg
84
+ └── theme.css
85
+ ```
86
+
87
+ The configuration controls:
88
+
89
+ - Site title, description, URL, logo, and favicon
90
+ - Repository links and local custom CSS
91
+ - Social links, table of contents, pagination, last-updated metadata, and code styling
92
+ - Sidebar roots, labels, and content blocklists
93
+ - The frontmatter field used as a page title
94
+
95
+ Example identity configuration:
96
+
97
+ ```json
98
+ {
99
+ "site": {
100
+ "title": "Engineering Docs",
101
+ "description": "Documentation for the engineering team.",
102
+ "logo": {
103
+ "src": ".upcontent/logo.svg",
104
+ "alt": "Engineering Docs"
105
+ },
106
+ "favicon": ".upcontent/favicon.svg"
107
+ },
108
+ "repo": {
109
+ "url": "https://github.com/acme/engineering-docs"
110
+ },
111
+ "theme": {
112
+ "customCss": [".upcontent/theme.css"]
113
+ }
114
+ }
115
+ ```
116
+
117
+ The [configuration reference](../customization/config-json/) explains every supported field and its fallback behavior.
118
+
119
+ ### Palette variations
120
+
121
+ The same Starlight surface can carry different consumer identities through tokens and custom CSS:
122
+
123
+ <PaletteShowcase />
124
+
125
+ These are examples for a consumer repository, not a runtime theme picker that every published portal needs to expose.
126
+
127
+ ## Content boundaries
128
+
129
+ The build excludes protected internal paths and consumer blocklists before parsing. This keeps excluded files out of both the content collection and the sidebar.
130
+
131
+ ```json
132
+ {
133
+ "navigation": {
134
+ "blocklist": {
135
+ "exact": ["notes.md"],
136
+ "prefixes": ["drafts/", "internal/"]
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ Broken wiki links and traversal attempts fail with the source file and invalid reference. The same pipeline also works when `CONTENT_PATH` points to a separate consumer repository.
143
+
144
+ ## Validation and publishing
145
+
146
+ The repeatable local contract is:
147
+
148
+ ```sh
149
+ pnpm test
150
+ pnpm check
151
+ make build CONTENT_PATH=.
152
+ make check-external
153
+ ```
154
+
155
+ The reference CI workflow validates the site and an external consumer fixture. The deployment workflow publishes `dist/` to a static host such as GitHub Pages.
156
+
157
+ ## Explore the product
158
+
159
+ - [Your first build](../getting-started/first-build/)
160
+ - [Set up a consumer repository](../getting-started/consumer-repository/)
161
+ - [Authoring content](../guides/authoring-content/)
162
+ - [Validate your site](../guides/validate-your-site/)
163
+ - [Deployment](../deployment/)
Binary file
@@ -0,0 +1,167 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { cpSync, existsSync, mkdirSync, rmSync, statSync } from 'node:fs'
3
+ import { basename, resolve, sep } from 'node:path'
4
+ import { unified } from '@astrojs/markdown-remark'
5
+ import sitemap from '@astrojs/sitemap'
6
+ import starlight from '@astrojs/starlight'
7
+ import { defineConfig } from 'astro/config'
8
+ import { visit } from 'unist-util-visit'
9
+ import { rehypeCallouts } from './src/lib/rehype-callouts.ts'
10
+ import { remarkStripDuplicateTitle } from './src/lib/remark-strip-duplicate-title.ts'
11
+ import { remarkStructuredDataPreview } from './src/lib/remark-structured-data-preview.ts'
12
+ import { remarkWikiLinks } from './src/lib/remark-wiki-links.ts'
13
+ import { remarkDocumentLinks } from './src/lib/remark-doc-links.ts'
14
+ import { getPortalConfig } from './src/lib/portal-config.ts'
15
+ import { buildSidebar } from './src/lib/sidebar.ts'
16
+ import { getNoindexRoutes } from './src/lib/seo-sitemap.ts'
17
+ import { PRODUCT_NAME, PRODUCT_TAGLINE } from './src/lib/product-identity.ts'
18
+
19
+ const docsRoot = fileURLToPath(new URL('./src/content/docs', import.meta.url))
20
+ const portalConfig = getPortalConfig()
21
+
22
+ function copyContentAssetDirectory(assetPath) {
23
+ const source = resolve(docsRoot, assetPath)
24
+ const target = resolve(process.cwd(), 'public', assetPath)
25
+ if (!existsSync(source) || !statSync(source).isDirectory()) {
26
+ rmSync(target, { force: true, recursive: true })
27
+ return
28
+ }
29
+
30
+ rmSync(target, { force: true, recursive: true })
31
+ mkdirSync(resolve(target, '..'), { recursive: true })
32
+ cpSync(source, target, { recursive: true })
33
+ }
34
+
35
+ copyContentAssetDirectory('assets/readme')
36
+
37
+ function resolvePortalAsset(assetPath) {
38
+ if (!assetPath || assetPath.startsWith('http')) return assetPath
39
+ if (assetPath.startsWith('/')) return assetPath
40
+
41
+ const source = resolve(docsRoot, assetPath)
42
+ if (!source.startsWith(`${resolve(docsRoot)}${sep}`) || !existsSync(source) || !statSync(source).isFile()) {
43
+ console.warn(`[${PRODUCT_NAME}] Portal asset not found: ${source}`)
44
+ return undefined
45
+ }
46
+
47
+ const targetDir = resolve(process.cwd(), 'public/upcontent-assets')
48
+ mkdirSync(targetDir, { recursive: true })
49
+ const targetName = basename(source)
50
+ cpSync(source, resolve(targetDir, targetName))
51
+ return `/upcontent-assets/${targetName}`
52
+ }
53
+
54
+ function resolvePortalLogo(logo) {
55
+ if (!logo) return undefined
56
+ if (logo.src.startsWith('http') || logo.src.startsWith('/')) return logo
57
+ const source = resolve(docsRoot, logo.src)
58
+ if (!source.startsWith(`${resolve(docsRoot)}${sep}`) || !existsSync(source) || !statSync(source).isFile()) {
59
+ console.warn(`[${PRODUCT_NAME}] Portal logo not found: ${source}`)
60
+ return undefined
61
+ }
62
+ const targetDir = resolve(process.cwd(), 'src/assets')
63
+ mkdirSync(targetDir, { recursive: true })
64
+ const target = resolve(targetDir, 'consumer-logo.svg')
65
+ cpSync(source, target)
66
+ return { ...logo, src: './src/assets/consumer-logo.svg' }
67
+ }
68
+
69
+ const consumerCss = (portalConfig.theme?.customCss ?? [])
70
+ .map(cssPath => resolve(docsRoot, cssPath))
71
+ .filter(cssPath => {
72
+ if (existsSync(cssPath)) return true
73
+ console.warn(`[${PRODUCT_NAME}] Custom CSS file not found: ${cssPath}`)
74
+ return false
75
+ })
76
+
77
+ const customCss = ['./src/styles/callouts.css', './src/styles/structured-data-preview.css', ...consumerCss]
78
+ const starlightOptions = portalConfig.starlight ?? {}
79
+ const noindexRoutes = getNoindexRoutes(docsRoot)
80
+ const configuredSiteUrl = process.env.SITE_URL || portalConfig.site?.url
81
+ let site
82
+ let base = process.env.BASE_PATH || undefined
83
+ if (portalConfig.seo?.enabled === true && configuredSiteUrl) {
84
+ try {
85
+ const parsedSiteUrl = new URL(configuredSiteUrl)
86
+ if (parsedSiteUrl.protocol !== 'http:' && parsedSiteUrl.protocol !== 'https:') throw new Error('unsupported protocol')
87
+ if (parsedSiteUrl.username || parsedSiteUrl.password) throw new Error('userinfo is not allowed')
88
+ site = parsedSiteUrl.origin
89
+ if (!process.env.BASE_PATH) base = parsedSiteUrl.pathname === '/' ? undefined : parsedSiteUrl.pathname
90
+ } catch {
91
+ console.warn(`[${PRODUCT_NAME}] SEO site URL must be an absolute URL: ${configuredSiteUrl}`)
92
+ }
93
+ }
94
+
95
+ function includeInSitemap(page) {
96
+ const pathname = new URL(page).pathname.replace(/\/+$/, '') || '/'
97
+ const basePath = (base || '').replace(/\/+$/, '')
98
+ const route = pathname === basePath
99
+ ? '/'
100
+ : basePath && pathname.startsWith(`${basePath}/`)
101
+ ? pathname.slice(basePath.length)
102
+ : pathname
103
+ return !noindexRoutes.has(route)
104
+ }
105
+
106
+ function serializeSitemapEntry(entry) {
107
+ if (!site) return entry
108
+ const homepageUrl = new URL(`${(base || '').replace(/\/+$/, '')}/`, site).href
109
+ if (entry.url === homepageUrl.replace(/\/$/, '') || entry.url === homepageUrl) {
110
+ return { ...entry, url: homepageUrl }
111
+ }
112
+ return entry
113
+ }
114
+
115
+ // Remark plugin: converts ```mermaid blocks to <div class="mermaid"> BEFORE Shiki runs
116
+ function remarkMermaid() {
117
+ return (tree) => {
118
+ visit(tree, 'code', (node, index, parent) => {
119
+ if (node.lang !== 'mermaid') return
120
+ // <br/> inside a div becomes a DOM element before Mermaid parses the text, breaking the parser
121
+ const safe = node.value.replace(/<br\s*\/?>/gi, ' ')
122
+ parent.children.splice(index, 1, {
123
+ type: 'html',
124
+ value: `<div class="mermaid">${safe}</div>`,
125
+ })
126
+ return index + 1
127
+ })
128
+ }
129
+ }
130
+
131
+ export default defineConfig({
132
+ output: 'static',
133
+ site,
134
+ base,
135
+ integrations: [
136
+ sitemap({ filter: includeInSitemap, serialize: serializeSitemapEntry }),
137
+ starlight({
138
+ title: portalConfig.site?.title ?? PRODUCT_NAME,
139
+ description: portalConfig.site?.description ?? PRODUCT_TAGLINE,
140
+ logo: resolvePortalLogo(portalConfig.site?.logo),
141
+ favicon: resolvePortalAsset(portalConfig.site?.favicon),
142
+ components: {
143
+ Head: './src/overrides/Head.astro',
144
+ Footer: './src/overrides/Footer.astro',
145
+ },
146
+ customCss,
147
+ social: starlightOptions.social,
148
+ tableOfContents: starlightOptions.tableOfContents,
149
+ lastUpdated: starlightOptions.lastUpdated,
150
+ pagination: starlightOptions.pagination,
151
+ expressiveCode: starlightOptions.expressiveCode,
152
+ sidebar: buildSidebar(docsRoot),
153
+ }),
154
+ ],
155
+ markdown: {
156
+ processor: unified({
157
+ remarkPlugins: [
158
+ remarkStripDuplicateTitle,
159
+ [remarkWikiLinks, { contentRoot: docsRoot, basePath: import.meta.env.BASE_URL, failOnBrokenLinks: true }],
160
+ [remarkDocumentLinks, { contentRoot: docsRoot, basePath: import.meta.env.BASE_URL }],
161
+ remarkMermaid,
162
+ remarkStructuredDataPreview,
163
+ ],
164
+ rehypePlugins: [rehypeCallouts],
165
+ }),
166
+ },
167
+ })
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: JSON configuration reference
3
+ description: Complete reference for the consumer-owned .upcontent/config.json file.
4
+ sidebar:
5
+ order: 6
6
+ ---
7
+
8
+ The `.upcontent/config.json` file is optional. An omitted field uses the portal or Starlight default.
9
+
10
+ ## Start with the minimum
11
+
12
+ You only need a title and repository link to get started. Add a logo and custom CSS when you are ready to make the portal yours:
13
+
14
+ ```json
15
+ {
16
+ "site": {
17
+ "title": "Engineering Docs",
18
+ "description": "Documentation for the engineering team."
19
+ },
20
+ "repo": {
21
+ "url": "https://github.com/acme/engineering-docs"
22
+ }
23
+ }
24
+ ```
25
+
26
+ Relative asset paths resolve from the consumer repository. Absolute URLs are also supported for externally hosted assets.
27
+
28
+ ## Complete example
29
+
30
+ The optional fields below cover navigation, Starlight presentation, and content conventions. You can add them one group at a time.
31
+
32
+ <details>
33
+ <summary>Show the complete configuration</summary>
34
+
35
+ ```json
36
+ {
37
+ "site": {
38
+ "title": "Engineering Docs",
39
+ "description": "Documentation for the engineering team.",
40
+ "url": "https://docs.example.com",
41
+ "socialImage": "https://docs.example.com/social-card.png",
42
+ "locale": "en-US",
43
+ "logo": {
44
+ "src": ".upcontent/logo.svg",
45
+ "alt": "Engineering Docs",
46
+ "replacesTitle": false
47
+ },
48
+ "favicon": ".upcontent/favicon.svg"
49
+ },
50
+ "seo": {
51
+ "enabled": true
52
+ },
53
+ "repo": {
54
+ "url": "https://github.com/acme/engineering-docs"
55
+ },
56
+ "theme": {
57
+ "customCss": [".upcontent/theme.css"]
58
+ },
59
+ "starlight": {
60
+ "social": [
61
+ { "icon": "github", "label": "GitHub", "href": "https://github.com/acme/engineering-docs" }
62
+ ],
63
+ "tableOfContents": { "minHeadingLevel": 2, "maxHeadingLevel": 3 },
64
+ "lastUpdated": true,
65
+ "pagination": true,
66
+ "expressiveCode": {
67
+ "styleOverrides": { "borderRadius": "0.6rem" }
68
+ }
69
+ },
70
+ "navigation": {
71
+ "roots": ["README.md", "guides"],
72
+ "labelOverrides": { "api": "API reference" },
73
+ "blocklist": {
74
+ "exact": ["notes.md"],
75
+ "prefixes": ["drafts/"]
76
+ }
77
+ },
78
+ "content": {
79
+ "titleField": "title"
80
+ }
81
+ }
82
+ ```
83
+
84
+ </details>
85
+
86
+ ## Field groups
87
+
88
+ | Group | Controls |
89
+ | --- | --- |
90
+ | `site` | Name, description, URL, social image, locale, logo, and favicon. |
91
+ | `seo` | Explicitly enables search-engine discoverability. Defaults to disabled. |
92
+ | `repo` | Source repository links. |
93
+ | `theme` | Consumer-owned local CSS. |
94
+ | `starlight` | Safe layout, social, table of contents, pagination, and code options. |
95
+ | `navigation` | Sidebar roots, labels, and blocklists. |
96
+ | `content` | Frontmatter title field selection. |
97
+
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.
@@ -0,0 +1,62 @@
1
+ ---
2
+ title: Content behavior
3
+ description: Configure page titles, metadata, and the Markdown features available to authors.
4
+ sidebar:
5
+ order: 5
6
+ ---
7
+
8
+ Upcontent accepts Markdown and MDX files from the consumer repository. Starlight provides the page layout and frontmatter schema; Upcontent adds title fallback, wiki links, callouts, Mermaid, and structured previews.
9
+
10
+ ## Choose a title field
11
+
12
+ If an existing repository uses a field such as `heading` instead of `title`, configure it once:
13
+
14
+ ```json
15
+ {
16
+ "content": {
17
+ "titleField": "heading"
18
+ }
19
+ }
20
+ ```
21
+
22
+ When the configured field is missing, the portal falls back to the filename. A file named `first-build.md` becomes `First Build`.
23
+
24
+ ## Page metadata
25
+
26
+ Use frontmatter at the top of a page:
27
+
28
+ ```md
29
+ ---
30
+ heading: Configure a consumer repository
31
+ sidebar:
32
+ order: 2
33
+ ---
34
+ ```
35
+
36
+ Use `##` for body sections because the page title is rendered as the top-level heading.
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
+
52
+ ## Supported content features
53
+
54
+ - Obsidian-style callouts for notes, tips, cautions, and dangers
55
+ - Mermaid diagrams with theme-aware rendering and interaction controls
56
+ - JSON and YAML data previews
57
+ - CSV tables
58
+ - `[[wiki links]]` with build-time target validation
59
+ - Syntax-highlighted code blocks
60
+ - Standard Markdown links, tables, lists, and images
61
+
62
+ See [Authoring content](../guides/authoring-content/) for writing rules and the [capability showcase](../showcase/) for rendered examples.
@@ -0,0 +1,43 @@
1
+ ---
2
+ title: Environment variables
3
+ description: Configure repository links and hosting paths at build time.
4
+ sidebar:
5
+ order: 7
6
+ ---
7
+
8
+ The portal is static, but a few values belong to the build environment rather than consumer content.
9
+
10
+ ## Supported variables
11
+
12
+ | Variable | Purpose | Example |
13
+ | --- | --- | --- |
14
+ | `CONTENT_PATH` | Filesystem path passed to `make dev` or `make build`. | `./` |
15
+ | `REPO_URL` | Overrides the configured source repository URL for generated GitHub links. | `https://github.com/acme/docs` |
16
+ | `BASE_PATH` | URL prefix for a project site hosted below the domain root. | `/engineering-docs` |
17
+ | `SITE_URL` | Canonical site origin used by Astro integrations such as the sitemap. | `https://docs.example.com` |
18
+
19
+ ## Local development
20
+
21
+ Pass the content path to Make:
22
+
23
+ ```sh
24
+ make dev CONTENT_PATH=.
25
+ ```
26
+
27
+ `CONTENT_PATH` is a Make variable, not a value read from `.upcontent/config.json`.
28
+
29
+ ## Static hosting
30
+
31
+ For GitHub Pages project sites, set a base path and site URL in the deployment workflow:
32
+
33
+ ```yaml
34
+ env:
35
+ BASE_PATH: /engineering-docs
36
+ SITE_URL: https://acme.github.io/engineering-docs
37
+ ```
38
+
39
+ For a custom domain or a host serving the site at `/`, leave `BASE_PATH` empty and set `SITE_URL` to the canonical origin.
40
+
41
+ ## Precedence
42
+
43
+ `REPO_URL` takes precedence over `repo.url` for generated source links. `BASE_PATH` and `SITE_URL` are build inputs; they do not change the consumer configuration file.