@geml/geml 1.4.5 → 1.5.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.
@@ -290,7 +290,7 @@ export const TOOLS = [
290
290
  // EVERY line carries the full `doc.geml#id`, including same-document
291
291
  // targets that profile §4 would abbreviate to `#id`. The reader here is
292
292
  // an agent, and each line has to be usable as-is for the next
293
- // `open_symbol`/`trace_calls` call. A bare id would make it infer the
293
+ // `geml_codemap_node`/`geml_codemap_callchain` call. A bare id would make it infer the
294
294
  // document from the line's ancestors — cheaper output, one more thing to
295
295
  // get wrong.
296
296
  const walk = (doc, id, level, prefix, last) => {
@@ -8,8 +8,8 @@
8
8
  // graph area (nested frame), so the whole map is browsable offline — this is
9
9
  // the "copy the folder to someone" mode. For a live view that never goes
10
10
  // stale, use `geml codemap serve` instead.
11
- import { readdirSync, readFileSync, writeFileSync } from "node:fs";
12
- import { join, basename } from "node:path";
11
+ import { readdirSync, readFileSync, writeFileSync, realpathSync } from "node:fs";
12
+ import { join, basename, sep, resolve as resolvePath } from "node:path";
13
13
  import { parse, renderHtml } from "../dist/geml.js";
14
14
 
15
15
  if (process.argv[2] === "--help" || process.argv[2] === "-h") {
@@ -25,9 +25,22 @@ const dir = process.argv[2] || ".geml-code-graph";
25
25
  // text + parsed docs live in memory for the duration of the run).
26
26
  const texts = new Map(); // rel -> text | null
27
27
  const parsed = new Map(); // text -> Document
28
+ // `rel` is document-controlled — a `src=` composed by the renderer — and the
29
+ // output of this command is a published artifact, so an unconfined read here
30
+ // writes any .geml on the filesystem into a file that then gets served. Gate on
31
+ // the realpath, the same shape `serve` and the CLI resolver use.
32
+ let realDir = null;
33
+ try { realDir = realpathSync(resolvePath(dir)); } catch { realDir = null; }
28
34
  const loadDoc = (rel) => {
29
35
  if (!texts.has(rel)) {
30
- try { texts.set(rel, readFileSync(join(dir, rel), "utf8")); } catch { texts.set(rel, null); }
36
+ let text = null;
37
+ if (realDir !== null) {
38
+ try {
39
+ const real = realpathSync(join(dir, rel));
40
+ if (real === realDir || real.startsWith(realDir + sep)) text = readFileSync(real, "utf8");
41
+ } catch { text = null; }
42
+ }
43
+ texts.set(rel, text);
31
44
  }
32
45
  return texts.get(rel);
33
46
  };
package/codemap/serve.mjs CHANGED
@@ -182,7 +182,14 @@ export function createApp({ dir, root, port, cacheMb, srcRoot }) {
182
182
  return entry;
183
183
  };
184
184
  const loadDoc = (rel) => {
185
- const e = loadCached(join(root, rel));
185
+ // `rel` is document-controlled — it is whatever a `src=` composed — so it goes
186
+ // through the same symlink-safe gate the URL router uses. Without it a
187
+ // transclusion could name `../../../secret.geml` and this loader would read
188
+ // and serve any .geml on the filesystem. (confine is declared below; loadDoc
189
+ // is only ever called while handling a request, so the closure is resolved.)
190
+ const abs = confine(join(root, rel));
191
+ if (abs === null) return null;
192
+ const e = loadCached(abs);
186
193
  return e ? e.text : null;
187
194
  };
188
195
  // loadDoc hands out the cached string instance, so the by-text lookup hits
@@ -1,4 +1,4 @@
1
- export type DiagnosticCode = "unterminated-block" | "unknown-block-type" | "block-nesting-too-deep" | "list-nesting-too-deep" | "inline-nesting-too-deep" | "duplicate-id" | "unresolved-reference" | "unresolved-footnote" | "unresolved-cross-document-reference" | "unresolvable-document" | "unchecked-cross-document-reference" | "unknown-metadata-reference" | "table-src-and-body" | "unknown-table-format" | "bad-compute-formula" | "unlexable-compute-formula" | "compute-error" | "bad-summary-entry" | "summary-unknown-column" | "unlexable-summary-expression" | "summary-error" | "bad-span" | "span-outside-table" | "unknown-diagram-format" | "ignored-diagram-body" | "code-graph-missing-src" | "code-graph-unresolvable-document" | "chart-missing-data" | "chart-data-not-a-table" | "chart-missing-type" | "chart-unknown-type" | "chart-unknown-rows-scope" | "chart-missing-channel" | "chart-empty-channel" | "chart-unknown-column" | "chart-unused-channel" | "chart-missing-summary-row" | "chart-summary-row-unavailable" | "chart-non-numeric-value";
1
+ export type DiagnosticCode = "unterminated-block" | "unknown-block-type" | "block-nesting-too-deep" | "list-nesting-too-deep" | "inline-nesting-too-deep" | "duplicate-id" | "unresolved-reference" | "unresolved-footnote" | "unresolved-cross-document-reference" | "unresolvable-document" | "unchecked-cross-document-reference" | "embed-missing-src" | "ignored-embed-body" | "transclusion-cycle" | "embed-target-not-geml" | "media-target-is-document" | "inline-transclusion-not-inline" | "unsafe-embed-scheme" | "source-attr-conflict" | "unresolvable-table-source" | "table-source-not-a-table" | "unknown-metadata-reference" | "table-src-and-body" | "unknown-table-format" | "bad-compute-formula" | "unlexable-compute-formula" | "compute-error" | "bad-summary-entry" | "summary-unknown-column" | "unlexable-summary-expression" | "summary-error" | "bad-span" | "span-outside-table" | "unknown-diagram-format" | "ignored-diagram-body" | "code-graph-missing-src" | "code-graph-unresolvable-document" | "chart-missing-data" | "chart-data-not-a-table" | "chart-missing-type" | "chart-unknown-type" | "chart-unknown-rows-scope" | "chart-missing-channel" | "chart-empty-channel" | "chart-unknown-column" | "chart-unused-channel" | "chart-missing-summary-row" | "chart-summary-row-unavailable" | "chart-non-numeric-value";
2
2
  export interface Diagnostic {
3
3
  severity: "error" | "warning";
4
4
  code: DiagnosticCode;
@@ -24,6 +24,16 @@ export const SEVERITY = {
24
24
  "unresolved-cross-document-reference": "error",
25
25
  "unresolvable-document": "error",
26
26
  "unchecked-cross-document-reference": "warning",
27
+ "embed-missing-src": "error",
28
+ "ignored-embed-body": "warning",
29
+ "transclusion-cycle": "error",
30
+ "embed-target-not-geml": "error",
31
+ "media-target-is-document": "error",
32
+ "inline-transclusion-not-inline": "error",
33
+ "unsafe-embed-scheme": "error",
34
+ "source-attr-conflict": "error",
35
+ "unresolvable-table-source": "error",
36
+ "table-source-not-a-table": "error",
27
37
  "unknown-metadata-reference": "error",
28
38
  "table-src-and-body": "error",
29
39
  "unknown-table-format": "warning",
package/dist/geml.d.ts CHANGED
@@ -64,7 +64,11 @@ export interface Document {
64
64
  }
65
65
  export interface ParseOptions {
66
66
  resolveDoc?: (doc: string) => string | null;
67
+ self?: string;
67
68
  }
69
+ export declare function projectableInlines(blocks: Block[], id: string): {
70
+ inlines: Inline[];
71
+ } | "not-inline" | null;
68
72
  export declare function parse(source: string, opts?: ParseOptions): Document;
69
73
  export interface Span {
70
74
  start: number;