diff --git a/.gitignore b/.gitignore index 0d315a04..3f5eb39e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ purestack-setup .codex/* /.tmp* /frontend/out/ +/frontend/purestack.studio/legal/_private/ diff --git a/frontend/README.md b/frontend/README.md index 79a29730..7d63a5fc 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -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 | diff --git a/frontend/ensureLegalContent.test.ts b/frontend/ensureLegalContent.test.ts new file mode 100644 index 00000000..a0a8d599 --- /dev/null +++ b/frontend/ensureLegalContent.test.ts @@ -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) { + 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( + ``, + { contentDir, sourceRelPath: `legal/${page}.mdx` }, + ) + expect(content).toContain('Content required.') + expect(content).toContain(`legal/_private/${page}.mdx`) + expect(content).toContain('Existing private legal content.

'])( + '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']) + }) + }) +}) diff --git a/frontend/ensureLegalContent.ts b/frontend/ensureLegalContent.ts new file mode 100644 index 00000000..198716f7 --- /dev/null +++ b/frontend/ensureLegalContent.ts @@ -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 = `
+

${title}

+

Content required. Add your own ${title} before publishing.

+

Edit legal/_private/${fileName} in the site's content folder. This file is ignored by Git.

+
+` + 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 + } + } +} diff --git a/frontend/legal-content.md b/frontend/legal-content.md new file mode 100644 index 00000000..60931404 --- /dev/null +++ b/frontend/legal-content.md @@ -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. diff --git a/frontend/purestack.studio/components/footer.mdx b/frontend/purestack.studio/components/footer.mdx index 53a52660..09d5e877 100644 --- a/frontend/purestack.studio/components/footer.mdx +++ b/frontend/purestack.studio/components/footer.mdx @@ -4,8 +4,22 @@ tone="neutral" variant="none" > + diff --git a/frontend/purestack.studio/footer.mdx b/frontend/purestack.studio/footer.mdx index 9e05d6b3..a2ec58c2 100644 --- a/frontend/purestack.studio/footer.mdx +++ b/frontend/purestack.studio/footer.mdx @@ -7,17 +7,26 @@