🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@geml/geml

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@geml/geml - npm Package Compare versions

Comparing version
1.7.0
to
1.7.1
+20
-6
codemap/build.mjs

@@ -39,3 +39,3 @@ #!/usr/bin/env node

import { writeFileSync, mkdirSync, existsSync, readFileSync, statSync } from "node:fs";
import { join, resolve, basename, dirname, relative } from "node:path";
import { join, resolve, basename, dirname, relative, sep } from "node:path";
import { fileURLToPath } from "node:url";

@@ -104,3 +104,3 @@ import { execFileSync, spawnSync } from "node:child_process";

root: rootAbs, globs: excludeGlobs0, gitignore: !args.includes("--no-gitignore"),
files: [...files, ...manifests, ...pkgs], exec: execFileSync,
files: [...files, ...manifests, ...pkgs], run: execFileSync,
});

@@ -141,3 +141,6 @@ const jobs = detectLanguages(rootAbs, { files, manifests, pkgs, excluder });

// spaced launcher PATH is a full path, so quoting it keeps %~dp0 correct.
const q = (s) => (/[\s"]/.test(String(s)) ? `"${String(s).replace(/"/g, '\\"')}"` : String(s));
// WHEN it does quote, it defers to shq below: escaping only `"` left a
// trailing backslash (a path ending `...\`) escaping our own closing quote,
// which merges the next token into this one. Same CRT rules, one source.
const q = (s) => (/[\s"]/.test(String(s)) ? shq(s) : String(s));
// Hardened quote for command ARGUMENTS on win32. Node does NOT escape args

@@ -384,3 +387,3 @@ // under shell:true — it only concatenates them (Node DEP0190) — so an

files: [...new Set(symbols.map((s) => s.file))],
exec: execFileSync,
run: execFileSync,
});

@@ -398,3 +401,3 @@ const kept = symbols.filter((s) => !excluder(s.file));

root: rootAbs, globs: excludeGlobs, gitignore: !args.includes("--no-gitignore"),
files: [...c.files, ...c.manifests, ...c.pkgs], exec: execFileSync,
files: [...c.files, ...c.manifests, ...c.pkgs], run: execFileSync,
});

@@ -428,3 +431,14 @@ entryHints = detectEntries(rootAbs, {

files: scanFiles,
readText: (rel) => { try { return readFileSync(join(rootAbs, ...rel.split("/")), "utf8"); } catch { return null; } },
// `rel` is indexer OUTPUT, not a path this build authored, so a `..`
// segment must not turn a source read into an escape from the scanned
// root. Same gate the document resolver uses: resolve first, then require
// the result to BE the root or sit under it — compared with the separator,
// so a sibling named `<root>-evil` is not mistaken for a child.
readText: (rel) => {
try {
const p = resolve(rootAbs, ...String(rel).split("/"));
if (p !== rootAbs && !p.startsWith(rootAbs + sep)) return null;
return readFileSync(p, "utf8");
} catch { return null; }
},
});

@@ -431,0 +445,0 @@ for (const e of httpEdges) edges.push(e);

@@ -18,13 +18,13 @@ // Source exclusion for the codemap build.

export function globToRegExp(glob) {
// The glob comes from a `--exclude` argument, so nothing in it may reach the
// compiled pattern as *syntax*. Split on the wildcards, keeping them (the
// capture group), which leaves the array strictly alternating: even indices
// are literal text, odd indices are `*`, `**` or `**/`. Literals go through a
// total regex-metacharacter escape; wildcards map to fixed patterns. Neither
// path can carry an unescaped metacharacter through.
const parts = String(glob).split(/(\*\*\/?|\*)/);
let re = "";
for (let i = 0; i < glob.length; i++) {
const c = glob[i];
if (c === "*") {
if (glob[i + 1] === "*") { re += ".*"; i++; if (glob[i + 1] === "/") i++; }
else re += "[^/]*";
} else if ("\\^$+?.()|{}[]".includes(c)) {
re += "\\" + c;
} else {
re += c;
}
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 0) re += parts[i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
else re += parts[i] === "*" ? "[^/]*" : ".*"; // `**` and `**/` span separators
}

@@ -37,6 +37,10 @@ return new RegExp("^" + re + "$");

// the dir is not a repo — both mean "ignore nothing", not a build failure.
export function gitIgnored(root, files, exec = _execFileSync) {
// The injected runner is named `run`, not `exec`: it is always an execFile-shaped
// (program, args[]) call that spawns NO shell, whereas a callback named `exec`
// reads — to a human skimming, and to a static analyser — as the shell-string
// child_process API. The name should not imply the dangerous one.
export function gitIgnored(root, files, run = _execFileSync) {
if (!files.length) return new Set();
try {
const out = exec("git", ["-C", root, "check-ignore", "--stdin"], { input: files.join("\n"), encoding: "utf8" });
const out = run("git", ["-C", root, "check-ignore", "--stdin"], { input: files.join("\n"), encoding: "utf8" });
return new Set(out.split(/\r?\n/).filter(Boolean));

@@ -50,6 +54,6 @@ } catch (e) {

// Build a predicate (file) => shouldExclude.
export function makeExcluder({ root, globs = [], gitignore = true, files = [], exec } = {}) {
export function makeExcluder({ root, globs = [], gitignore = true, files = [], run } = {}) {
const res = globs.map(globToRegExp);
const ignored = gitignore ? gitIgnored(root, files, exec) : new Set();
const ignored = gitignore ? gitIgnored(root, files, run) : new Set();
return (file) => ignored.has(file) || res.some((r) => r.test(file));
}

@@ -98,3 +98,6 @@ #!/usr/bin/env node

// metachar inside a dir-name argument is therefore inert.
const q = (s) => (/[\s"]/.test(String(s)) ? `"${String(s).replace(/"/g, '\\"')}"` : String(s));
// q quotes only when it must; WHEN it does it defers to shq, so a program path
// ending in a backslash cannot escape our own closing quote and swallow the
// next token. One set of CRT rules, in one place.
const q = (s) => (/[\s"]/.test(String(s)) ? shq(s) : String(s));
const shq = (s) => `"${String(s).replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1')}"`;

@@ -101,0 +104,0 @@ // Human-readable render of a step for the log / refusal message — DISPLAY ONLY,

@@ -40,3 +40,6 @@ #!/usr/bin/env node

// CRT rules for embedded " / trailing \.
const q = (s) => (/[\s"]/.test(String(s)) ? `"${String(s).replace(/"/g, '\\"')}"` : String(s));
// q quotes only when it must; WHEN it does it defers to shq, so a program path
// ending in a backslash cannot escape our own closing quote and swallow the
// next token. One set of CRT rules, in one place.
const q = (s) => (/[\s"]/.test(String(s)) ? shq(s) : String(s));
const shq = (s) => `"${String(s).replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1')}"`;

@@ -43,0 +46,0 @@ // A codemap document's `src=` routes are written relative to the indexed

@@ -58,3 +58,8 @@ // GEML reference parser — Markdown → GEML conversion.

const bareSafe = /^[^\s"]+$/.test(v);
return bareSafe ? `${m[1]}=${v}` : `${m[1]}="${v.replace(/"/g, '\\"')}"`;
// No backslash escape here — a `data` block value is read by `coerce`, which
// simply strips the outer quotes (§4); GEML defines no `\"` escape at all.
// Emitting one wrote the backslash into the parsed value, so `a"b` came back
// as `a\"b`. Quoting the value verbatim round-trips instead: coerce keeps
// everything between the first and last quote, embedded quotes included.
return bareSafe ? `${m[1]}=${v}` : `${m[1]}="${v}"`;
}

@@ -61,0 +66,0 @@ // Rewrite Markdown autolinks `<https://…>` / `<mailto:…>` into GEML links

@@ -51,6 +51,28 @@ // GEML -> Markdown projection (the inverse direction of from-md.ts).

}
// Escape a `|` so GFM keeps it inside the cell instead of splitting the row.
// GFM resolves backslash escapes in a row BEFORE it splits on `|`, so a
// backslash run sitting right in front of our escape would eat it: a code span
// holding `a\|b` became `a\\|b`, which reads as a literal backslash followed by
// an UNescaped pipe — a spurious cell break. Double any such run first, then
// escape the pipe. Runs already produced by escText (`\\` for a literal
// backslash) survive this unchanged, so pre-rendered Markdown stays intact.
//
// The backslash run is matched as `\\+\|?` — one ATOMIC token, run and pipe
// together — not as `(\\*)\|`. The latter is quadratic: on a cell holding a
// long run of backslashes and no pipe, the engine matches the run from every
// index in it and fails at the required `|` each time. Here the greedy `\\+`
// takes the whole run in one match and the trailing `\|?` is optional, so
// nothing backtracks and each character is visited once.
function escPipe(s) {
return s.replace(/\\+\|?|\|/g, (m) => {
if (m.charAt(m.length - 1) !== "|")
return m; // a run with no pipe after it
const bs = m.slice(0, -1); // the run that would otherwise eat our escape
return bs + bs + "\\|";
});
}
// Inline text for a table cell: render inlines, then neutralise the two bytes
// that would break a GFM cell.
function cellText(c) {
return seq(c.inlines).replace(/\|/g, "\\|").replace(/\n/g, " ");
return escPipe(seq(c.inlines)).replace(/\n/g, " ");
}

@@ -76,3 +98,3 @@ // ---------------------------------------------------------------------------

lines.push(`*${t.caption}*`, "");
lines.push(`| ${cols.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`);
lines.push(`| ${cols.map(escPipe).join(" | ")} |`);
lines.push(`| ${cols.map((_, i) => sep(t.align[i])).join(" | ")} |`);

@@ -79,0 +101,0 @@ const pad = (cells) => {

{
"name": "@geml/geml",
"version": "1.7.0",
"version": "1.7.1",
"mcpName": "io.github.geml-spec/geml",

@@ -5,0 +5,0 @@ "publishConfig": {

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display