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,46 @@
1
+ ---
2
+ title: GitHub Pages
3
+ description: Publish a consumer repository through the reference GitHub Actions workflow.
4
+ sidebar:
5
+ order: 2
6
+ ---
7
+
8
+ The reusable Upcontent workflow builds the consumer repository and uploads `dist/` to GitHub Pages. Run `pnpm dlx upcontent init` in the consumer repository to generate the caller workflow.
9
+
10
+ ## Enable Pages
11
+
12
+ 1. Open the repository's **Settings > Pages**.
13
+ 2. Set the source to **GitHub Actions**.
14
+ 3. Confirm the workflow has `contents: read`, `pages: write`, and `id-token: write` permissions.
15
+ 4. Push to `main` or run the deployment workflow manually.
16
+
17
+ ## Configure the site URL
18
+
19
+ A project site normally needs a base path:
20
+
21
+ ```yaml
22
+ env:
23
+ BASE_PATH: /engineering-docs
24
+ SITE_URL: https://acme.github.io/engineering-docs
25
+ ```
26
+
27
+ A custom domain normally uses an empty base path:
28
+
29
+ ```yaml
30
+ env:
31
+ BASE_PATH: ''
32
+ SITE_URL: https://docs.acme.com
33
+ ```
34
+
35
+ Read [Environment variables](../customization/environment/) before changing these values.
36
+
37
+ ## Verify the published site
38
+
39
+ After deployment, open the published URL and check:
40
+
41
+ - The header logo and favicon load.
42
+ - Internal links include the correct base path.
43
+ - Search returns results.
44
+ - Light and dark themes work.
45
+ - Mermaid, callouts, and structured previews render.
46
+ - The browser console has no required-asset errors.
@@ -0,0 +1,24 @@
1
+ ---
2
+ title: Deployment
3
+ description: Publish the generated static portal to GitHub Pages or another static host.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ Upcontent produces static files. The deployment job only needs to build the content and upload `dist/` to a host that serves HTML, CSS, JavaScript, and assets.
9
+
10
+ ## Choose a path
11
+
12
+ - [GitHub Pages](github-pages/): use the included workflow and repository settings.
13
+ - [Other static hosts](static-hosts/): publish `dist/` to Netlify, Vercel, S3, or another file host.
14
+ - [Environment variables](../customization/environment/): set the URL and base path for the host.
15
+
16
+ ## Before publishing
17
+
18
+ ```sh
19
+ pnpm test
20
+ pnpm check
21
+ make build CONTENT_PATH=/path/to/your-consumer-repo
22
+ ```
23
+
24
+ Then confirm that `dist/` contains the generated pages and Pagefind assets.
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Other static hosts
3
+ description: Publish dist to a static hosting provider of your choice.
4
+ sidebar:
5
+ order: 3
6
+ ---
7
+
8
+ The output of `make build` is portable. Upload the contents of `dist/` to any host that serves static files.
9
+
10
+ ## Generic flow
11
+
12
+ ```sh
13
+ make build CONTENT_PATH=/path/to/your-consumer-repo
14
+ upload dist/ to your hosting provider
15
+ ```
16
+
17
+ Set `SITE_URL` when the host supports canonical URLs or sitemap generation. Set `BASE_PATH` when the site is served below the domain root.
18
+
19
+ ## Separate portal and content repositories
20
+
21
+ A CI job can check out both repositories before building:
22
+
23
+ ```text
24
+ checkout the portal repository
25
+ checkout the consumer repository
26
+ make build CONTENT_PATH=/workspace/consumer
27
+ upload dist/
28
+ ```
29
+
30
+ The consumer checkout provides Markdown, `.upcontent/config.json`, assets, and CSS. The portal checkout provides the renderer and validation pipeline.
31
+
32
+ ## Privacy
33
+
34
+ A private source repository does not make a public static host private. Use a host with access controls when the published site must be restricted.
@@ -0,0 +1,156 @@
1
+ ---
2
+ title: Set up a consumer repository
3
+ description: Prepare a documentation repository to be rendered by Upcontent.
4
+ sidebar:
5
+ order: 2
6
+ ---
7
+
8
+ A consumer repository is the source of truth for a published portal. It contains the documentation and the `.upcontent/` directory that describes the consumer's identity and presentation.
9
+
10
+ Upcontent itself is also a GitHub repository. This project uses the same consumer contract described here: the repository root is the golden consumer, while `src/` contains the renderer.
11
+
12
+ ## This site as an example
13
+
14
+ The repository behind this site keeps the renderer and its golden consumer together:
15
+
16
+ ```text
17
+ upcontent/
18
+ ├── .upcontent/
19
+ │ ├── config.json
20
+ │ ├── favicon.svg
21
+ │ ├── logo.svg
22
+ │ └── portal.css
23
+ ├── README.md
24
+ ├── getting-started/
25
+ ├── guides/
26
+ ├── customization/
27
+ ├── deployment/
28
+ ├── SHOWCASE.mdx
29
+ ├── package.json
30
+ └── .github/workflows/
31
+ ```
32
+
33
+ The public pages are Markdown or MDX files in the repository root. The renderer implementation lives separately under `src/`; it is not part of the published sidebar. A separate consumer repository only needs its own documentation and `.upcontent/` directory.
34
+
35
+ This site's actual `.upcontent/config.json` uses the same fields available to consumers:
36
+
37
+ ```json
38
+ {
39
+ "site": {
40
+ "title": "Upcontent",
41
+ "description": "Create a beautiful, customizable Starlight portal from your documentation repository.",
42
+ "url": "https://lumamontes.github.io/upcontent",
43
+ "logo": {
44
+ "src": ".upcontent/logo.svg",
45
+ "alt": "",
46
+ "replacesTitle": false
47
+ },
48
+ "favicon": ".upcontent/favicon.svg"
49
+ },
50
+ "repo": {
51
+ "url": "https://github.com/lumamontes/upcontent"
52
+ },
53
+ "theme": {
54
+ "customCss": [".upcontent/portal.css"]
55
+ },
56
+ "starlight": {
57
+ "lastUpdated": true,
58
+ "pagination": true
59
+ }
60
+ }
61
+ ```
62
+
63
+ The filename is `.upcontent/config.json`; `portal.json` is not part of the current contract.
64
+
65
+ ## Recommended structure
66
+
67
+ ```text
68
+ my-consumer-repo/
69
+ ├── .upcontent/
70
+ │ ├── config.json
71
+ │ ├── favicon.svg
72
+ │ ├── logo.svg
73
+ │ └── theme.css
74
+ ├── getting-started/
75
+ │ └── first-page.md
76
+ ├── guides/
77
+ │ └── operating-the-system.md
78
+ └── README.md
79
+ ```
80
+
81
+ The structure is a recommendation, not a requirement. Existing repositories can keep their current folders and use `navigation.roots` and `navigation.labelOverrides` to shape the sidebar.
82
+
83
+ ## Add the Upcontent configuration
84
+
85
+ Create `.upcontent/config.json`:
86
+
87
+ ```json
88
+ {
89
+ "site": {
90
+ "title": "Engineering Docs",
91
+ "description": "Documentation for the engineering team.",
92
+ "logo": {
93
+ "src": ".upcontent/logo.svg",
94
+ "alt": "Engineering Docs"
95
+ },
96
+ "favicon": ".upcontent/favicon.svg"
97
+ },
98
+ "repo": {
99
+ "url": "https://github.com/acme/engineering-docs"
100
+ },
101
+ "theme": {
102
+ "customCss": [".upcontent/theme.css"]
103
+ }
104
+ }
105
+ ```
106
+
107
+ Relative asset paths resolve from the consumer repository. Absolute URLs are also supported for externally hosted assets.
108
+
109
+ See the [capability showcase](../../showcase/) and [configuration reference](../../customization/config-json/) for the supported options.
110
+
111
+ ## Bootstrap with the CLI
112
+
113
+ From the consumer repository root:
114
+
115
+ ```sh
116
+ pnpm dlx upcontent init
117
+ ```
118
+
119
+ This creates the `.upcontent/` directory and a GitHub Pages workflow. The command preserves existing files and asks you to use `--force` before replacing generated files.
120
+
121
+ Preview the repository locally before publishing:
122
+
123
+ ```sh
124
+ pnpm dlx upcontent dev
125
+ ```
126
+
127
+ ## Choose the content root
128
+
129
+ Use the consumer repository itself:
130
+
131
+ ```sh
132
+ make dev CONTENT_PATH=/path/to/my-consumer-repo
133
+ ```
134
+
135
+ For separate portal and content repositories, check out both repositories in the workflow and pass the content checkout as `CONTENT_PATH`.
136
+
137
+ ## Keep internal material private from navigation
138
+
139
+ The portal always excludes its protected internal paths. Add consumer-specific paths with `navigation.blocklist`:
140
+
141
+ ```json
142
+ {
143
+ "navigation": {
144
+ "blocklist": {
145
+ "exact": ["notes.md"],
146
+ "prefixes": ["drafts/", "internal/"]
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ Blocklisted files are excluded before content parsing. They do not become pages or sidebar links.
153
+
154
+ :::caution
155
+ Blocklisting is a build-time content boundary, not an access-control system. Do not publish secrets or rely on a public static host to protect a private document.
156
+ :::
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: Your first build
3
+ description: Run Upcontent locally and understand what it generates.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ This guide takes you from a cloned Upcontent repository to a local documentation portal. If you have not cloned it yet:
9
+
10
+ ```sh
11
+ git clone https://github.com/lumamontes/upcontent.git
12
+ cd upcontent
13
+ ```
14
+
15
+ ## What you need
16
+
17
+ - Node.js supported by the repository toolchain
18
+ - pnpm
19
+ - A documentation repository containing Markdown or MDX files
20
+
21
+ Install dependencies from the portal repository:
22
+
23
+ ```sh
24
+ pnpm install
25
+ ```
26
+
27
+ ## Start the portal locally
28
+
29
+ Point `CONTENT_PATH` at the documentation repository you want to preview:
30
+
31
+ ```sh
32
+ make dev CONTENT_PATH=.
33
+ ```
34
+
35
+ Open the local URL printed by Astro. Changes to content and consumer configuration are reflected by the development server.
36
+
37
+ ## Build the static site
38
+
39
+ Build the same consumer without a development server:
40
+
41
+ ```sh
42
+ make build CONTENT_PATH=.
43
+ ```
44
+
45
+ The generated site is written to `dist/`. It contains HTML, assets, and the Pagefind search index. Preview the generated output with:
46
+
47
+ ```sh
48
+ pnpm exec astro preview
49
+ ```
50
+
51
+ ## Explore the example
52
+
53
+ Start with these pages:
54
+
55
+ - [Capability showcase](../../showcase/) shows rendering, content, customization, and workflows in one place.
56
+ - [Authoring content](../../guides/authoring-content/) explains the writing conventions used here.
57
+ - [Validate your site](../../guides/validate-your-site/) is the repeatable acceptance checklist.
58
+
59
+ ## Use another consumer repository
60
+
61
+ The portal can render content from a different directory without copying it into this repository:
62
+
63
+ ```sh
64
+ make dev CONTENT_PATH=/path/to/my-consumer-repo
65
+ ```
66
+
67
+ The consumer repository owns its Markdown, `.upcontent/config.json`, assets, and custom CSS. The portal repository owns the renderer and validation pipeline.
68
+
69
+ :::tip[The important boundary]
70
+ Treat `CONTENT_PATH` as the product boundary. If a change only works when files are added to the portal implementation, it is not yet a consumer customization.
71
+ :::
72
+
73
+ ## Next steps
74
+
75
+ 1. Read [Set up a consumer repository](../consumer-repository/).
76
+ 2. Add or review the consumer configuration in `.upcontent/config.json`.
77
+ 3. Follow [Authoring content](../../guides/authoring-content/) before adding new pages.
78
+ 4. Run [Validate your site](../../guides/validate-your-site/).
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: Authoring content
3
+ description: Write clear, navigable Markdown for a static Starlight portal.
4
+ sidebar:
5
+ order: 1
6
+ ---
7
+
8
+ Good documentation is task-oriented, explicit about its audience, and easy to scan. This guide describes the conventions used in a clear technical documentation site.
9
+
10
+ ## Start with the reader's task
11
+
12
+ Prefer a page title that completes the sentence "I want to...":
13
+
14
+ - Good: `Configure a consumer repository`
15
+ - Weak: `Configuration`
16
+
17
+ Open with one or two sentences that explain the outcome. Put prerequisites before the first action. Keep each section focused on one decision or step.
18
+
19
+ ## Use frontmatter deliberately
20
+
21
+ Every page can declare metadata at the top of the file:
22
+
23
+ ```md
24
+ ---
25
+ title: Configure a consumer repository
26
+ sidebar:
27
+ order: 2
28
+ ---
29
+ ```
30
+
31
+ The title becomes the page heading and the default sidebar label. The description is used for page metadata and search context. Use `sidebar.order` only when ordering carries meaning; otherwise let the portal sort pages consistently.
32
+
33
+ ## Structure headings for scanning
34
+
35
+ The page title is the top-level heading generated by Starlight. Start body sections at `##`:
36
+
37
+ ```md
38
+ ## Before you begin
39
+
40
+ ## Configure the repository
41
+
42
+ ### Add the portal configuration
43
+
44
+ ### Verify the result
45
+ ```
46
+
47
+ Short sections, descriptive headings, and lists help readers find the answer without reading every paragraph.
48
+
49
+ ## Show commands in context
50
+
51
+ Use a language tag and explain what the reader should observe:
52
+
53
+ ```sh
54
+ make build CONTENT_PATH=.
55
+ ```
56
+
57
+ Do not show commands without saying where to run them, what they change, or how to verify the result. Use placeholders for values that readers must replace:
58
+
59
+ ```sh
60
+ make dev CONTENT_PATH=/path/to/your-consumer-repo
61
+ ```
62
+
63
+ ## Link to stable concepts
64
+
65
+ Use normal Markdown links for known pages. Use wiki links when the source repository needs filename-oriented references:
66
+
67
+ ```md
68
+ [Validate your site](../guides/validate-your-site/)
69
+ [Capability showcase](../showcase/)
70
+ ```
71
+
72
+ Wiki links must resolve to a document. The build fails when a target is missing, so a typo cannot silently become published broken navigation.
73
+
74
+ ## Use callouts for decisions
75
+
76
+ Callouts should contain short, consequential information:
77
+
78
+ :::tip[Prefer the smallest change]
79
+ Start with consumer configuration and CSS tokens before adding implementation code.
80
+ :::
81
+
82
+ :::caution
83
+ Static output is public wherever the selected host is public. Repository privacy does not automatically make the published portal private.
84
+ :::
85
+
86
+ Avoid turning every paragraph into a callout. If everything is highlighted, nothing is prioritized.
87
+
88
+ ## Make examples executable
89
+
90
+ The showcase uses real examples for Mermaid, JSON, YAML, CSV, callouts, and palette switching. Prefer examples that can be built and inspected over decorative snippets that are never exercised.
91
+
92
+ When an example depends on a file, keep that file in the repository and include the validation command in the surrounding text.
93
+
94
+ ## Content review checklist
95
+
96
+ Before publishing a page, check:
97
+
98
+ - The title describes the reader's outcome.
99
+ - The first paragraph explains why the page exists.
100
+ - Prerequisites appear before actions.
101
+ - Headings begin at `##` in the body.
102
+ - Commands include their working directory and expected result.
103
+ - Links use stable page paths and wiki links resolve.
104
+ - Warnings explain a real risk rather than adding visual noise.
105
+ - The page was built and inspected at desktop and mobile widths.
@@ -0,0 +1,53 @@
1
+ ---
2
+ title: Validate your site
3
+ description: Check content, configuration, generated output, and browser behavior before publishing.
4
+ sidebar:
5
+ order: 2
6
+ ---
7
+
8
+ Use this checklist before opening a pull request or publishing a consumer repository.
9
+
10
+ ## Run the build contract
11
+
12
+ ```sh
13
+ pnpm test
14
+ pnpm check
15
+ make build CONTENT_PATH=.
16
+ make check-external
17
+ git diff --check
18
+ ```
19
+
20
+ The tests cover content loading, title fallback, navigation, blocklists, wiki links, callouts, Mermaid transformation, and structured previews. The external build confirms that a separate consumer repository works too.
21
+
22
+ ## Check the browser
23
+
24
+ Start the portal locally:
25
+
26
+ ```sh
27
+ make dev CONTENT_PATH=.
28
+ ```
29
+
30
+ Open the pages listed in the [capability showcase](../showcase/) and check:
31
+
32
+ - The logo appears once and the site name is clear.
33
+ - The sidebar groups are understandable and the active page is easy to find.
34
+ - Search, internal links, and the table of contents work.
35
+ - Light and dark themes remain readable.
36
+ - Callouts, Mermaid, JSON, YAML, and CSV examples render correctly.
37
+ - The layout works at desktop and mobile widths.
38
+ - The browser console has no required-asset errors.
39
+
40
+ ## Check content boundaries
41
+
42
+ The build should not publish:
43
+
44
+ - `.upcontent/` files
45
+ - Protected repository internals
46
+ - Consumer blocklisted paths
47
+ - Documents with malformed or traversal wiki links
48
+
49
+ When a change affects blocklists or navigation roots, verify both the generated route list and the sidebar. Hiding a link is not enough; excluded content must be absent before parsing.
50
+
51
+ ## Record the result
52
+
53
+ If a check fails, record the source page, route, and browser or build output in the relevant engineering ticket. Do not mark a documentation change complete based only on a successful local page load.
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "upcontent",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "bin": {
6
+ "upcontent": "scripts/upcontent-cli.mjs"
7
+ },
8
+ "dependencies": {
9
+ "@astrojs/markdown-remark": "^7.3.1",
10
+ "@astrojs/starlight": "^0.42.1",
11
+ "@fontsource/poppins": "^5.3.0",
12
+ "astro": "^7.3.3",
13
+ "js-yaml": "^4.3.2",
14
+ "mermaid": "^11.0.0",
15
+ "papaparse": "^5.7.0",
16
+ "unist-util-visit": "^5.0.0",
17
+ "zod": "^3.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@astrojs/check": "^0.9.10",
21
+ "@types/js-yaml": "^4.0.9",
22
+ "@types/papaparse": "^5.5.2",
23
+ "rehype-stringify": "^10.0.1",
24
+ "remark-parse": "^11.0.0",
25
+ "remark-rehype": "^11.1.2",
26
+ "typescript": "^5.0.0",
27
+ "unified": "^11.0.5",
28
+ "vitest": "^2.0.0"
29
+ },
30
+ "scripts": {
31
+ "dev": "astro dev",
32
+ "build": "astro build",
33
+ "check": "astro check",
34
+ "test": "vitest run"
35
+ }
6
36
  }
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { spawnSync } from 'node:child_process'
7
+
8
+ const command = process.argv[2] ?? 'help'
9
+ const force = process.argv.includes('--force')
10
+ const root = process.cwd()
11
+ const rendererRoot = dirname(dirname(fileURLToPath(import.meta.url)))
12
+
13
+ const config = `{
14
+ "site": {
15
+ "title": "Documentation",
16
+ "description": "Project documentation.",
17
+ "logo": {
18
+ "src": ".upcontent/logo.svg",
19
+ "alt": "Documentation"
20
+ },
21
+ "favicon": ".upcontent/favicon.svg"
22
+ },
23
+ "repo": {
24
+ "url": "https://github.com/ORG/REPOSITORY"
25
+ },
26
+ "theme": {
27
+ "customCss": [".upcontent/theme.css"]
28
+ }
29
+ }
30
+ `
31
+
32
+ const theme = `:root {
33
+ --sl-color-accent: #0f766e;
34
+ --sl-color-accent-high: #115e59;
35
+ }
36
+ `
37
+
38
+ const logo = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72">
39
+ <rect width="56" height="56" x="8" y="8" rx="16" fill="#0f766e"/>
40
+ <path d="M20 24h32v8H20zm0 16h24v8H20z" fill="#fff"/>
41
+ </svg>
42
+ `
43
+
44
+ const favicon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
45
+ <rect width="64" height="64" rx="16" fill="#0f766e"/>
46
+ <path d="M18 20h28v8H18zm0 16h21v8H18z" fill="#fff"/>
47
+ </svg>
48
+ `
49
+
50
+ const workflow = `name: Publish documentation
51
+
52
+ on:
53
+ push:
54
+ branches: [main]
55
+ workflow_dispatch:
56
+
57
+ jobs:
58
+ publish:
59
+ uses: lumamontes/upcontent/.github/workflows/reusable-pages.yml@main
60
+ permissions:
61
+ contents: read
62
+ pages: write
63
+ id-token: write
64
+ `
65
+
66
+ function init() {
67
+ const files = [
68
+ ['.upcontent/config.json', config],
69
+ ['.upcontent/theme.css', theme],
70
+ ['.upcontent/logo.svg', logo],
71
+ ['.upcontent/favicon.svg', favicon],
72
+ ['.github/workflows/deploy-docs.yml', workflow],
73
+ ]
74
+ const collisions = files.filter(([relativePath]) => existsSync(join(root, relativePath)))
75
+ if (collisions.length > 0 && !force) {
76
+ for (const [relativePath] of collisions) {
77
+ console.error(`Refusing to overwrite ${relativePath}. Use --force to replace generated files.`)
78
+ }
79
+ process.exitCode = 1
80
+ return
81
+ }
82
+ for (const [relativePath, content] of files) {
83
+ const absolutePath = join(root, relativePath)
84
+ mkdirSync(dirname(absolutePath), { recursive: true })
85
+ writeFileSync(absolutePath, content)
86
+ console.log(`created ${relativePath}`)
87
+ }
88
+ console.log('\nNext steps:')
89
+ console.log('1. Replace ORG/REPOSITORY in .upcontent/config.json.')
90
+ console.log('2. Review the generated identity and theme files.')
91
+ console.log('3. Run `pnpm dlx upcontent dev` to preview locally.')
92
+ console.log('4. Push to main to publish through GitHub Actions.')
93
+ }
94
+
95
+ function run(name, args) {
96
+ const result = spawnSync(name, args, { cwd: rendererRoot, stdio: 'inherit' })
97
+ if (result.error) throw result.error
98
+ return result.status === 0
99
+ }
100
+
101
+ if (command === 'init') init()
102
+ else if (command === 'dev') {
103
+ process.exitCode = run('make', ['dev', `CONTENT_PATH=${root}`]) ? 0 : 1
104
+ }
105
+ else if (command === 'check') {
106
+ const passed = run('pnpm', ['test'])
107
+ && run('pnpm', ['check'])
108
+ && run('make', ['build', `CONTENT_PATH=${root}`])
109
+ process.exitCode = passed ? 0 : 1
110
+ }
111
+ else {
112
+ console.log('Usage: upcontent <init|dev|check> [--force]')
113
+ }
@@ -0,0 +1,24 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
2
+
3
+ const html = readFileSync('dist/readme/index.html', 'utf8')
4
+ const required = [
5
+ 'External Consumer',
6
+ 'https://github.com/example/external-consumer',
7
+ '/upcontent-assets/favicon.svg',
8
+ ]
9
+
10
+ for (const value of required) {
11
+ if (!html.includes(value)) throw new Error(`External consumer artifact is missing: ${value}`)
12
+ }
13
+
14
+ if (!existsSync('dist/upcontent-assets/favicon.svg')) throw new Error('External consumer favicon was not copied')
15
+ if (!html.includes('alt="External Consumer"')) throw new Error('External consumer logo was not rendered')
16
+ if (existsSync('dist/forbidden/index.html')) throw new Error('External consumer blocklist leaked forbidden.md')
17
+ if (existsSync('dist/upcontent-renderer/readme/index.html')) {
18
+ throw new Error('External consumer build leaked renderer checkout content')
19
+ }
20
+ if (!existsSync('dist/pagefind/pagefind.js')) throw new Error('External consumer Pagefind output is missing')
21
+
22
+ const cssFiles = readdirSync('dist/_astro').filter(file => file.endsWith('.css'))
23
+ const hasConsumerCss = cssFiles.some(file => readFileSync(`dist/_astro/${file}`, 'utf8').includes('#0f766e'))
24
+ if (!hasConsumerCss) throw new Error('External consumer CSS was not included')