@zvndev/darkmown 0.8.1 → 0.9.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.
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Compile a page source file into a full HTML document plus its assets.
3
+ * @param {string} file Absolute path to the source `.md`/`.wd` file.
4
+ * @param {Paths} context Resolved project paths.
5
+ * @returns {CompiledPage}
6
+ */
7
+ export function compilePage(file: string, context: Paths): CompiledPage;
8
+ /**
9
+ * Compile a source file into its body HTML, frontmatter, and assets (no page shell).
10
+ * @param {string} file Absolute path to the source file.
11
+ * @param {Paths} context Resolved project paths.
12
+ * @param {string[]} [stack] Include stack for cycle detection.
13
+ * @param {Record<string, unknown>} [vars] Initial static scope variables.
14
+ * @returns {CompiledDocument}
15
+ */
16
+ export function compileDocument(file: string, context: Paths, stack?: string[], vars?: Record<string, unknown>): CompiledDocument;
17
+ /**
18
+ * Split a raw file into its frontmatter `meta` and `body`.
19
+ * @param {string} raw Full file contents.
20
+ * @param {string} [file] Source path, used only for error messages.
21
+ * @returns {{ meta: Meta, body: string }}
22
+ */
23
+ export function parseFrontmatter(raw: string, file?: string): {
24
+ meta: Meta;
25
+ body: string;
26
+ };
27
+ /**
28
+ * Stable per-render key for a loop row, disambiguating duplicates with `#n`.
29
+ * @param {unknown} item
30
+ * @param {Map<string, number>} counts Mutable seen-count accumulator.
31
+ * @returns {string}
32
+ */
33
+ export function loopKeyOf(item: unknown, counts: Map<string, number>): string;
34
+ /**
35
+ * Escape a value for safe inclusion in HTML text and attribute contexts.
36
+ * @param {unknown} value
37
+ * @returns {string}
38
+ */
39
+ export function escapeHtml(value: unknown): string;
40
+ /**
41
+ * A compiled `@loop … where` predicate.
42
+ */
43
+ export type Predicate = {
44
+ /**
45
+ * Safe JS boolean expression over `I()`/`S()`/`C()`.
46
+ */
47
+ body: string;
48
+ /**
49
+ * Whether the predicate reads declared state.
50
+ */
51
+ refsState: boolean;
52
+ };
53
+ /**
54
+ * A parsed `:button` action.
55
+ */
56
+ export type Action = {
57
+ /**
58
+ * Runtime operation (inc/dec/add/append/set/remove/append-row).
59
+ */
60
+ op: string;
61
+ /**
62
+ * State key the action mutates.
63
+ */
64
+ target: string;
65
+ /**
66
+ * Literal value for value-carrying ops.
67
+ */
68
+ value?: unknown;
69
+ };
70
+ export type Paths = import("./config.js").Paths;
71
+ /**
72
+ * Frontmatter values are scalars or inline arrays of scalars.
73
+ */
74
+ export type FrontmatterValue = string | number | boolean | null | Array<string | number | boolean | null>;
75
+ /**
76
+ * Parsed page frontmatter, keyed by field name.
77
+ */
78
+ export type Meta = Record<string, FrontmatterValue>;
79
+ /**
80
+ * Collected output assets for a compiled document.
81
+ */
82
+ export type Assets = {
83
+ /**
84
+ * Public hrefs of compiled skin stylesheets.
85
+ */
86
+ skins: Set<string>;
87
+ /**
88
+ * Public hrefs of colocated page scripts.
89
+ */
90
+ scripts: Set<string>;
91
+ /**
92
+ * Source path → public href for emitted assets.
93
+ */
94
+ files: Map<string, string>;
95
+ /**
96
+ * Whether the reactive runtime is required.
97
+ */
98
+ runtime: boolean;
99
+ };
100
+ /**
101
+ * Per-document compilation accumulator shared across includes/sections.
102
+ */
103
+ export type Compilation = {
104
+ assets: Assets;
105
+ /**
106
+ * Declared state keys → initial values.
107
+ */
108
+ state: Map<string, unknown>;
109
+ /**
110
+ * Non-fatal authoring hints.
111
+ */
112
+ warnings: string[];
113
+ /**
114
+ * Counter for auto-generated section ids.
115
+ */
116
+ sectionCounter: number;
117
+ };
118
+ /**
119
+ * A lexical scope chain for static interpolation values (include args, loop vars).
120
+ */
121
+ export type Scope = {
122
+ parent: Scope | null;
123
+ vars: Record<string, unknown>;
124
+ };
125
+ /**
126
+ * Per-file compile context threaded through the directive handlers.
127
+ */
128
+ export type Ctx = {
129
+ /**
130
+ * Absolute path to the file being compiled.
131
+ */
132
+ file: string;
133
+ /**
134
+ * Resolved project paths.
135
+ */
136
+ context: Paths;
137
+ /**
138
+ * Include stack (real paths) for cycle detection.
139
+ */
140
+ stack: string[];
141
+ /**
142
+ * Static value scope chain.
143
+ */
144
+ scope: Scope;
145
+ /**
146
+ * Shared compilation accumulator.
147
+ */
148
+ comp: Compilation;
149
+ /**
150
+ * Active section-id scope chain.
151
+ */
152
+ sections: string[];
153
+ /**
154
+ * Name of the current reactive loop item, if any.
155
+ */
156
+ loopItem: string | null;
157
+ /**
158
+ * State key of the list being looped, if any.
159
+ */
160
+ loopKey?: string | undefined;
161
+ /**
162
+ * Markdown-it instance selected for this file.
163
+ */
164
+ md?: MarkdownIt | undefined;
165
+ };
166
+ /**
167
+ * A compiled page document.
168
+ */
169
+ export type CompiledDocument = {
170
+ meta: Meta;
171
+ html: string;
172
+ assets: Assets;
173
+ warnings: string[];
174
+ };
175
+ /**
176
+ * A compiled page: the document plus its full HTML shell.
177
+ */
178
+ export type CompiledPage = {
179
+ meta: Meta;
180
+ html: string;
181
+ assets: Assets;
182
+ warnings: string[];
183
+ };
184
+ import MarkdownIt from "markdown-it";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Resolved project paths used across the build pipeline.
3
+ * @typedef {object} Paths
4
+ * @property {string} cwd Project working directory.
5
+ * @property {string} routesRoot Absolute path to `site/pages`.
6
+ * @property {string} shelfRoot Absolute path to the include shelf `site/_`.
7
+ * @property {string} distRoot Absolute path to the build output `dist`.
8
+ */
9
+ /**
10
+ * Build the absolute project paths for a given working directory.
11
+ * @param {string} [cwd] Project working directory (defaults to `process.cwd()`).
12
+ * @returns {Paths}
13
+ */
14
+ export function createPaths(cwd?: string): Paths;
15
+ /**
16
+ * Whether a file or directory name is hidden from routing (starts with `.`, `-`, or `_`).
17
+ * @param {string} name
18
+ * @returns {boolean}
19
+ */
20
+ export function isHiddenName(name: string): boolean;
21
+ export const routesDir: "site/pages";
22
+ export const shelfDir: "site/_";
23
+ export const distDir: "dist";
24
+ export const pageExtensions: Set<string>;
25
+ /**
26
+ * Resolved project paths used across the build pipeline.
27
+ */
28
+ export type Paths = {
29
+ /**
30
+ * Project working directory.
31
+ */
32
+ cwd: string;
33
+ /**
34
+ * Absolute path to `site/pages`.
35
+ */
36
+ routesRoot: string;
37
+ /**
38
+ * Absolute path to the include shelf `site/_`.
39
+ */
40
+ shelfRoot: string;
41
+ /**
42
+ * Absolute path to the build output `dist`.
43
+ */
44
+ distRoot: string;
45
+ };
package/types/dev.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Inject the dev-client `<script>` before `</body>` (or append it if absent).
3
+ * @param {string} html Page HTML to augment.
4
+ * @returns {string}
5
+ */
6
+ export function injectDevClient(html: string): string;
7
+ /**
8
+ * The dev-client browser script that opens the SSE channel for live reload.
9
+ * @returns {string}
10
+ */
11
+ export function devClientScript(): string;
12
+ export const devClientPath: "/__wd/dev-client.js";
13
+ export const devEventsPath: "/__wd/dev-events";
@@ -0,0 +1,33 @@
1
+ // Public type surface for @zvndev/darkmown.
2
+ // Generated declarations live alongside this file (see `npm run types`).
3
+ // This entry re-exports the functions and shapes a consumer is expected to use.
4
+
5
+ export { buildSite } from "./builder.js";
6
+ export type { RouteManifestEntry, Paths, Assets } from "./builder.js";
7
+
8
+ export {
9
+ compilePage,
10
+ compileDocument,
11
+ parseFrontmatter,
12
+ escapeHtml,
13
+ loopKeyOf
14
+ } from "./compiler.js";
15
+ export type {
16
+ Meta,
17
+ FrontmatterValue,
18
+ CompiledPage,
19
+ CompiledDocument
20
+ } from "./compiler.js";
21
+
22
+ export { createPaths, isHiddenName, pageExtensions } from "./config.js";
23
+
24
+ export { discoverRoutes, routeFromFile, outputPathForRoute } from "./router.js";
25
+ export type { Route } from "./router.js";
26
+
27
+ export { compileSkin } from "./skin.js";
28
+
29
+ export { initProject } from "./scaffold.js";
30
+
31
+ export { serve, resolvePublicFile, contentType } from "./statics.js";
32
+
33
+ export { injectDevClient, devClientScript } from "./dev.js";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A discovered page route: the source file and its public route path.
3
+ * @typedef {object} Route
4
+ * @property {string} file Absolute path to the source `.md`/`.wd` file.
5
+ * @property {string} route Public route path (always trailing-slashed, e.g. `/about/`).
6
+ */
7
+ /**
8
+ * Walk `routesRoot` and collect page routes, sorted and de-duplicated.
9
+ * @param {string} routesRoot Absolute path to `site/pages`.
10
+ * @returns {Route[]}
11
+ */
12
+ export function discoverRoutes(routesRoot: string): Route[];
13
+ /**
14
+ * Derive a public route path from a source file path.
15
+ * @param {string} routesRoot Absolute path to `site/pages`.
16
+ * @param {string} file Absolute path to the source file.
17
+ * @returns {string}
18
+ */
19
+ export function routeFromFile(routesRoot: string, file: string): string;
20
+ /**
21
+ * Map a public route to the `index.html` output path under `distRoot`.
22
+ * @param {string} distRoot Absolute path to the build output directory.
23
+ * @param {string} route Public route path.
24
+ * @returns {string}
25
+ */
26
+ export function outputPathForRoute(distRoot: string, route: string): string;
27
+ /**
28
+ * A discovered page route: the source file and its public route path.
29
+ */
30
+ export type Route = {
31
+ /**
32
+ * Absolute path to the source `.md`/`.wd` file.
33
+ */
34
+ file: string;
35
+ /**
36
+ * Public route path (always trailing-slashed, e.g. `/about/`).
37
+ */
38
+ route: string;
39
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Scaffold a new Darkmown project into `root`, creating files that don't exist.
3
+ * @param {string} root Absolute path to the target project directory.
4
+ * @returns {{ root: string }}
5
+ */
6
+ export function initProject(root: string): {
7
+ root: string;
8
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * A nesting frame in the indentation stack.
3
+ * @typedef {object} SkinFrame
4
+ * @property {number} indent
5
+ * @property {string | null} selector
6
+ * @property {string} [media]
7
+ */
8
+ /**
9
+ * Compile indentation-based `.skin` source into CSS.
10
+ * @param {string} source
11
+ * @returns {string}
12
+ */
13
+ export function compileSkin(source: string): string;
14
+ /**
15
+ * A nesting frame in the indentation stack.
16
+ */
17
+ export type SkinFrame = {
18
+ indent: number;
19
+ selector: string | null;
20
+ media?: string | undefined;
21
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Serve a built file for a request URL out of `distRoot`, or a 404 page.
3
+ * @param {string} distRoot Absolute path to the build output directory.
4
+ * @param {string} url Request URL (may include a query string).
5
+ * @param {http.ServerResponse} res Response to write to.
6
+ * @returns {void}
7
+ */
8
+ export function serve(distRoot: string, url: string, res: http.ServerResponse): void;
9
+ /**
10
+ * Resolve a request URL to an absolute file inside `distRoot`, guarding against
11
+ * path traversal. Returns null when the resolved path escapes the root.
12
+ * @param {string} distRoot Absolute path to the build output directory.
13
+ * @param {string} url Request URL (may include a query string).
14
+ * @returns {string | null}
15
+ */
16
+ export function resolvePublicFile(distRoot: string, url: string): string | null;
17
+ /**
18
+ * Map a file path to a response content-type, defaulting to `text/html`.
19
+ * @param {string} file File path or name.
20
+ * @returns {string}
21
+ */
22
+ export function contentType(file: string): string;
23
+ import http from "node:http";
@@ -1,120 +0,0 @@
1
- # OKF integration — plan & options
2
-
3
- Status: **parked / decision pending** (2026-06-14). Research-verified; revisit when
4
- we decide how far to take it. v0.8.0 (array frontmatter + readable `meta`) already
5
- shipped the enabling groundwork.
6
-
7
- ## What OKF is (verified)
8
-
9
- **Open Knowledge Format (OKF)** is real. Published **2026-06-12** by the Google Cloud
10
- Data Analytics team (Sam McVeety, Amir Hormati).
11
-
12
- - Announcement: <https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing/>
13
- - Spec: <https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md>
14
- - Repo (Apache-2.0, `okf/` subdir): <https://github.com/GoogleCloudPlatform/knowledge-catalog>
15
- - **Not** the Open Knowledge Foundation (unrelated nonprofit — do not conflate).
16
-
17
- It is a **file format**, not a service: a directory of markdown "concept" files for
18
- feeding organizational knowledge to AI agents. Each concept file has YAML frontmatter
19
- where **`type` is the only required field**; `title`, `description`, `resource` (a URI
20
- for the underlying asset), `tags` (a YAML list), and `timestamp` (ISO 8601) are
21
- optional. Producers may add arbitrary keys; consumers must preserve unknown keys.
22
-
23
- - **Cross-links** are ordinary markdown links between concept files (bundle-relative
24
- absolute like `/tables/customers.md` recommended). They are treated as **directed,
25
- untyped graph edges** — the relationship's meaning lives in surrounding prose.
26
- - **Reserved files:** `index.md` (a directory listing for *progressive disclosure* — an
27
- agent navigates one level at a time) and `log.md` (chronological history). The root
28
- `index.md` frontmatter may declare `okf_version: "0.1"`.
29
- - **Conformance is permissive:** consumers MUST tolerate broken links, unknown `type`
30
- values, unknown keys, and missing optional fields (so agent-generated, partial bundles
31
- still work).
32
- - **Reference tooling (both labeled proof-of-concept):** a BigQuery *enrichment agent*
33
- that drafts OKF docs per table, and a *visualizer* — a `visualize` command that emits a
34
- single self-contained interactive HTML file (Cytoscape.js force-graph + marked.js body,
35
- no backend).
36
- - **Sample bundles:** GA4 e-commerce, Stack Overflow, Bitcoin. ("BigQuery Table /
37
- Dataset / Metric" are concept *types inside* bundles, not separate samples.)
38
-
39
- ## Why it's interesting for Darkmown
40
-
41
- OKF's entire surface — YAML frontmatter + a folder tree + relative markdown links →
42
- static HTML — **is what Darkmown already does**. A `.md` tree with a `type:` field and
43
- cross-links is *nearly an OKF bundle by accident*. Two directions:
44
-
45
- 1. **Darkmown as an OKF renderer.** Darkmown renders markdown bundles to static, zero-JS
46
- HTML — directly competitive with OKF's reference visualizer, which is OKF's weakest
47
- surface (a single JS-heavy HTML file).
48
- 2. **Every Darkmown site emits an OKF bundle.** Any docs/content site also becomes
49
- machine-readable agent context. No other markdown framework offers this.
50
-
51
- ## Why to be cautious
52
-
53
- - **v0.1 "Draft," explicitly evolving.** Google's own blog: "a starting point, not a
54
- finished standard." Field names and required fields may change (minor versions promise
55
- backward-compatible additions only; breaking changes need a major bump).
56
- - **Thin traction.** ~1.2k GitHub stars but ~8 issues, ~13 PRs, ~27 commits at launch.
57
- **No third-party adopters** — only Google's own Cloud Knowledge Catalog consumes it.
58
- - **Governance unclear.** Lives under Google's repo with no foundation/working group.
59
- - **Thin convention.** Net-new over "a markdown vault + Obsidian/Dataview" is small: one
60
- required field, two reserved filenames, link-as-edge semantics. Easy for others to
61
- ignore or fork.
62
-
63
- ## Decision (Kirby, 2026-06-14)
64
-
65
- **Support OKF naturally; do not tie ourselves to it.** Treat it as a cheap, additive
66
- interop convention, not a strategic bet or a core-model change.
67
-
68
- ## Options (revisit later)
69
-
70
- ### Option A — Do nothing more (status quo)
71
- v0.8.0 array frontmatter already makes Darkmown frontmatter able to carry `tags` lists
72
- and a `type` field. A Darkmown content tree with `type:` in frontmatter is already
73
- ~conformant. Cost: zero. Keep tracking adoption.
74
-
75
- ### Option B — Thin OKF emit (recommended first step if we act) — increment #3
76
- A flag-gated export from a built site: `okf: true` in config or `--emit-okf`.
77
- - During `buildSite` (`src/builder.js`), alongside `routes.json`, write `dist/okf/`.
78
- - For each page, emit an OKF doc: frontmatter (`type` defaulting to `page`, `title` from
79
- meta, `tags` from meta, `timestamp` from file mtime/git) + the page's markdown body.
80
- - Derive edges from internal links + `@include` edges (`resolveInclude`/`compileFile`
81
- already know these). Emit per-directory `index.md` for progressive disclosure.
82
- - **Map at emit time** — do not restructure Darkmown's internal model around OKF.
83
- - Effort: low; it hooks the existing route loop. Largely free.
84
-
85
- ### Option C — OKF render mode ("best OKF renderer") — increment #2
86
- `darkmown build --okf ./bundle [--out dist-okf]`: walk an arbitrary bundle root, render
87
- each doc (type-aware templates, generic fallback), rewrite `.md` cross-links to routes,
88
- emit a static index/graph view. Reuse `parseFrontmatter`, markdown-it, `compileFile` —
89
- do not fork the pipeline. Must stay `runtime: false`.
90
- - **Gate this on OKF showing real external adoption.** Today you'd build a renderer for a
91
- format only Google uses. Higher effort, higher coupling risk.
92
-
93
- ### Sequencing if we proceed
94
- 1. (done) Array frontmatter + readable `meta` — v0.8.0.
95
- 2. Option B (emit) — additive, low-regret.
96
- 3. Option C (render) — only if the format gets stickier.
97
-
98
- ## Hard "avoid" list
99
-
100
- - Don't hardcode Google/BigQuery `type` vocabularies (`BigQuery Table`, etc.) — `type` is
101
- uncentralized and producer-defined.
102
- - Don't bet on v0.1 field names / required-field stability — map our frontmatter → OKF at
103
- emit time.
104
- - Don't depend on OKF's reference enrichment agent or visualizer — both are proofs of
105
- concept. If we want a graph view, build our own; the format is trivial.
106
- - Don't market "OKF-native" as a moat — it's a thin convention with thin adoption. A
107
- cheap interoperability checkbox, not a strategic bet.
108
-
109
- ## Invariants to respect (if/when we build)
110
-
111
- - `.md` never gains directive behavior; OKF docs are content, rendered as content.
112
- - No eval of bundle content; the `constructor`/`prototype`/`__proto__` rejection in
113
- `getPath` still applies.
114
- - OKF render/emit are pure content transforms — `runtime: false` always.
115
- - Compile/render errors include the offending file path and a corrective suggestion.
116
-
117
- ## Source
118
-
119
- Full research briefing (cited): the research-investigator memory
120
- `reference_okf_open_knowledge_format.md`, and `docs/spec-alignment.md` Stage 7.