Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ purestack-setup
.codex/*
/.tmp*
/frontend/out/
/frontend/purestack.studio/legal/_private/
6 changes: 6 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ yarn frontend:publish
- Each command runs the `purestack` CLI. It loads `purestack.studio/purestack.config.ts`,
whose plugin registers the `studio` skin, composition components, template, and typed styles.

## Legal pages

Imprint and Privacy Policy content lives in Git-ignored MDX files. Missing files
are created automatically with placeholders; fill them before publishing.
See [Private legal content](legal-content.md) for paths and setup.

## Source map

| File | Purpose |
Expand Down
63 changes: 63 additions & 0 deletions frontend/ensureLegalContent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { expandImports } from '../packages/ts-ssg/src/mdx/imports'
import { ensureLegalContent } from './ensureLegalContent'

async function withContentDir(run: (directory: string) => Promise<void>) {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'purestack-legal-'))
try {
await run(directory)
} finally {
await fs.rm(directory, { recursive: true, force: true })
}
}

describe('private legal content', () => {
it('creates importable placeholders when files are missing', async () => {
await withContentDir(async (contentDir) => {
await ensureLegalContent(contentDir)
for (const page of ['imprint', 'privacy']) {
const content = await expandImports(
`<import-content src="./_private/${page}.mdx"/>`,
{ contentDir, sourceRelPath: `legal/${page}.mdx` },
)
expect(content).toContain('Content required.')
expect(content).toContain(`legal/_private/${page}.mdx`)
expect(content).toContain('<h1')
expect(content).not.toContain('Legal basis:')
}
})
})

it.each(['', '<p>Existing private legal content.</p>'])(
'preserves an existing file and creates only the missing file (%j)',
async (content) => {
await withContentDir(async (contentDir) => {
const directory = path.join(contentDir, 'legal', '_private')
await fs.mkdir(directory, { recursive: true })
const filePath = path.join(directory, 'imprint.mdx')
await fs.writeFile(filePath, content)
await ensureLegalContent(contentDir)
await ensureLegalContent(contentDir)
expect(await fs.readFile(filePath, 'utf8')).toBe(content)
expect(
await fs.readFile(path.join(directory, 'privacy.mdx'), 'utf8'),
).toContain('Content required.')
})
},
)

it('safely handles concurrent initialization', async () => {
await withContentDir(async (contentDir) => {
await Promise.all([
ensureLegalContent(contentDir),
ensureLegalContent(contentDir),
])
expect(
await fs.readdir(path.join(contentDir, 'legal', '_private')),
).toEqual(['imprint.mdx', 'privacy.mdx'])
})
})
})
28 changes: 28 additions & 0 deletions frontend/ensureLegalContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'

/** Create editable starters without overwriting the site's private legal text. */
export async function ensureLegalContent(contentDir: string) {
const directory = path.join(contentDir, 'legal', '_private')
await fs.mkdir(directory, { recursive: true })
for (const fileName of ['imprint.mdx', 'privacy.mdx']) {
const filePath = path.join(directory, fileName)
if (existsSync(filePath)) continue
const title = fileName === 'imprint.mdx' ? 'Imprint' : 'Privacy Policy'
const content = `<article class="container doc-content py-6">
<header class="mb-6"><h1 class="m-0">${title}</h1></header>
<p><strong>Content required.</strong> Add your own ${title} before publishing.</p>
<p>Edit <code>legal/_private/${fileName}</code> in the site's content folder. This file is ignored by Git.</p>
</article>
`
try {
await fs.writeFile(filePath, content, {
encoding: 'utf8',
flag: 'wx', // Create only if missing; never overwrite an existing file.
})
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
}
}
}
33 changes: 33 additions & 0 deletions frontend/legal-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Private legal content

The public page wrappers import the complete legal text from these local files:

- `frontend/purestack.studio/legal/_private/imprint.mdx`
- `frontend/purestack.studio/legal/_private/privacy.mdx`

The `_private` directory is ignored by Git. Files in it are shared content,
so they are included through `import-content` rather than published as separate pages.

Starting the frontend dev server, building, or publishing creates any missing
private file with a minimal notice asking the owner to supply their own content.
No legal-text templates are provided; each site owner is responsible for their
Imprint and Privacy Policy.
Existing files are never overwritten. Edit these files directly; no environment
variables or substitution step is needed.

```sh
yarn frontend
# Or:
yarn frontend:build
yarn frontend:publish
```

Replace the setup notice with your own legal content before publishing.
Use HTML or MDX without frontmatter. The starter supplies the page heading,
spacing, and container; keep or customize that markup as needed.

These files stay out of Git, but their contents appear in the generated public
website. Supply them separately in your deployment environment.

`import-content` reads within the site's content folder. If you keep the originals
outside the repository, copy them into `_private` before building or publishing.
18 changes: 16 additions & 2 deletions frontend/purestack.studio/components/footer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@
tone="neutral"
variant="none"
>
<template #legal>
<a href="/legal/imprint/">Imprint</a>
<a href="/legal/privacy/">Privacy Policy</a>
</template>
<template #social>
<a href="https://github.com/PureStackStudio/PureStack" target="_blank" rel="noopener" aria-label="PureStack GitHub repository"><Icon name="tabler:brand-github"/></a>
<a href="https://x.com/purestackstudio" target="_blank" rel="noopener" aria-label="PureStack Studio on X"><Icon name="tabler:brand-x"/></a>
<a
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
rel="noopener"
aria-label="PureStack GitHub repository"
><Icon name="tabler:brand-github"/></a>
<a
href="https://x.com/purestackstudio"
target="_blank"
rel="noopener"
aria-label="PureStack Studio on X"
><Icon name="tabler:brand-x"/></a>
</template>
</SiteFooter>
37 changes: 29 additions & 8 deletions frontend/purestack.studio/footer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,26 @@
<nav aria-label="Footer resources">
<div>
<span class="footer-label">EXPLORE</span
><a href="#framework">The framework</a
><a href="#ecosystem">The ecosystem</a
><a href="#getting-started">Get started</a>
><a href="/#framework">The framework</a
><a href="/#ecosystem">The ecosystem</a
><a href="/#getting-started">Get started</a>
</div>
<div>
<span class="footer-label">OPEN SOURCE</span
><a href="https://github.com/PureStackStudio/PureStack" target="_blank" rel="noopener">GitHub ↗</a
><a href="https://github.com/PureStackStudio/PureStack/issues" target="_blank" rel="noopener"
><a
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
rel="noopener"
>GitHub ↗</a
><a
href="https://github.com/PureStackStudio/PureStack/issues"
target="_blank"
rel="noopener"
>Issues ↗</a
><a
href="https://github.com/PureStackStudio/PureStack/blob/main/LICENSE" target="_blank" rel="noopener"
href="https://github.com/PureStackStudio/PureStack/blob/main/LICENSE"
target="_blank"
rel="noopener"
>MIT license ↗</a
>
</div>
Expand All @@ -28,9 +37,21 @@
><span class="footer-built"
><span class="status-dot"></span>Made for what comes next.</span
><span class="footer-actions"
><a href="/legal/imprint/">Imprint</a
><a href="/legal/privacy/">Privacy Policy</a
><span class="consent-settings-teleport-area"></span
><a href="https://github.com/PureStackStudio/PureStack" target="_blank" rel="noopener" aria-label="PureStack GitHub repository"><Icon name="tabler:brand-github"/></a
><a href="https://x.com/purestackstudio" target="_blank" rel="noopener" aria-label="PureStack Studio on X"><Icon name="tabler:brand-x"/></a
><a
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
rel="noopener"
aria-label="PureStack GitHub repository"
><Icon name="tabler:brand-github"/></a
><a
href="https://x.com/purestackstudio"
target="_blank"
rel="noopener"
aria-label="PureStack Studio on X"
><Icon name="tabler:brand-x"/></a
><a href="#main">Back to top ↑</a></span
>
</Flex>
Expand Down
18 changes: 16 additions & 2 deletions frontend/purestack.studio/guides/footer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@
tone="neutral"
variant="none"
>
<template #legal>
<a href="/legal/imprint/">Imprint</a>
<a href="/legal/privacy/">Privacy Policy</a>
</template>
<template #social>
<a href="https://github.com/PureStackStudio/PureStack" target="_blank" rel="noopener" aria-label="PureStack GitHub repository"><Icon name="tabler:brand-github"/></a>
<a href="https://x.com/purestackstudio" target="_blank" rel="noopener" aria-label="PureStack Studio on X"><Icon name="tabler:brand-x"/></a>
<a
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
rel="noopener"
aria-label="PureStack GitHub repository"
><Icon name="tabler:brand-github"/></a>
<a
href="https://x.com/purestackstudio"
target="_blank"
rel="noopener"
aria-label="PureStack Studio on X"
><Icon name="tabler:brand-x"/></a>
</template>
</SiteFooter>
12 changes: 6 additions & 6 deletions frontend/purestack.studio/header.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<Flex class="container header-inner" align="center">
<StudioBrand/>
<nav class="desktop-nav" aria-label="Main navigation">
<a href="#framework">Framework</a>
<a href="#ecosystem">Ecosystem</a>
<a href="/#framework">Framework</a>
<a href="/#ecosystem">Ecosystem</a>
<a href="/guides/">Guides</a>
<a href="/components/">Components</a>
<a href="#getting-started">Get started</a>
<a href="/#getting-started">Get started</a>
</nav>
<div class="header-actions">
<div class="topbar__actions">
Expand Down Expand Up @@ -35,11 +35,11 @@
<Icon name="tabler:menu-2"/>
</summary>
<nav aria-label="Mobile navigation">
<a href="#framework">Framework</a>
<a href="#ecosystem">Ecosystem</a>
<a href="/#framework">Framework</a>
<a href="/#ecosystem">Ecosystem</a>
<a href="/guides/">Guides</a>
<a href="/components/">Components</a>
<a href="#getting-started">Get started</a>
<a href="/#getting-started">Get started</a>
<a
href="https://github.com/PureStackStudio/PureStack"
target="_blank"
Expand Down
15 changes: 15 additions & 0 deletions frontend/purestack.studio/legal/imprint.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: Imprint
index: false
description: Contact information for PureStack Studio.
template: studio
layout:
fullWidth: true
showNav: false
showToc: false
showFooter: true
nav:
hidden: true
---

<import-content src="./_private/imprint.mdx"/>
15 changes: 15 additions & 0 deletions frontend/purestack.studio/legal/privacy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: Privacy Policy
index: false
description: How PureStack Studio processes personal data.
template: studio
layout:
fullWidth: true
showNav: false
showToc: false
showFooter: true
nav:
hidden: true
---

<import-content src="./_private/privacy.mdx"/>
11 changes: 2 additions & 9 deletions frontend/purestack.studio/siteConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
},
"consent": {
"enabled": true,
"policyVersion": "2026-09-16",
"policyVersion": "2026-10-07",
"privacyPolicyUrl": "/legal/privacy/",
"categories": [
{
"id": "necessary",
Expand All @@ -59,14 +60,6 @@
],
"services": []
},
"analytics": {
"ga4": {
"enabled": true,
"measurementId": "G-90ZXV1S6NF",
"consentCategory": "necessary",
"serviceId": "necessary"
}
},
"pagefind": {
"enabled": true,
"excludePaths": []
Expand Down
4 changes: 3 additions & 1 deletion frontend/studioPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { registerComponentGuideStyles } from './docs/componentGuide'
import { defineDocumentationComponents } from './docs/docsComponents'
import { previewTemplate } from './docs/previewTemplate'
import { sitePreviewPages } from './docs/sitePreviews'
import { ensureLegalContent } from './ensureLegalContent'
import { studioSkin } from './theme/studioSkin'
import { registerStudioStyles } from './theme/studioStyles'

Expand Down Expand Up @@ -43,7 +44,8 @@ export const studioPlugin = definePlugin({
},
},
hooks: {
onConfigResolved() {
async onConfigResolved({ config }) {
await ensureLegalContent(config.contentDir)
registerStudioStyles()
registerApiReferenceStyles()
registerComponentGuideStyles()
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "purestack-repo",
"version": "1.1.5",
"version": "1.1.6",
"packageManager": "yarn@4.18.1",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/purestack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purestack",
"version": "1.1.5",
"version": "1.1.6",
"packageManager": "yarn@4.9.2",
"module": "./dist/purestack.mjs",
"types": "./dist/purestack.d.mts",
Expand Down
2 changes: 1 addition & 1 deletion packages/purestack/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const version: string = '1.1.5'
export const version: string = '1.1.6'

export * from '@purestack/ts-ssg'
2 changes: 1 addition & 1 deletion packages/ts-common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purestack/ts-common",
"version": "1.1.5",
"version": "1.1.6",
"packageManager": "yarn@4.9.2",
"module": "./dist/ts-common.mjs",
"types": "./dist/ts-common.d.mts",
Expand Down
2 changes: 2 additions & 0 deletions packages/ts-common/src/consent-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface ConsentScript {
}

export interface ConsentService {
/** Set by the built-in GA4 integration to stop collection on consent withdrawal. */
ga4MeasurementId?: string
id: string
category: string
label?: string
Expand Down
Loading
Loading