upcontent 0.0.0-stage → 0.1.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.
Files changed (70) hide show
  1. package/.github/workflows/ci.yml +61 -0
  2. package/.github/workflows/deploy.yml +59 -0
  3. package/.github/workflows/reusable-pages.yml +74 -0
  4. package/.upcontent/config.json +82 -0
  5. package/.upcontent/favicon.svg +4 -0
  6. package/.upcontent/logo.svg +6 -0
  7. package/.upcontent/portal.css +139 -0
  8. package/Makefile +29 -0
  9. package/README.md +114 -2
  10. package/SHOWCASE.mdx +163 -0
  11. package/astro.config.mjs +125 -0
  12. package/customization/config-json.md +92 -0
  13. package/customization/content.md +48 -0
  14. package/customization/environment.md +43 -0
  15. package/customization/index.md +33 -0
  16. package/customization/navigation.md +57 -0
  17. package/customization/site-identity.md +43 -0
  18. package/customization/theme.md +47 -0
  19. package/deployment/github-pages.md +46 -0
  20. package/deployment/index.md +24 -0
  21. package/deployment/static-hosts.md +34 -0
  22. package/getting-started/consumer-repository.md +156 -0
  23. package/getting-started/first-build.md +78 -0
  24. package/guides/authoring-content.md +105 -0
  25. package/guides/validate-your-site.md +53 -0
  26. package/package.json +33 -3
  27. package/scripts/upcontent-cli.mjs +113 -0
  28. package/scripts/verify-external-build.mjs +24 -0
  29. package/src/components/MermaidLoader.astro +290 -0
  30. package/src/components/PaletteShowcase.astro +122 -0
  31. package/src/components/StructuredDataCopy.astro +22 -0
  32. package/src/content/__mocks__/astro-content.ts +7 -0
  33. package/src/content/__mocks__/astro-loaders.ts +3 -0
  34. package/src/content.config.test.ts +163 -0
  35. package/src/content.config.ts +90 -0
  36. package/src/lib/content-blocklist.test.ts +47 -0
  37. package/src/lib/content-blocklist.ts +55 -0
  38. package/src/lib/doc-links.test.ts +62 -0
  39. package/src/lib/doc-links.ts +31 -0
  40. package/src/lib/mermaid-render.test.ts +42 -0
  41. package/src/lib/mermaid-render.ts +24 -0
  42. package/src/lib/portal-config.test.ts +143 -0
  43. package/src/lib/portal-config.ts +176 -0
  44. package/src/lib/product-identity.ts +2 -0
  45. package/src/lib/rehype-callouts.test.ts +69 -0
  46. package/src/lib/rehype-callouts.ts +61 -0
  47. package/src/lib/remark-strip-duplicate-title.test.ts +73 -0
  48. package/src/lib/remark-strip-duplicate-title.ts +37 -0
  49. package/src/lib/remark-structured-data-preview.test.ts +104 -0
  50. package/src/lib/remark-structured-data-preview.ts +66 -0
  51. package/src/lib/remark-wiki-links.test.ts +102 -0
  52. package/src/lib/remark-wiki-links.ts +110 -0
  53. package/src/lib/sidebar.test.ts +142 -0
  54. package/src/lib/sidebar.ts +113 -0
  55. package/src/lib/structured-data-tree.test.ts +75 -0
  56. package/src/lib/structured-data-tree.ts +55 -0
  57. package/src/overrides/Footer.astro +114 -0
  58. package/src/overrides/Head.astro +20 -0
  59. package/src/pages/index.astro +20 -0
  60. package/src/styles/callouts.css +29 -0
  61. package/src/styles/structured-data-preview.css +95 -0
  62. package/test-fixtures/external-consumer/.upcontent/config.json +26 -0
  63. package/test-fixtures/external-consumer/.upcontent/favicon.svg +4 -0
  64. package/test-fixtures/external-consumer/.upcontent/logo.svg +4 -0
  65. package/test-fixtures/external-consumer/.upcontent/theme.css +4 -0
  66. package/test-fixtures/external-consumer/.upcontent-renderer/README.md +3 -0
  67. package/test-fixtures/external-consumer/README.md +6 -0
  68. package/test-fixtures/external-consumer/forbidden.md +5 -0
  69. package/tsconfig.json +7 -0
  70. package/vitest.config.ts +18 -0
@@ -0,0 +1,61 @@
1
+ name: Validate portal
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: validate-${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ validate:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - name: Check out repository
19
+ uses: actions/checkout@v5
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Set up pnpm
24
+ uses: pnpm/action-setup@v4
25
+ with:
26
+ version: 10.20.0
27
+
28
+ - name: Set up Node.js
29
+ uses: actions/setup-node@v5
30
+ with:
31
+ node-version: 22
32
+ cache: pnpm
33
+
34
+ - name: Install dependencies
35
+ run: pnpm install --frozen-lockfile
36
+
37
+ - name: Run tests
38
+ run: pnpm test
39
+
40
+ - name: Run Astro diagnostics
41
+ run: pnpm check
42
+
43
+ - name: Build golden consumer
44
+ run: make build CONTENT_PATH=.
45
+
46
+ - name: Check golden artifact
47
+ run: |
48
+ test -f dist/index.html
49
+ test -f dist/pagefind/pagefind.js
50
+ test -f dist/upcontent-assets/favicon.svg
51
+
52
+ - name: Build external consumer fixture
53
+ run: make check-external
54
+
55
+ - name: Check diff hygiene
56
+ env:
57
+ BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
58
+ run: |
59
+ git diff --check "$BASE_SHA" HEAD
60
+ git diff --exit-code
61
+ test -z "$(git status --porcelain --untracked-files=all)"
@@ -0,0 +1,59 @@
1
+ name: Deploy documentation portal
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Check out repository
22
+ uses: actions/checkout@v5
23
+
24
+ - name: Set up Pages
25
+ id: pages
26
+ uses: actions/configure-pages@v5
27
+
28
+ - name: Set up pnpm
29
+ uses: pnpm/action-setup@v4
30
+ with:
31
+ version: 10.20.0
32
+
33
+ - name: Set up Node.js
34
+ uses: actions/setup-node@v5
35
+ with:
36
+ node-version: 22
37
+ cache: pnpm
38
+
39
+ - name: Install dependencies
40
+ run: pnpm install --frozen-lockfile
41
+
42
+ - name: Build static portal
43
+ run: make build CONTENT_PATH=. REPO_URL="https://github.com/${{ github.repository }}" BASE_PATH="${{ steps.pages.outputs.base_path }}" SITE_URL="${{ steps.pages.outputs.origin }}"
44
+
45
+ - name: Upload static artifact
46
+ uses: actions/upload-pages-artifact@v4
47
+ with:
48
+ path: dist
49
+
50
+ deploy:
51
+ environment:
52
+ name: github-pages
53
+ url: ${{ steps.deployment.outputs.page_url }}
54
+ runs-on: ubuntu-latest
55
+ needs: build
56
+ steps:
57
+ - name: Deploy to GitHub Pages
58
+ id: deployment
59
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,74 @@
1
+ name: Reusable Upcontent Pages build
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ portal-repository:
7
+ description: Repository containing the Upcontent renderer.
8
+ required: false
9
+ type: string
10
+ default: lumamontes/upcontent
11
+ portal-ref:
12
+ description: Tag, branch, or commit of the Upcontent renderer.
13
+ required: false
14
+ type: string
15
+ default: main
16
+
17
+ permissions:
18
+ contents: read
19
+ pages: write
20
+ id-token: write
21
+
22
+ jobs:
23
+ build:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - name: Check out consumer repository
27
+ uses: actions/checkout@v5
28
+
29
+ - name: Check out Upcontent renderer
30
+ uses: actions/checkout@v5
31
+ with:
32
+ repository: ${{ inputs.portal-repository }}
33
+ ref: ${{ inputs.portal-ref }}
34
+ path: .upcontent-renderer
35
+
36
+ - name: Set up Pages
37
+ id: pages
38
+ uses: actions/configure-pages@v5
39
+
40
+ - name: Set up pnpm
41
+ uses: pnpm/action-setup@v4
42
+ with:
43
+ version: 10.20.0
44
+
45
+ - name: Set up Node.js
46
+ uses: actions/setup-node@v5
47
+ with:
48
+ node-version: 22
49
+ cache: pnpm
50
+ cache-dependency-path: .upcontent-renderer/pnpm-lock.yaml
51
+
52
+ - name: Install renderer dependencies
53
+ working-directory: .upcontent-renderer
54
+ run: pnpm install --frozen-lockfile
55
+
56
+ - name: Build portal
57
+ working-directory: .upcontent-renderer
58
+ run: make build CONTENT_PATH="${{ github.workspace }}" REPO_URL="https://github.com/${{ github.repository }}" BASE_PATH="${{ steps.pages.outputs.base_path }}" SITE_URL="${{ steps.pages.outputs.origin }}"
59
+
60
+ - name: Upload static artifact
61
+ uses: actions/upload-pages-artifact@v4
62
+ with:
63
+ path: .upcontent-renderer/dist
64
+
65
+ deploy:
66
+ environment:
67
+ name: github-pages
68
+ url: ${{ steps.deployment.outputs.page_url }}
69
+ runs-on: ubuntu-latest
70
+ needs: build
71
+ steps:
72
+ - name: Deploy to GitHub Pages
73
+ id: deployment
74
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,82 @@
1
+ {
2
+ "site": {
3
+ "title": "Upcontent",
4
+ "description": "Create a beautiful, customizable Starlight portal from your documentation repository.",
5
+ "url": "https://lumamontes.github.io/upcontent",
6
+ "logo": {
7
+ "src": ".upcontent/logo.svg",
8
+ "alt": "",
9
+ "replacesTitle": false
10
+ },
11
+ "favicon": ".upcontent/favicon.svg"
12
+ },
13
+ "repo": {
14
+ "url": "https://github.com/lumamontes/upcontent"
15
+ },
16
+ "theme": {
17
+ "customCss": [".upcontent/portal.css"]
18
+ },
19
+ "starlight": {
20
+ "social": [
21
+ {
22
+ "icon": "github",
23
+ "label": "GitHub",
24
+ "href": "https://github.com/lumamontes/upcontent"
25
+ }
26
+ ],
27
+ "tableOfContents": {
28
+ "minHeadingLevel": 2,
29
+ "maxHeadingLevel": 3
30
+ },
31
+ "lastUpdated": true,
32
+ "pagination": true,
33
+ "expressiveCode": {
34
+ "styleOverrides": {
35
+ "borderRadius": "0.6rem"
36
+ }
37
+ }
38
+ },
39
+ "navigation": {
40
+ "roots": [
41
+ "README.md",
42
+ "getting-started",
43
+ "guides",
44
+ "customization",
45
+ "deployment",
46
+ "SHOWCASE.mdx"
47
+ ],
48
+ "blocklist": {
49
+ "prefixes": [
50
+ ".git/",
51
+ ".scratch/",
52
+ ".agents/",
53
+ ".claude/",
54
+ ".cursor/",
55
+ ".codex/",
56
+ ".opencode/",
57
+ "node_modules/",
58
+ "dist/",
59
+ ".astro/",
60
+ "src/",
61
+ "server/",
62
+ "docs/agents/",
63
+ "docs/",
64
+ "test-fixtures/",
65
+ "external-consumer/",
66
+ "workflows/"
67
+ ],
68
+ "exact": [
69
+ "AGENTS.md",
70
+ "CLAUDE.md",
71
+ "CONTEXT.md",
72
+ "PRODUCT.md"
73
+ ]
74
+ },
75
+ "labelOverrides": {
76
+ "docs": "Project Docs"
77
+ }
78
+ },
79
+ "content": {
80
+ "titleField": "heading"
81
+ }
82
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="16" fill="#0f766e"/>
3
+ <path d="M18 20h28v8H18zm0 16h21v8H18z" fill="#fff"/>
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72" role="img" aria-labelledby="title desc">
2
+ <title id="title">Upcontent</title>
3
+ <desc id="desc">Upcontent mark.</desc>
4
+ <rect width="56" height="56" x="8" y="8" rx="16" fill="#0f766e"/>
5
+ <path d="M20 24h32v8H20zm0 16h24v8H20z" fill="#fff"/>
6
+ </svg>
@@ -0,0 +1,139 @@
1
+ @import '@fontsource/poppins/400.css';
2
+ @import '@fontsource/poppins/500.css';
3
+ @import '@fontsource/poppins/600.css';
4
+ @import '@fontsource/poppins/700.css';
5
+
6
+ /* The consumer owns this identity and can replace it without changing the renderer. */
7
+ :root {
8
+ --sl-color-accent: #0f766e;
9
+ --sl-color-accent-high: #115e59;
10
+ --sl-color-accent-low: #ccfbf1;
11
+ --sl-color-text-accent: #0f766e;
12
+ --sl-color-bg: #f8faf7;
13
+ --sl-color-bg-nav: #f8faf7;
14
+ --sl-color-bg-sidebar: #eef4f1;
15
+ --sl-color-bg-inline-code: #e3eeea;
16
+ --sl-color-hairline-light: #d8e5df;
17
+ --sl-font: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
18
+ --sl-font-mono: 'SFMono-Regular', Consolas, monospace;
19
+ --sl-text-5xl: 3.35rem;
20
+ --sl-content-width: 48rem;
21
+ --sl-line-height: 1.72;
22
+ --up-sidebar-active: rgba(15, 118, 110, 0.08);
23
+ --up-sidebar-hover: rgba(15, 118, 110, 0.07);
24
+ }
25
+
26
+ :root[data-theme='light'] {
27
+ --sl-color-accent: #0f766e;
28
+ --sl-color-accent-high: #115e59;
29
+ --sl-color-accent-low: #ccfbf1;
30
+ --sl-color-text-accent: #0f766e;
31
+ --sl-color-bg: #f8faf7;
32
+ --sl-color-bg-nav: #f8faf7;
33
+ --sl-color-bg-sidebar: #eef4f1;
34
+ --sl-color-bg-inline-code: #e3eeea;
35
+ --sl-color-hairline-light: #d8e5df;
36
+ --up-sidebar-active: rgba(15, 118, 110, 0.08);
37
+ --up-sidebar-hover: rgba(15, 118, 110, 0.07);
38
+ }
39
+
40
+ :root[data-theme='dark'] {
41
+ --sl-color-accent: #5eead4;
42
+ --sl-color-accent-high: #99f6e4;
43
+ --sl-color-accent-low: #134e4a;
44
+ --sl-color-text-accent: #99f6e4;
45
+ --sl-color-bg: #0d191a;
46
+ --sl-color-bg-nav: #102324;
47
+ --sl-color-bg-sidebar: #0f2021;
48
+ --sl-color-bg-inline-code: #173334;
49
+ --sl-color-hairline-light: #274445;
50
+ --up-sidebar-active: rgba(94, 234, 212, 0.09);
51
+ --up-sidebar-hover: rgba(94, 234, 212, 0.08);
52
+ }
53
+
54
+ .site-title {
55
+ font-size: 1.15rem;
56
+ font-weight: 600;
57
+ letter-spacing: -0.025em;
58
+ }
59
+
60
+ .sl-container > h1,
61
+ .sl-markdown-content h1 {
62
+ font-weight: 600;
63
+ letter-spacing: -0.035em;
64
+ }
65
+
66
+ .sl-markdown-content h2 {
67
+ margin-top: 2.6em;
68
+ padding-top: 0.7em;
69
+ border-top: 1px solid var(--sl-color-hairline-light);
70
+ letter-spacing: -0.035em;
71
+ }
72
+
73
+ .sl-markdown-content :not(pre) > code {
74
+ border: 1px solid var(--sl-color-hairline-light);
75
+ border-radius: 0.25rem;
76
+ padding: 0.12em 0.35em;
77
+ }
78
+
79
+ .sl-markdown-content blockquote {
80
+ border-inline-start-color: var(--sl-color-accent);
81
+ }
82
+
83
+ .header {
84
+ border-bottom: 1px solid var(--sl-color-hairline-light);
85
+ }
86
+
87
+ .sl-markdown-content a:not(.sl-link-button) {
88
+ text-decoration-thickness: 0.08em;
89
+ text-underline-offset: 0.18em;
90
+ }
91
+
92
+ .sidebar-content .top-level > li + li {
93
+ margin-top: 0.3rem;
94
+ }
95
+
96
+ .sidebar-content .top-level > li > details > summary {
97
+ color: var(--sl-color-text);
98
+ font-weight: 600;
99
+ letter-spacing: 0.005em;
100
+ padding-block: 0.35rem;
101
+ }
102
+
103
+ .sidebar-content .top-level > li > details > summary .caret {
104
+ color: var(--sl-color-gray-3);
105
+ }
106
+
107
+ .sidebar-content ul ul {
108
+ margin-top: 0.2rem;
109
+ }
110
+
111
+ .sidebar-content ul ul li {
112
+ border-inline-start-color: var(--sl-color-hairline-light);
113
+ }
114
+
115
+ .sidebar-content a {
116
+ border-radius: 0.45rem;
117
+ padding-block: 0.42rem;
118
+ transition: background-color 140ms ease, color 140ms ease;
119
+ }
120
+
121
+ .sidebar-content a:hover,
122
+ .sidebar-content a:focus-visible {
123
+ background: var(--up-sidebar-hover);
124
+ color: var(--sl-color-text);
125
+ }
126
+
127
+ .sidebar-content a[aria-current='page'],
128
+ .sidebar-content a[aria-current='page']:hover,
129
+ .sidebar-content a[aria-current='page']:focus-visible {
130
+ background: var(--up-sidebar-active);
131
+ color: var(--sl-color-text-accent);
132
+ font-weight: 600;
133
+ }
134
+
135
+ @media (min-width: 50em) {
136
+ .sl-markdown-content h1 {
137
+ max-width: 15ch;
138
+ }
139
+ }
package/Makefile ADDED
@@ -0,0 +1,29 @@
1
+ CONTENT_PATH ?= $(HOME)/www/my-content-repo
2
+ REPO_URL ?=
3
+ BASE_PATH ?=
4
+ SITE_URL ?=
5
+
6
+ .PHONY: dev build preview check-external
7
+
8
+ define prepare-content
9
+ @test -d "$(CONTENT_PATH)" || (printf 'Content path does not exist: %s\n' "$(CONTENT_PATH)" >&2; exit 1)
10
+ @rm -f src/content/docs
11
+ @ln -s "$(abspath $(CONTENT_PATH))" src/content/docs
12
+ endef
13
+
14
+ dev:
15
+ $(prepare-content)
16
+ REPO_URL=$(REPO_URL) BASE_PATH=$(BASE_PATH) SITE_URL=$(SITE_URL) pnpm exec astro dev
17
+
18
+ build:
19
+ $(prepare-content)
20
+ REPO_URL=$(REPO_URL) BASE_PATH=$(BASE_PATH) SITE_URL=$(SITE_URL) pnpm exec astro build
21
+
22
+ preview:
23
+ $(MAKE) build CONTENT_PATH="$(CONTENT_PATH)" REPO_URL="$(REPO_URL)" BASE_PATH="$(BASE_PATH)" SITE_URL="$(SITE_URL)"
24
+ pnpm exec astro preview
25
+
26
+ check-external:
27
+ $(MAKE) build CONTENT_PATH="$(CURDIR)/test-fixtures/external-consumer" REPO_URL="https://github.com/example/external-consumer"
28
+ @test -f dist/readme/index.html
29
+ node scripts/verify-external-build.mjs
package/README.md CHANGED
@@ -1,3 +1,115 @@
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
+ Upcontent turns a Markdown repository into a documentation site that is ready to share.
7
+
8
+ It is for software teams that already have documentation, but need a better way to publish it: clear navigation, search, useful rendering for technical content, consumer-owned branding, and a repeatable static build.
9
+
10
+ ## The short version
11
+
12
+ Upcontent gives a documentation repository:
13
+
14
+ - A responsive [Starlight](https://starlight.astro.build/) portal with navigation, search, themes, and table of contents
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
20
+
21
+ The source repository remains the source of truth. Upcontent does not require a database, a companion server, or a new authoring system.
22
+
23
+ ## Is this the right tool?
24
+
25
+ Upcontent is a good fit when:
26
+
27
+ - Your documentation already lives in Markdown or MDX.
28
+ - Your team wants to keep writing in Git.
29
+ - You want the portal to be owned and deployed by the documentation repository.
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.
32
+
33
+ It is not the right fit when your site needs runtime authentication, server-rendered personalization, or review comments inside the published portal.
34
+
35
+ ## Get started
36
+
37
+ ### Publish an existing documentation repository
38
+
39
+ If your Markdown already lives in a GitHub repository, run the bootstrap command from that repository's root:
40
+
41
+ ```sh
42
+ pnpm dlx upcontent init
43
+ pnpm dlx upcontent dev
44
+ ```
45
+
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.
47
+
48
+ See [Set up a consumer repository](getting-started/consumer-repository/) for the generated structure and configuration.
49
+
50
+ ### Explore this repository locally
51
+
52
+ Clone this repository, then start the included golden consumer. This path is for exploring Upcontent itself:
53
+
54
+ ```sh
55
+ git clone https://github.com/lumamontes/upcontent.git
56
+ cd upcontent
57
+ pnpm install
58
+ make dev CONTENT_PATH=.
59
+ ```
60
+
61
+ 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
+
63
+ The generated consumer configuration looks like this:
64
+
65
+ ```json
66
+ {
67
+ "site": {
68
+ "title": "Engineering Docs",
69
+ "description": "Documentation for the engineering team.",
70
+ "logo": {
71
+ "src": ".upcontent/logo.svg",
72
+ "alt": "Engineering Docs"
73
+ },
74
+ "favicon": ".upcontent/favicon.svg"
75
+ },
76
+ "repo": {
77
+ "url": "https://github.com/acme/engineering-docs"
78
+ },
79
+ "theme": {
80
+ "customCss": [".upcontent/theme.css"]
81
+ }
82
+ }
83
+ ```
84
+
85
+ Follow [Set up a consumer repository](getting-started/consumer-repository/) for the complete setup, then use [Customization](customization/) to shape the portal.
86
+
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
+ ## Build for production
97
+
98
+ ```sh
99
+ make build CONTENT_PATH=/path/to/your-consumer-repo
100
+ ```
101
+
102
+ The generated site is written to `dist/` and includes the Pagefind search index.
103
+
104
+ Before publishing, run:
105
+
106
+ ```sh
107
+ pnpm test
108
+ pnpm check
109
+ make build CONTENT_PATH=.
110
+ make check-external
111
+ ```
112
+
113
+ ## Built on Astro and Starlight
114
+
115
+ 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.