pattern-dump 0.2.0-alpha.0 → 0.4.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.
@@ -1,6 +1,7 @@
1
1
  import type { ShellBrandConfig, SwatchSlots, ThemeSwitch } from "../contracts/index.js";
2
2
  export type { ShellBrandConfig, SwatchSlots, ThemeSwitch };
3
- export { ConfigError, configProblems, sameHostWorkbenchPath, suggest, validateConfig } from "./validate.js";
3
+ export { ConfigError, configProblems, sameHostWorkbenchPath, storefrontBasePath, suggest, validateConfig, withSourceDefaults } from "./validate.js";
4
+ export { withOwnManifest } from "./own-manifest.js";
4
5
  export interface AssetsConfig {
5
6
  /** Where assets live. Only 'central' (the default: one assets dir, served at /assets/) is supported
6
7
  * so far; 'component-relative' is reserved, and the config check rejects it as not supported yet. */
@@ -195,10 +196,11 @@ export interface StorefrontConfig {
195
196
  */
196
197
  workbenchUrl?: string;
197
198
  /**
198
- * The site's address, such as `https://example.com/`: an absolute http(s) URL with no path (the storefront
199
- * is served from the root of its host), no query or fragment; loaded as its origin followed by `/`. With it,
200
- * pages get a canonical URL, Open Graph and JSON-LD, and `pd build` writes a sitemap; without it they are
201
- * left out, and `pd build` says so once.
199
+ * The site's address, such as `https://example.com/`: an absolute http(s) URL with no query or fragment; loaded
200
+ * with exactly one trailing slash. It may have a path, such as `https://example.github.io/project/`: the site is
201
+ * then published under that path (its base path), and every page, stylesheet, script and image it links is
202
+ * served under it, in `pd dev` too. With it, pages get a canonical URL, Open Graph and JSON-LD, and `pd build`
203
+ * writes a sitemap; without it they are left out, `pd build` says so once, and the site is served from `/`.
202
204
  */
203
205
  url?: string;
204
206
  /** The language of every page, as `<html lang>` takes it (a BCP 47 tag such as `de-AT`). Default: `en`. */
@@ -213,6 +215,13 @@ export interface StorefrontConfig {
213
215
  image?: string;
214
216
  /** This site's image encoding, over the project's `images`. */
215
217
  images?: ImageSettings;
218
+ /**
219
+ * The site's own additions to every page's `<head>`, such as an analytics script or a search engine's verification
220
+ * tag: a Handlebars file relative to the root, such as `./head.hbs`, rendered with `site` (plain Handlebars, without
221
+ * Pattern Dump's helpers) and placed after the skin's `partials/head`. It is checked as `partials/head` is: only
222
+ * `<meta>`, `<link>`, `<script>`, `<style>` and `<noscript>`, and none of the tags Pattern Dump writes itself.
223
+ */
224
+ head?: string;
216
225
  /**
217
226
  * Values every page of the site shares, such as its title or the line in its footer. Every layout gets
218
227
  * them as `site`; a page cannot set them. Which keys there are, and their types, is what the `site`
@@ -242,4 +251,10 @@ export type StorefrontNavEntry = {
242
251
  label: string;
243
252
  children: StorefrontNavEntry[];
244
253
  };
245
- export declare function defineConfig(config: PatternDumpConfig): PatternDumpConfig;
254
+ /**
255
+ * What a project writes: the config, where `sourceDir`, `patternsDir` and `styles` may be left out (a site whose
256
+ * patterns and styles all come from pattern packages has none of its own). `defineConfig` and `validateConfig` fill
257
+ * them in (`withSourceDefaults`), so everything that reads a config gets all three.
258
+ */
259
+ export type PatternDumpConfigInput = Omit<PatternDumpConfig, "sourceDir" | "patternsDir" | "styles"> & Partial<Pick<PatternDumpConfig, "sourceDir" | "patternsDir" | "styles">>;
260
+ export declare function defineConfig(config: PatternDumpConfigInput): PatternDumpConfig;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The config with each folder its project's own manifest names and the config doesn't, or the config as it is when
3
+ * there is no manifest. `where` names the two files in the error. A config that sets a field the manifest sets too,
4
+ * to another value, throws a ConfigError listing every such field. The raw config is never changed.
5
+ */
6
+ export declare function withOwnManifest(raw: unknown, manifest: unknown, where: {
7
+ config: string;
8
+ manifest: string;
9
+ }): unknown;
@@ -1,4 +1,4 @@
1
- import type { PatternDumpConfig } from "./index.js";
1
+ import type { PatternDumpConfig, PatternDumpConfigInput } from "./index.js";
2
2
  /** The closest of `known` to `key` when it is near enough to be a typo of it, else undefined. Used for
3
3
  * "did you mean" in config problems, and by other checks that name an unknown key. */
4
4
  export declare function suggest(key: string, known: string[]): string | undefined;
@@ -10,11 +10,24 @@ export declare class ConfigError extends Error {
10
10
  /** The problems with a loaded config, one line each; empty when it is valid. */
11
11
  export declare function configProblems(raw: unknown): string[];
12
12
  /** Check a loaded config and return it typed, or throw one error listing every problem. */
13
+ /**
14
+ * A config with its source folders filled in where it names none: `sourceDir` is "./src", and `patternsDir` and
15
+ * `styles.dir` are its `patterns` and `styles` folders. None of them has to exist: a site whose patterns and styles
16
+ * all come from pattern packages has none of its own. What the config names is kept as it is.
17
+ */
18
+ export declare function withSourceDefaults(config: PatternDumpConfigInput): PatternDumpConfig;
13
19
  export declare function validateConfig(raw: unknown, source: string): PatternDumpConfig;
20
+ /**
21
+ * The storefront's base path: the path of `storefront.url` ("/" at the root of a host, "/project/" under a path), or
22
+ * "/" for a site with no address. Reads a validated config, so the URL is already in its one form.
23
+ */
24
+ export declare function storefrontBasePath(storefront: {
25
+ url?: string;
26
+ } | undefined): string;
14
27
  /**
15
28
  * The path of a workbench published on the storefront's own host, or undefined when it has a host of its own.
16
29
  * Same-host means a root-relative `workbenchUrl` ("/workbench/"), or an absolute one on the origin of
17
- * `storefront.url`. Reads a validated config, so both are already normalised (one trailing slash).
30
+ * `storefront.url` (whatever the site's own path). The path is the workbench's full path on the host. Reads a validated config, so both are already normalised (one trailing slash).
18
31
  */
19
32
  export declare function sameHostWorkbenchPath(storefront: {
20
33
  workbenchUrl?: string;
@@ -70,7 +70,7 @@ export interface TreeNode {
70
70
  children?: TreeNode[];
71
71
  }
72
72
  export type DiagnosticSeverity = "error" | "warning" | "info";
73
- export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION" | "DATA_INVALID" | "SCHEMA_INVALID";
73
+ export type DiagnosticKind = "RENDER_ERROR" | "UNFILLED_REQUIRED" | "SHAPE_MISMATCH" | "HTML_INVALID" | "CASCADE_WARNING" | "A11Y" | "HIGHLIGHT_FALLBACK" | "HANDLE_COLLISION" | "DATA_INVALID" | "SCHEMA_INVALID" | "HELPER_SHADOW";
74
74
  /** Fields every diagnostic carries, whichever engine produced it. */
75
75
  interface DiagnosticBase {
76
76
  severity: DiagnosticSeverity;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pattern-dump",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.4.0",
4
4
  "description": "A workbench for HTML and CSS design systems: live component previews, docs, checks and static publishing.",
5
5
  "keywords": [
6
6
  "design-system",
@@ -1,5 +1,6 @@
1
- # Builds the website and the workbench, and publishes both to GitHub Pages:
2
- # the website at the root of your domain, the workbench under /workbench/.
1
+ # Builds the website and the workbench, and publishes both to GitHub Pages: the website at the address in
2
+ # storefront.url (your domain, or a project page's address with its path), the workbench inside it at
3
+ # workbench/ (build.basePath gives its full path).
3
4
  # Before the first run: Settings → Pages → Source: "GitHub Actions", and your custom domain: see "Publish it" in the README.
4
5
  name: Pages
5
6
 
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Hello, articles
3
+ description: The example article. Copy it to start a new one.
4
+ date: 2026-11-14
5
+ tags: [getting-started, notes]
6
+ ---
7
+
8
+ An article is a page in `pages/articles/` with a `date` (`YYYY-MM-DD`), which orders the listing and is the page's
9
+ publication date for search engines. `tags` are lowercase words for grouping. A page with `draft: true` shows while
10
+ you work and is left out of `pd build`.
@@ -0,0 +1,10 @@
1
+ ---
2
+ title: Articles
3
+ description: Everything written here, newest first.
4
+ layout: listing
5
+ sort: date
6
+ order: 2
7
+ ---
8
+
9
+ This page is `pages/articles/index.md`. Every page in this folder is listed below, newest first. Add a markdown
10
+ file with a `date` and it shows up here, with no link to write.
@@ -0,0 +1,16 @@
1
+ {{#> partials-base }}
2
+ <article class="page listing">
3
+ <h1 class="page-title">{{title}}</h1>
4
+ {{#if description}}<p class="page-description">{{description}}</p>{{/if}}
5
+ <div class="page-body">{{{body}}}</div>
6
+ <ul class="listing-entries" role="list">
7
+ {{#each page.children}}
8
+ <li class="listing-entry">
9
+ <a class="listing-title" href="{{route}}">{{title}}</a>
10
+ {{#if date}}<time class="listing-date" datetime="{{date}}">{{date}}</time>{{/if}}
11
+ {{#if description}}<p class="listing-teaser">{{description}}</p>{{/if}}
12
+ </li>
13
+ {{/each}}
14
+ </ul>
15
+ </article>
16
+ {{/partials-base}}
@@ -0,0 +1,13 @@
1
+ {
2
+ "title": "Articles",
3
+ "description": "Everything written here, newest first.",
4
+ "body": "<p>The folder's index.md, rendered.</p>",
5
+ "site": { "title": __PD_BRAND_JSON__, "footer": "Made with Pattern Dump." },
6
+ "nav": [{ "route": "/", "title": "Home" }, { "route": "/articles/", "title": "Articles", "current": true }],
7
+ "page": {
8
+ "children": [
9
+ { "route": "/articles/second/", "title": "A second article", "description": "Its teaser: the page's description, or its first paragraph.", "date": "2026-11-14", "image": null, "tags": ["notes"], "draft": false },
10
+ { "route": "/articles/first/", "title": "A first article", "description": "Listed below the newer one.", "date": "2026-11-01", "image": null, "tags": [], "draft": false }
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: Listing
3
+ ---
4
+
5
+ The layout of a folder's `index.md` that lists the folder's pages, such as `pages/articles/index.md`: set
6
+ `layout: listing`, and `sort: date` to list the newest first. It declares `page.children`, so the storefront fills
7
+ it with the folder's pages, each with its route, title, description (or its first paragraph), date, image and tags.
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Listing",
4
+ "description": "A folder's index.md that lists the folder's pages: a collection, such as articles.",
5
+ "type": "object",
6
+ "required": ["title"],
7
+ "properties": {
8
+ "title": { "type": "string", "description": "The listing's heading, and its title in the navigation" },
9
+ "description": { "type": "string", "description": "One sentence under the heading, and the page's description for search results" },
10
+ "page": {
11
+ "type": "object",
12
+ "description": "What the storefront works out for the page",
13
+ "properties": {
14
+ "children": { "description": "The folder's pages, newest first with sort: date" }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -2,6 +2,7 @@
2
2
  <article class="page">
3
3
  <h1 class="page-title">{{title}}</h1>
4
4
  {{#if description}}<p class="page-description">{{description}}</p>{{/if}}
5
+ {{#if page.date}}<div class="page-meta"><time datetime="{{page.date}}">{{page.date}}</time>{{#if page.tags.length}}<ul class="page-tags" aria-label="Tags">{{#each page.tags}}<li>{{this}}</li>{{/each}}</ul>{{/if}}</div>{{/if}}
5
6
  <div class="page-body">{{{body}}}</div>
6
7
  {{#if page.next}}<p class="page-next"><a rel="next" href="{{page.next.route}}">Next: {{page.next.title}}</a></p>{{/if}}
7
8
  </article>
@@ -4,5 +4,5 @@
4
4
  "body": "<p>The page's markdown, rendered.</p>",
5
5
  "site": { "title": __PD_BRAND_JSON__, "footer": "Made with Pattern Dump." },
6
6
  "nav": [{ "route": "/", "title": "Home", "current": true }, { "route": "/about/", "title": "About" }],
7
- "page": { "next": { "route": "/about/", "title": "About" } }
7
+ "page": { "next": { "route": "/about/", "title": "About" }, "date": "2026-11-14", "tags": ["notes"] }
8
8
  }
@@ -3,4 +3,5 @@ title: Page
3
3
  ---
4
4
 
5
5
  The layout every page renders with, unless its frontmatter names another with `layout: <name>`.
6
- Its schema is the frontmatter contract: a page may set `title` (required) and `description`.
6
+ Its schema is the frontmatter contract: a page may set `title` (required) and `description`. A page with a `date`
7
+ (an article) shows it under the description, with its `tags`.
@@ -12,7 +12,9 @@
12
12
  "type": "object",
13
13
  "description": "What the storefront works out for the page",
14
14
  "properties": {
15
- "next": { "description": "The page after this one in the navigation" }
15
+ "next": { "description": "The page after this one in the navigation" },
16
+ "date": { "description": "The page's date, from its frontmatter, for an article" },
17
+ "tags": { "description": "The page's tags, from its frontmatter" }
16
18
  }
17
19
  }
18
20
  }
@@ -0,0 +1,15 @@
1
+ @use "../base/tokens";
2
+
3
+ .listing-entries { margin: tokens.$space-lg 0 0; padding: 0; list-style: none; }
4
+ .listing-entry { margin-block-end: tokens.$space-lg; }
5
+ .listing-title { font-weight: 600; }
6
+ .listing-date { margin-inline-start: tokens.$space-sm; color: var(--color-text-muted, #4b5563); }
7
+ .listing-teaser {
8
+ margin: 0;
9
+ // The teaser is never shortened in the data: three lines at most here.
10
+ display: -webkit-box;
11
+ -webkit-box-orient: vertical;
12
+ -webkit-line-clamp: 3;
13
+ line-clamp: 3;
14
+ overflow: hidden;
15
+ }
@@ -4,3 +4,6 @@
4
4
  .page-description { margin-block: 0 tokens.$space-lg; color: var(--color-text-muted, #4b5563); }
5
5
  .page-body iframe { width: 100%; border: 0; }
6
6
  .page-next { margin-top: tokens.$space-lg; }
7
+ .page-meta { margin-block: 0 tokens.$space-lg; color: var(--color-text-muted, #4b5563); }
8
+ .page-tags { display: inline; margin: 0; padding: 0; list-style: none; }
9
+ .page-tags li { display: inline; margin-inline-start: tokens.$space-sm; }
@@ -6,8 +6,11 @@
6
6
  // The workbench is published on the website's own host, under /workbench/ (and built for that path,
7
7
  // build.basePath below).
8
8
  workbenchUrl: "/workbench/",
9
- // Your domain, once you have one: canonical URLs, link previews and the sitemap are built from it.
9
+ // The site's address, once you know it: canonical URLs, link previews and the sitemap are built from it.
10
10
  // url: "https://www.example.com/",
11
+ // Or a subfolder, such as a GitHub project page: the site is then served under the path, in `npm run dev` too,
12
+ // and the workbench goes under it as well (workbenchUrl and build.basePath "/<repository>/workbench/").
13
+ // url: "https://<owner>.github.io/<repository>/",
11
14
  site: { title: __PD_BRAND_JSON__, footer: "Made with Pattern Dump." },
12
15
  },
13
16
  build: { basePath: "/workbench/" },
@@ -30,6 +30,9 @@ returns by itself. How `pd-` tags work:
30
30
  | `src/patterns/components/note/` | the component the home page shows |
31
31
  | `pattern-dump.config.ts` | `storefront`: the website's name and footer (`site`), and where it is published |
32
32
 
33
+ `pages/articles/` is a collection: its `index.md` (layout `listing`, `sort: date`) lists every page in the folder,
34
+ newest first, by its `date`. Copy `hello.md` to write another; `draft: true` keeps one out of the build.
35
+
33
36
  A page that doesn't exist shows a built-in 404 page; write `pages/404.md` to change it. A page that moved
34
37
  can forward from its old address (`redirectFrom: [/old/]`), and `index: false` keeps a page out of search
35
38
  engines. All of it is in the [storefront guide](https://github.com/n8design/pattern-dump/blob/main/docs/guide/storefront.md).
@@ -42,9 +45,14 @@ what the GitHub Pages workflow does.
42
45
 
43
46
  ### Publish it
44
47
 
45
- `.github/workflows/pages.yml` publishes both to GitHub Pages on every push to `main`. The website lives at
46
- the root of its host, so it needs a custom domain (a project page under `/<repository>/` can't host it).
47
- Once, in the repository on GitHub:
48
+ `.github/workflows/pages.yml` publishes both to GitHub Pages on every push to `main`, at a custom domain
49
+ or as a project page.
50
+
51
+ **As a project page** (`https://<owner>.github.io/<repository>/`, no domain needed): in
52
+ `pattern-dump.config.ts`, set `storefront.url` to that address, and `storefront.workbenchUrl` and
53
+ `build.basePath` to `/<repository>/workbench/`. Then **Settings → Pages → Source:** "GitHub Actions".
54
+
55
+ **At a custom domain**, once, in the repository on GitHub:
48
56
 
49
57
  1. **Settings → Pages → Source:** "GitHub Actions".
50
58
  2. **Settings → Pages → Custom domain:** your domain, then Save. GitHub recommends verifying the domain in