@haruhimemoe/next-kit 0.8.0 → 0.10.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.
- package/CHANGELOG.md +16 -1
- package/README.md +13 -1
- package/dist/check/standards.d.ts +4 -4
- package/dist/check/standards.js +6 -6
- package/dist/legal/blocks.d.ts +62 -0
- package/dist/legal/blocks.js +43 -0
- package/dist/legal/entries.d.ts +22 -0
- package/dist/legal/entries.js +45 -0
- package/dist/legal/index.d.ts +12 -0
- package/dist/legal/index.js +12 -0
- package/dist/legal/types.d.ts +42 -0
- package/dist/legal/types.js +16 -0
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.10.0] - 2026-10-05
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- **Breaking.** `next-kit check`'s legal standard requires all five pages of the legal convention: `content/legal/{terms,privacy,your-privacy-rights,copyright,disclaimers}.mdx` (was terms and privacy only).
|
|
13
|
+
|
|
14
|
+
## [0.9.0] - 2026-10-05
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `legal` entry point: the five-page legal convention every service ships (terms, privacy, your-privacy-rights, copyright, disclaimers). A `LegalSite` config (site name, operator, contact email, effective date, data stores, processors, cookies), seven plain server-safe blocks an app drops into its own legal MDX (`LegalContact`, `DataWeKeep`, `Processors`, `YourRights`, `DmcaNotice`, `NoWarranty`, `Changes`; no hooks, no `@haruhimemoe/ui`), and `legalEntries(site, pages?)` for the five standard `ContentEntry` records so apps stop hand-writing the same titles and descriptions. next-kit's first `.tsx` entry point. `LegalSite.hosting` adds an optional line to `DmcaNotice`; `Changes` takes an optional per-page `date`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- The `@haruhimemoe/ui` peer range also allows `^0.18.0`.
|
|
21
|
+
|
|
9
22
|
## [0.8.0] - 2026-10-05
|
|
10
23
|
|
|
11
24
|
### Changed
|
|
@@ -96,7 +109,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
96
109
|
- `@haruhimemoe/next-kit/auth-react`: `createSignedInMarker`, `createAccountStore`, `useAccount`, `createAccount`, `RestoreSignedIn` and `osuSignIn`.
|
|
97
110
|
- `@haruhimemoe/next-kit/testing`: `startMemoryMongo`, `setupTestDb`, `setupMsw` and the fake osu! app env.
|
|
98
111
|
|
|
99
|
-
[unreleased]: https://github.com/haruhimemoe/next-kit/compare/v0.
|
|
112
|
+
[unreleased]: https://github.com/haruhimemoe/next-kit/compare/v0.10.0...HEAD
|
|
113
|
+
[0.10.0]: https://github.com/haruhimemoe/next-kit/compare/v0.9.0...v0.10.0
|
|
114
|
+
[0.9.0]: https://github.com/haruhimemoe/next-kit/compare/v0.8.0...v0.9.0
|
|
100
115
|
[0.8.0]: https://github.com/haruhimemoe/next-kit/compare/v0.7.0...v0.8.0
|
|
101
116
|
[0.6.2]: https://github.com/haruhimemoe/next-kit/compare/v0.6.1...v0.6.2
|
|
102
117
|
[0.6.1]: https://github.com/haruhimemoe/next-kit/compare/v0.6.0...v0.6.1
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ The Next.js server plumbing the haruhime.moe tools share. [packs.haruhime.moe](h
|
|
|
13
13
|
- **`/testing`:** Vitest helpers: one in-memory MongoDB per run, an msw server that refuses unhandled requests, and a fake env.
|
|
14
14
|
- **`/api-keys`:** the shared key format (an app prefix like `hpk_` plus 32 random bytes), a key store over `api_keys`, and the `/api/v1` guard with the standard limits.
|
|
15
15
|
- **`/docs`:** a content registry for an app's docs, guides and legal pages: sections, entries, app-made extra entries (like bb's tag pages), the path helpers a dynamic route needs, and `mdxToMarkdown` to turn bb-flavored MDX into plain Markdown. No runtime imports. **`/docs/files`:** reads the markdown files a registry's entries point at and reports drift between the registry and disk (node:fs).
|
|
16
|
+
- **`/legal`:** the five-page legal convention (terms, privacy, your-privacy-rights, copyright, disclaimers). A `LegalSite` config, seven plain server-safe blocks (`LegalContact`, `DataWeKeep`, `Processors`, `YourRights`, `DmcaNotice`, `NoWarranty`, `Changes`) an app drops into its own legal MDX, and `legalEntries` for the app's content registry.
|
|
16
17
|
- **`/vcs`:** document history in MongoDB on top of `@haruhimemoe/vcs`: one line of revisions per document, saves merged onto whatever landed since their base, revert, diffs, and autosave pruning.
|
|
17
18
|
|
|
18
19
|
Every name, path, limit and message comes from the caller. There is no root entry point; import a subpath.
|
|
@@ -38,6 +39,7 @@ bun add @haruhimemoe/next-kit zod
|
|
|
38
39
|
| `api-keys` | `mongodb` ^7.6.0 |
|
|
39
40
|
| `docs` | nothing else |
|
|
40
41
|
| `docs/files` | nothing else (`node:fs` is built in) |
|
|
42
|
+
| `legal` | `react` ^19.3.0 (types only, for JSX) |
|
|
41
43
|
| `vcs` | `mongodb` ^7.6.0, `@haruhimemoe/vcs` ^0.1.0 |
|
|
42
44
|
|
|
43
45
|
## Use
|
|
@@ -226,7 +228,7 @@ Every app is checked against:
|
|
|
226
228
|
|
|
227
229
|
- **crawl** (always): `robots.ts`, `sitemap.ts`, `llms.txt/route.ts`, `llms-full.txt/route.ts`, and `.well-known/security.txt/route.ts` (each also accepted as a route handler, like `robots.txt/route.ts`).
|
|
228
230
|
- **brand** (always): `brand/page.tsx`.
|
|
229
|
-
- **legal** (always): `legal/page.tsx`, `legal/[x]/page.tsx`, `legal/[x]/md/route.ts` (any dynamic segment name), and
|
|
231
|
+
- **legal** (always): `legal/page.tsx`, `legal/[x]/page.tsx`, `legal/[x]/md/route.ts` (any dynamic segment name), and the five legal convention pages: `content/legal/{terms,privacy,your-privacy-rights,copyright,disclaimers}.mdx`.
|
|
230
232
|
- **docs**, once `src/app/api/v1` exists or any `content/docs` file does: `docs/page.tsx`, `docs/[x]/page.tsx`, `docs/[x]/md/route.ts`.
|
|
231
233
|
- **guides**, only once a `content/guides` file exists: the same three files under `guides/`. An app with no guides is never asked for them.
|
|
232
234
|
- **api**, once `src/app/api/v1` exists: `api/v1/me/route.ts`, `api/v1/openapi.json/route.ts`, `api/me/api-key/route.ts`, and `content/docs/api.mdx`.
|
|
@@ -393,6 +395,16 @@ No runtime imports.
|
|
|
393
395
|
| `readContentMarkdown(content, section, slug, { root?, siteUrl, transforms? })` | Reads a registered entry's markdown file and converts it with `mdxToMarkdown` (using the entry's `title`). `root` defaults to `process.cwd()`. Returns null for an unregistered slug; rejects (ENOENT) when the slug is registered but its file is missing. |
|
|
394
396
|
| `contentFileDrift(content, { root? })` | `{ missingFiles, unregistered }`: `missingFiles` lists registered entries with no file on disk (like `"guides/x.mdx"`); `unregistered` lists `.mdx` files on disk with no registry entry. `root` defaults to `process.cwd()`. |
|
|
395
397
|
|
|
398
|
+
### legal
|
|
399
|
+
|
|
400
|
+
| Export | What it does |
|
|
401
|
+
| --- | --- |
|
|
402
|
+
| `LEGAL_SLUGS`, `LegalSlug` | The five standard legal page slugs, in order: `"terms"`, `"privacy"`, `"your-privacy-rights"`, `"copyright"`, `"disclaimers"`. |
|
|
403
|
+
| `LegalSite` | The config every block and `legalEntries` renders from: `siteName`, `operator`, `contactEmail`, `effectiveDate` (`YYYY-MM-DD`), `stores` (`LegalDataStore[]`), `processors` (`LegalProcessor[]`), `cookies` (`string[]`), optional `hosting` (one sentence on what users can post, shown by `DmcaNotice`). |
|
|
404
|
+
| `LegalDataStore`, `LegalProcessor` | One kind of data kept (`what`, `why`), and one third party that processes it (`name`, `purpose`, `link?`). |
|
|
405
|
+
| `LegalContact`, `DataWeKeep`, `Processors`, `YourRights`, `DmcaNotice`, `NoWarranty`, `Changes` | The seven blocks. Each takes `{ site: LegalSite }` and renders plain semantic HTML (no `@haruhimemoe/ui`), so it inherits the app's MDX prose styling. `DataWeKeep` skips the cookies list when `site.cookies` is empty. `Processors` links a processor that has a `link`, and plain-texts one that doesn't. `DmcaNotice` prints `site.hosting` only when set. `Changes` also takes an optional `date` for a page updated on its own day. |
|
|
406
|
+
| `legalEntries(site, pages?)` | The five `ContentEntry` records (`docs`'s registry shape) for the legal convention, with the default title and description (`site.siteName` filled in) and `lastUpdated` set to `site.effectiveDate`. `pages` overrides any field per slug; everything else keeps the default. |
|
|
407
|
+
|
|
396
408
|
### vcs
|
|
397
409
|
|
|
398
410
|
| Export | What it does |
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* @file src/check/standards.ts
|
|
3
3
|
* @desc The routes and content files every haruhime app serves, checked against an app's route
|
|
4
4
|
* file list (paths under src/app) and content file list (paths under content/). Crawl and
|
|
5
|
-
* brand are always checked; legal always checks its three routes plus
|
|
6
|
-
*
|
|
7
|
-
* only once a content/guides file does; the API standard, once api/v1 exists, checks its
|
|
5
|
+
* brand are always checked; legal always checks its three routes plus the five pages of
|
|
6
|
+
* the legal convention (LEGAL_SLUGS); docs joins in once api/v1 exists or a content/docs
|
|
7
|
+
* file does; guides joins in only once a content/guides file does; the API standard, once api/v1 exists, checks its
|
|
8
8
|
* three routes plus content/docs/api.mdx. Checks files only: the content registry itself
|
|
9
9
|
* is never parsed.
|
|
10
10
|
* @author David @dvhsh (https://dvh.sh)
|
|
11
11
|
* @created Sat Oct 3, 2026
|
|
12
|
-
* @modified
|
|
12
|
+
* @modified Mon Oct 5, 2026
|
|
13
13
|
*/
|
|
14
14
|
/** One standard's outcome. `missing` entries carry their own prefix (src/app/... or content/...). */
|
|
15
15
|
export type StandardResult = {
|
package/dist/check/standards.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
* @file src/check/standards.ts
|
|
3
3
|
* @desc The routes and content files every haruhime app serves, checked against an app's route
|
|
4
4
|
* file list (paths under src/app) and content file list (paths under content/). Crawl and
|
|
5
|
-
* brand are always checked; legal always checks its three routes plus
|
|
6
|
-
*
|
|
7
|
-
* only once a content/guides file does; the API standard, once api/v1 exists, checks its
|
|
5
|
+
* brand are always checked; legal always checks its three routes plus the five pages of
|
|
6
|
+
* the legal convention (LEGAL_SLUGS); docs joins in once api/v1 exists or a content/docs
|
|
7
|
+
* file does; guides joins in only once a content/guides file does; the API standard, once api/v1 exists, checks its
|
|
8
8
|
* three routes plus content/docs/api.mdx. Checks files only: the content registry itself
|
|
9
9
|
* is never parsed.
|
|
10
10
|
* @author David @dvhsh (https://dvh.sh)
|
|
11
11
|
* @created Sat Oct 3, 2026
|
|
12
|
-
* @modified
|
|
12
|
+
* @modified Mon Oct 5, 2026
|
|
13
13
|
*/
|
|
14
|
+
import { LEGAL_SLUGS } from "../legal/types.js";
|
|
14
15
|
/** A route under src/app, matched against any of `patterns`. */
|
|
15
16
|
const route = (name, ...patterns) => ({
|
|
16
17
|
missing: `src/app/${name}`,
|
|
@@ -37,8 +38,7 @@ const sectionRoutes = (section) => [
|
|
|
37
38
|
];
|
|
38
39
|
const LEGAL = [
|
|
39
40
|
...sectionRoutes("legal"),
|
|
40
|
-
content(
|
|
41
|
-
content("legal/privacy.mdx"),
|
|
41
|
+
...LEGAL_SLUGS.map((slug) => content(`legal/${slug}.mdx`)),
|
|
42
42
|
];
|
|
43
43
|
const DOCS = sectionRoutes("docs");
|
|
44
44
|
const GUIDES = sectionRoutes("guides");
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/blocks.tsx
|
|
3
|
+
* @desc The seven legal blocks: plain server-safe React (no hooks) an app drops into its own
|
|
4
|
+
* legal MDX pages, rendered from a `LegalSite` config instead of hand-written boilerplate.
|
|
5
|
+
* Semantic HTML only (section, h2, p, ul, a), so it inherits the app's MDX prose styling.
|
|
6
|
+
* No `@haruhimemoe/ui` dependency: these are plain text blocks, not UI components.
|
|
7
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
8
|
+
* @created Mon Oct 5, 2026
|
|
9
|
+
* @modified Mon Oct 5, 2026
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactElement } from "react";
|
|
12
|
+
import type { LegalSite } from "./types.js";
|
|
13
|
+
/** Every block's props: the site config it renders from. */
|
|
14
|
+
export type LegalBlockProps = {
|
|
15
|
+
site: LegalSite;
|
|
16
|
+
};
|
|
17
|
+
/** `Changes` props: the site config, plus this page's own date when it differs from the site's. */
|
|
18
|
+
export type ChangesProps = LegalBlockProps & {
|
|
19
|
+
date?: string | undefined;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @function LegalContact
|
|
23
|
+
* @param props {LegalBlockProps} the site config
|
|
24
|
+
* @returns {ReactElement} who runs the site and the address for legal questions
|
|
25
|
+
*/
|
|
26
|
+
export declare const LegalContact: ({ site }: LegalBlockProps) => ReactElement;
|
|
27
|
+
/**
|
|
28
|
+
* @function DataWeKeep
|
|
29
|
+
* @param props {LegalBlockProps} the site config
|
|
30
|
+
* @returns {ReactElement} what the app stores and why, plus the cookies it sets
|
|
31
|
+
*/
|
|
32
|
+
export declare const DataWeKeep: ({ site }: LegalBlockProps) => ReactElement;
|
|
33
|
+
/**
|
|
34
|
+
* @function Processors
|
|
35
|
+
* @param props {LegalBlockProps} the site config
|
|
36
|
+
* @returns {ReactElement} the third parties that process data on the app's behalf
|
|
37
|
+
*/
|
|
38
|
+
export declare const Processors: ({ site }: LegalBlockProps) => ReactElement;
|
|
39
|
+
/**
|
|
40
|
+
* @function YourRights
|
|
41
|
+
* @param props {LegalBlockProps} the site config
|
|
42
|
+
* @returns {ReactElement} the GDPR and CCPA rights every visitor has, and how to use them
|
|
43
|
+
*/
|
|
44
|
+
export declare const YourRights: ({ site }: LegalBlockProps) => ReactElement;
|
|
45
|
+
/**
|
|
46
|
+
* @function DmcaNotice
|
|
47
|
+
* @param props {LegalBlockProps} the site config
|
|
48
|
+
* @returns {ReactElement} the DMCA agent, and the notice and counter-notice steps
|
|
49
|
+
*/
|
|
50
|
+
export declare const DmcaNotice: ({ site }: LegalBlockProps) => ReactElement;
|
|
51
|
+
/**
|
|
52
|
+
* @function NoWarranty
|
|
53
|
+
* @param props {LegalBlockProps} the site config
|
|
54
|
+
* @returns {ReactElement} the "as is" warranty disclaimer and limitation of liability
|
|
55
|
+
*/
|
|
56
|
+
export declare const NoWarranty: ({ site }: LegalBlockProps) => ReactElement;
|
|
57
|
+
/**
|
|
58
|
+
* @function Changes
|
|
59
|
+
* @param props {ChangesProps} the site config and an optional per-page date
|
|
60
|
+
* @returns {ReactElement} the standard "we may update this page" line with its effective date
|
|
61
|
+
*/
|
|
62
|
+
export declare const Changes: ({ site, date }: ChangesProps) => ReactElement;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* @function LegalContact
|
|
4
|
+
* @param props {LegalBlockProps} the site config
|
|
5
|
+
* @returns {ReactElement} who runs the site and the address for legal questions
|
|
6
|
+
*/
|
|
7
|
+
export const LegalContact = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Contact" }), _jsxs("p", { children: [site.operator, " runs ", site.siteName, ". For anything on this page, write to", " ", _jsx("a", { href: `mailto:${site.contactEmail}`, children: site.contactEmail }), "."] })] }));
|
|
8
|
+
/**
|
|
9
|
+
* @function DataWeKeep
|
|
10
|
+
* @param props {LegalBlockProps} the site config
|
|
11
|
+
* @returns {ReactElement} what the app stores and why, plus the cookies it sets
|
|
12
|
+
*/
|
|
13
|
+
export const DataWeKeep = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "What we store" }), _jsx("ul", { children: site.stores.map((store) => (_jsxs("li", { children: [_jsxs("strong", { children: [store.what, "."] }), " ", store.why] }, store.what))) }), site.cookies.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h3", { children: "Cookies" }), _jsx("ul", { children: site.cookies.map((cookie) => (_jsx("li", { children: cookie }, cookie))) })] }))] }));
|
|
14
|
+
/**
|
|
15
|
+
* @function Processors
|
|
16
|
+
* @param props {LegalBlockProps} the site config
|
|
17
|
+
* @returns {ReactElement} the third parties that process data on the app's behalf
|
|
18
|
+
*/
|
|
19
|
+
export const Processors = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Service providers" }), _jsx("ul", { children: site.processors.map((processor) => (_jsxs("li", { children: [_jsx("strong", { children: processor.link ? _jsx("a", { href: processor.link, children: processor.name }) : processor.name }), " ", processor.purpose] }, processor.name))) })] }));
|
|
20
|
+
/**
|
|
21
|
+
* @function YourRights
|
|
22
|
+
* @param props {LegalBlockProps} the site config
|
|
23
|
+
* @returns {ReactElement} the GDPR and CCPA rights every visitor has, and how to use them
|
|
24
|
+
*/
|
|
25
|
+
export const YourRights = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Your rights under the GDPR" }), _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Access and portability." }), " Get a copy of your data in a machine-readable file."] }), _jsxs("li", { children: [_jsx("strong", { children: "Rectification." }), " Have wrong data corrected."] }), _jsxs("li", { children: [_jsx("strong", { children: "Erasure." }), " Have your data deleted."] }), _jsxs("li", { children: [_jsx("strong", { children: "Restriction." }), " Ask us to pause using your data while a question about it is sorted out."] }), _jsxs("li", { children: [_jsx("strong", { children: "Objection." }), " Object to anything we do on the basis of legitimate interest."] }), _jsxs("li", { children: [_jsx("strong", { children: "Complaint." }), " Complain to your data protection supervisory authority."] })] }), _jsx("h2", { children: "Your rights under the CCPA" }), _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("strong", { children: "Know, delete and correct." }), " Ask what we hold about you, ask us to delete it, and ask us to correct it."] }), _jsxs("li", { children: [_jsx("strong", { children: "Selling and sharing." }), " We don't sell or share personal information."] }), _jsxs("li", { children: [_jsx("strong", { children: "Opting out." }), " We honor Global Privacy Control signals."] })] }), _jsxs("p", { children: ["To use any of these rights, email", " ", _jsx("a", { href: `mailto:${site.contactEmail}`, children: site.contactEmail }), "."] })] }));
|
|
26
|
+
/**
|
|
27
|
+
* @function DmcaNotice
|
|
28
|
+
* @param props {LegalBlockProps} the site config
|
|
29
|
+
* @returns {ReactElement} the DMCA agent, and the notice and counter-notice steps
|
|
30
|
+
*/
|
|
31
|
+
export const DmcaNotice = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Copyright and DMCA" }), site.hosting ? _jsx("p", { children: site.hosting }) : null, _jsxs("p", { children: ["Our designated agent for copyright notices is", " ", _jsx("a", { href: `mailto:${site.contactEmail}`, children: site.contactEmail }), "."] }), _jsx("h3", { children: "A takedown notice should include" }), _jsxs("ol", { children: [_jsx("li", { children: "your contact information;" }), _jsx("li", { children: "the copyrighted work you believe is infringed;" }), _jsx("li", { children: "the URL or page the material appears on;" }), _jsx("li", { children: "a good-faith statement that the use is not authorized;" }), _jsx("li", { children: "a sworn statement that you're the rights holder, or authorized to act for them;" }), _jsx("li", { children: "your physical or electronic signature." })] }), _jsx("h3", { children: "A counter-notice should include" }), _jsxs("ol", { children: [_jsx("li", { children: "your contact information;" }), _jsx("li", { children: "the material removed and where it appeared;" }), _jsx("li", { children: "a sworn, good-faith statement that it was removed by mistake or misidentification;" }), _jsxs("li", { children: ["your consent to the jurisdiction of your local courts, or ", site.operator, "'s;"] }), _jsx("li", { children: "your physical or electronic signature." })] })] }));
|
|
32
|
+
/**
|
|
33
|
+
* @function NoWarranty
|
|
34
|
+
* @param props {LegalBlockProps} the site config
|
|
35
|
+
* @returns {ReactElement} the "as is" warranty disclaimer and limitation of liability
|
|
36
|
+
*/
|
|
37
|
+
export const NoWarranty = ({ site }) => (_jsxs("section", { children: [_jsx("h2", { children: "Disclaimer of warranties" }), _jsxs("p", { children: [site.siteName, " is provided \"as is\" and \"as available\", without warranties of any kind, express or implied, including merchantability, fitness for a particular purpose and non-infringement."] }), _jsx("h2", { children: "Limitation of liability" }), _jsxs("p", { children: ["To the fullest extent the law allows, ", site.operator, " is not liable for any indirect, incidental, special, consequential or punitive damages, or for any loss of data or accounts, arising from your use of ", site.siteName, "."] })] }));
|
|
38
|
+
/**
|
|
39
|
+
* @function Changes
|
|
40
|
+
* @param props {ChangesProps} the site config and an optional per-page date
|
|
41
|
+
* @returns {ReactElement} the standard "we may update this page" line with its effective date
|
|
42
|
+
*/
|
|
43
|
+
export const Changes = ({ site, date }) => (_jsxs("section", { children: [_jsx("h2", { children: "Changes" }), _jsxs("p", { children: ["We may update this page. It was last updated on ", date ?? site.effectiveDate, "."] })] }));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/entries.ts
|
|
3
|
+
* @desc legalEntries: the five standard `ContentEntry` records (`docs`'s registry shape) for the
|
|
4
|
+
* five-page legal convention, so apps stop hand-writing the same titles and descriptions.
|
|
5
|
+
* An app overrides any field per slug through `pages`; unlisted fields keep the default.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
import type { ContentEntry } from "../docs/registry.js";
|
|
11
|
+
import { type LegalSite, type LegalSlug } from "./types.js";
|
|
12
|
+
/** Per-slug overrides for any `ContentEntry` field, keyed by `LegalSlug`. */
|
|
13
|
+
export type LegalPageOverrides = Partial<Record<LegalSlug, Partial<ContentEntry>>>;
|
|
14
|
+
/**
|
|
15
|
+
* @function legalEntries
|
|
16
|
+
* @param site {LegalSite} the site config; `siteName` fills the default descriptions
|
|
17
|
+
* @param pages {LegalPageOverrides} [pages] per-slug overrides (a different description, a
|
|
18
|
+
* `navTitle`, a page-specific `lastUpdated`); omitted fields keep the default
|
|
19
|
+
* @returns {ContentEntry[]} the five entries, in `LEGAL_SLUGS` order, ready for the `legal` array
|
|
20
|
+
* an app's content registry (`defineContent`) takes
|
|
21
|
+
*/
|
|
22
|
+
export declare const legalEntries: (site: LegalSite, pages?: LegalPageOverrides) => ContentEntry[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/entries.ts
|
|
3
|
+
* @desc legalEntries: the five standard `ContentEntry` records (`docs`'s registry shape) for the
|
|
4
|
+
* five-page legal convention, so apps stop hand-writing the same titles and descriptions.
|
|
5
|
+
* An app overrides any field per slug through `pages`; unlisted fields keep the default.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
import { LEGAL_SLUGS } from "./types.js";
|
|
11
|
+
/** The default title and description for each of the five legal slugs. */
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
terms: { title: "Terms of Service", description: "The rules for using {site}." },
|
|
14
|
+
privacy: { title: "Privacy Policy", description: "What {site} stores and why." },
|
|
15
|
+
"your-privacy-rights": {
|
|
16
|
+
title: "GDPR & CCPA",
|
|
17
|
+
description: "Your rights over your data under the GDPR and the CCPA, and how to use them.",
|
|
18
|
+
},
|
|
19
|
+
copyright: {
|
|
20
|
+
title: "Copyright & Takedown",
|
|
21
|
+
description: "How to report a copyright concern, and where to send a DMCA notice.",
|
|
22
|
+
},
|
|
23
|
+
disclaimers: {
|
|
24
|
+
title: "Disclaimers",
|
|
25
|
+
description: "Who {site} isn't affiliated with, and what it doesn't promise.",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* @function legalEntries
|
|
30
|
+
* @param site {LegalSite} the site config; `siteName` fills the default descriptions
|
|
31
|
+
* @param pages {LegalPageOverrides} [pages] per-slug overrides (a different description, a
|
|
32
|
+
* `navTitle`, a page-specific `lastUpdated`); omitted fields keep the default
|
|
33
|
+
* @returns {ContentEntry[]} the five entries, in `LEGAL_SLUGS` order, ready for the `legal` array
|
|
34
|
+
* an app's content registry (`defineContent`) takes
|
|
35
|
+
*/
|
|
36
|
+
export const legalEntries = (site, pages) => LEGAL_SLUGS.map((slug) => {
|
|
37
|
+
const fallback = DEFAULTS[slug];
|
|
38
|
+
return {
|
|
39
|
+
slug,
|
|
40
|
+
title: fallback.title,
|
|
41
|
+
description: fallback.description.replace("{site}", site.siteName),
|
|
42
|
+
lastUpdated: site.effectiveDate,
|
|
43
|
+
...pages?.[slug],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/index.ts
|
|
3
|
+
* @desc @haruhimemoe/next-kit/legal: the five-page legal convention (terms, privacy,
|
|
4
|
+
* your-privacy-rights, copyright, disclaimers). `LegalSite` config, seven MDX blocks
|
|
5
|
+
* rendered from it, and `legalEntries` for the app's content registry.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
export { Changes, type ChangesProps, DataWeKeep, DmcaNotice, type LegalBlockProps, LegalContact, NoWarranty, Processors, YourRights, } from "./blocks.js";
|
|
11
|
+
export { type LegalPageOverrides, legalEntries } from "./entries.js";
|
|
12
|
+
export { LEGAL_SLUGS, type LegalDataStore, type LegalProcessor, type LegalSite, type LegalSlug, } from "./types.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/index.ts
|
|
3
|
+
* @desc @haruhimemoe/next-kit/legal: the five-page legal convention (terms, privacy,
|
|
4
|
+
* your-privacy-rights, copyright, disclaimers). `LegalSite` config, seven MDX blocks
|
|
5
|
+
* rendered from it, and `legalEntries` for the app's content registry.
|
|
6
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
7
|
+
* @created Mon Oct 5, 2026
|
|
8
|
+
* @modified Mon Oct 5, 2026
|
|
9
|
+
*/
|
|
10
|
+
export { Changes, DataWeKeep, DmcaNotice, LegalContact, NoWarranty, Processors, YourRights, } from "./blocks.js";
|
|
11
|
+
export { legalEntries } from "./entries.js";
|
|
12
|
+
export { LEGAL_SLUGS, } from "./types.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/types.ts
|
|
3
|
+
* @desc LegalSite: the per-app config the legal blocks and legalEntries render from. No site
|
|
4
|
+
* name, URL, email or data list is hardcoded here; every app passes its own.
|
|
5
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
6
|
+
* @created Mon Oct 5, 2026
|
|
7
|
+
* @modified Mon Oct 5, 2026
|
|
8
|
+
*/
|
|
9
|
+
/** A third party that processes data on the app's behalf, like Vercel or MongoDB Atlas. */
|
|
10
|
+
export type LegalProcessor = {
|
|
11
|
+
name: string;
|
|
12
|
+
purpose: string;
|
|
13
|
+
link?: string;
|
|
14
|
+
};
|
|
15
|
+
/** One kind of data the app stores, and why. */
|
|
16
|
+
export type LegalDataStore = {
|
|
17
|
+
what: string;
|
|
18
|
+
why: string;
|
|
19
|
+
};
|
|
20
|
+
/** The five standard legal page slugs every service ships, in the order they're listed. */
|
|
21
|
+
export declare const LEGAL_SLUGS: readonly ["terms", "privacy", "your-privacy-rights", "copyright", "disclaimers"];
|
|
22
|
+
/** One of `LEGAL_SLUGS`. */
|
|
23
|
+
export type LegalSlug = (typeof LEGAL_SLUGS)[number];
|
|
24
|
+
/** The config every legal block and `legalEntries` renders from. */
|
|
25
|
+
export type LegalSite = {
|
|
26
|
+
/** The site's display name, like "packs.haruhime.moe". */
|
|
27
|
+
siteName: string;
|
|
28
|
+
/** Who runs the site, for the contact and "who's responsible" lines. */
|
|
29
|
+
operator: string;
|
|
30
|
+
/** Where a legal request, question or notice goes. */
|
|
31
|
+
contactEmail: string;
|
|
32
|
+
/** YYYY-MM-DD. Shown as this page's "last updated" date by `Changes`. */
|
|
33
|
+
effectiveDate: string;
|
|
34
|
+
/** What the app keeps, in `DataWeKeep`'s order. */
|
|
35
|
+
stores: readonly LegalDataStore[];
|
|
36
|
+
/** Who processes data on the app's behalf, in `Processors`'s order. */
|
|
37
|
+
processors: readonly LegalProcessor[];
|
|
38
|
+
/** The cookies the app sets, one line each, in `DataWeKeep`'s cookie list. */
|
|
39
|
+
cookies: readonly string[];
|
|
40
|
+
/** One sentence on what users can post or upload, shown by `DmcaNotice`. Omitted: no line. */
|
|
41
|
+
hosting?: string | undefined;
|
|
42
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file src/legal/types.ts
|
|
3
|
+
* @desc LegalSite: the per-app config the legal blocks and legalEntries render from. No site
|
|
4
|
+
* name, URL, email or data list is hardcoded here; every app passes its own.
|
|
5
|
+
* @author David @dvhsh (https://dvh.sh)
|
|
6
|
+
* @created Mon Oct 5, 2026
|
|
7
|
+
* @modified Mon Oct 5, 2026
|
|
8
|
+
*/
|
|
9
|
+
/** The five standard legal page slugs every service ships, in the order they're listed. */
|
|
10
|
+
export const LEGAL_SLUGS = [
|
|
11
|
+
"terms",
|
|
12
|
+
"privacy",
|
|
13
|
+
"your-privacy-rights",
|
|
14
|
+
"copyright",
|
|
15
|
+
"disclaimers",
|
|
16
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haruhimemoe/next-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "The Next.js plumbing the haruhime.moe tools share: JSON route helpers, rate limits and budgets in MongoDB, bearer machine auth, zod env parsing, a connect-once MongoDB client with safe index builds, better-auth with osu! sign-in, the signed-in marker and account store for the browser, SEO: metadata, robots.txt, sitemaps, JSON-LD and llms.txt, and per-app API keys and the /api/v1 guard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
"types": "./dist/docs/files/index.d.ts",
|
|
71
71
|
"default": "./dist/docs/files/index.js"
|
|
72
72
|
},
|
|
73
|
+
"./legal": {
|
|
74
|
+
"types": "./dist/legal/index.d.ts",
|
|
75
|
+
"default": "./dist/legal/index.js"
|
|
76
|
+
},
|
|
73
77
|
"./vcs": {
|
|
74
78
|
"types": "./dist/vcs/index.d.ts",
|
|
75
79
|
"default": "./dist/vcs/index.js"
|
|
@@ -104,7 +108,7 @@
|
|
|
104
108
|
},
|
|
105
109
|
"peerDependencies": {
|
|
106
110
|
"@haruhimemoe/osu": "^0.2.0 || ^0.3.0 || ^0.4.0",
|
|
107
|
-
"@haruhimemoe/ui": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0",
|
|
111
|
+
"@haruhimemoe/ui": "^0.14.0 || ^0.15.0 || ^0.16.0 || ^0.17.0 || ^0.18.0",
|
|
108
112
|
"@haruhimemoe/vcs": "^0.1.0",
|
|
109
113
|
"better-auth": "^1.7.5",
|
|
110
114
|
"mongodb": "^7.6.0",
|