@markii/core 0.13.0 → 0.14.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { parse } from './parse.js';
1
+ export { parse, isRecognizedTextDirective } from './parse.js';
2
2
  export { extractFrontmatter, extractFrontmatterUses, type Frontmatter, } from './frontmatter.js';
3
3
  export { toHast, nodeToHast, isSafeUrl, type MarkNode } from './to-hast.js';
4
4
  export { extractScripts, isBareAttribute, isValidScriptName, parseMetaAttributes, type ScriptBlock, } from './scripts.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // subpath instead of here so a browser bundler consuming `@markii/core` (e.g.
5
5
  // via `@markii/react`) never has to reason about Node built-ins reachable from
6
6
  // its entry point.
7
- export { parse } from './parse.js';
7
+ export { parse, isRecognizedTextDirective } from './parse.js';
8
8
  export { extractFrontmatter, extractFrontmatterUses, } from './frontmatter.js';
9
9
  export { toHast, nodeToHast, isSafeUrl } from './to-hast.js';
10
10
  export { extractScripts, isBareAttribute, isValidScriptName, parseMetaAttributes, } from './scripts.js';
package/dist/parse.d.ts CHANGED
@@ -27,3 +27,22 @@ import type { Root } from 'mdast';
27
27
  * format-defined key (`uses`) off that raw text for the simple list forms.
28
28
  */
29
29
  export declare function parse(text: string): Root;
30
+ /**
31
+ * The word-start DEMOTION rule (GitHub issue #43): whether an already
32
+ * tokenized text directive (`remark-directive` has already decided a run
33
+ * of text looks like `:name[...]`) should be KEPT as a directive or
34
+ * demoted back to plain text, per `demoteInvalidTextDirectives` above.
35
+ *
36
+ * This is scoped narrowly on purpose: it is not the rule that decides
37
+ * where a directive starts. That decision, and the colon-run and bracket
38
+ * grammar (how many colons open which directive form, what counts as a
39
+ * closing bracket), live entirely in `remark-directive` upstream and are
40
+ * not exposed here. This function only answers the one question this
41
+ * module adds on top: given a text directive `remark-directive` already
42
+ * found, does its name start with a letter, and does its colon sit at a
43
+ * word start? A third-party tokenizer (a Lezer grammar, for instance) that
44
+ * adopts this predicate gets parity on that one word-start rule and
45
+ * nothing more — it still needs its own colon-run and bracket handling to
46
+ * match `remark-directive`'s grammar.
47
+ */
48
+ export declare function isRecognizedTextDirective(name: string, colonOffset: number, source: string): boolean;
package/dist/parse.js CHANGED
@@ -94,7 +94,25 @@ function demoteInvalidTextDirectives(tree, source) {
94
94
  const STARTS_WITH_LETTER = /^[A-Za-z]/;
95
95
  /** ASCII letter-or-digit check for the character immediately before the colon. */
96
96
  const IS_WORD_CHARACTER = /[A-Za-z0-9]/;
97
- function isRecognizedTextDirective(name, colonOffset, source) {
97
+ /**
98
+ * The word-start DEMOTION rule (GitHub issue #43): whether an already
99
+ * tokenized text directive (`remark-directive` has already decided a run
100
+ * of text looks like `:name[...]`) should be KEPT as a directive or
101
+ * demoted back to plain text, per `demoteInvalidTextDirectives` above.
102
+ *
103
+ * This is scoped narrowly on purpose: it is not the rule that decides
104
+ * where a directive starts. That decision, and the colon-run and bracket
105
+ * grammar (how many colons open which directive form, what counts as a
106
+ * closing bracket), live entirely in `remark-directive` upstream and are
107
+ * not exposed here. This function only answers the one question this
108
+ * module adds on top: given a text directive `remark-directive` already
109
+ * found, does its name start with a letter, and does its colon sit at a
110
+ * word start? A third-party tokenizer (a Lezer grammar, for instance) that
111
+ * adopts this predicate gets parity on that one word-start rule and
112
+ * nothing more — it still needs its own colon-run and bracket handling to
113
+ * match `remark-directive`'s grammar.
114
+ */
115
+ export function isRecognizedTextDirective(name, colonOffset, source) {
98
116
  if (!STARTS_WITH_LETTER.test(name))
99
117
  return false;
100
118
  if (colonOffset === 0)
package/dist/scripts.d.ts CHANGED
@@ -16,6 +16,22 @@ import type { Code, Root } from 'mdast';
16
16
  * ` ```lua {src=scripts/etl.lua name=stars}` ` with an empty body"); `code`
17
17
  * is still the fence's own body (empty for a `src=` reference).
18
18
  *
19
+ * `permissions`, when present, is the script fence's own declared list of
20
+ * hostnames (` ```lua {name=gh permissions=api.github.com}` `, or
21
+ * comma-separated for more than one:
22
+ * `permissions=api.github.com,api.weather.com`). It is DISPLAY-ONLY: a
23
+ * grant prompt may show it next to the hosts a static scan actually finds
24
+ * ("declares: ..."), and a mismatch between the two is a diagnostics line,
25
+ * mirroring how a bundle manifest's `permissions.net` is already
26
+ * display-only against the same scan
27
+ * (`@markii/host`'s `script-requirements.ts`). It is NEVER an input to
28
+ * what gets granted — the literal-URL closure scan stays the only source
29
+ * of truth for that. Absent for a script with no `permissions` attribute
30
+ * at all; an empty comma-separated list (`permissions=`, or a value with
31
+ * no non-empty segment) is treated the same as absent, not as "declares
32
+ * nothing", since the format's script names are lowercase-hostname-shaped
33
+ * text with no way to write an empty entry on purpose.
34
+ *
19
35
  * `publish`, when present, is always `true` — docs/scripting.md: "**Publishing is
20
36
  * declarative**: the bare `publish` attribute on the script fence, no API to
21
37
  * call." "Bare" is load-bearing: it is set ONLY when the fence spells the
@@ -44,6 +60,8 @@ export interface ScriptBlock {
44
60
  src?: string;
45
61
  code: string;
46
62
  publish?: true;
63
+ /** See `permissions`'s doc comment above. Omitted, never an empty array, when the fence declares none. */
64
+ permissions?: string[];
47
65
  position?: Code['position'];
48
66
  }
49
67
  /**
package/dist/scripts.js CHANGED
@@ -134,6 +134,25 @@ export function isBareAttribute(meta, key) {
134
134
  }
135
135
  return bare;
136
136
  }
137
+ /**
138
+ * Splits a script fence's `permissions` attribute value into a list of
139
+ * declared hostnames: comma-separated, each segment trimmed and
140
+ * lowercased (matching `@markii/host`'s `manifestNetHosts` case-folding
141
+ * for the bundle-manifest equivalent, so the two declared-vs-scanned
142
+ * comparisons stay on the same footing), empty segments dropped. Returns
143
+ * `undefined` for a missing/empty attribute or one with no non-empty
144
+ * segment, per `ScriptBlock.permissions`'s doc comment — never an empty
145
+ * array.
146
+ */
147
+ function parsePermissions(raw) {
148
+ if (!raw)
149
+ return undefined;
150
+ const hosts = raw
151
+ .split(',')
152
+ .map((host) => host.trim().toLowerCase())
153
+ .filter((host) => host.length > 0);
154
+ return hosts.length > 0 ? hosts : undefined;
155
+ }
137
156
  /**
138
157
  * Walks a parsed mdast `Root` and returns every script block, in document
139
158
  * order. A `code` node is a script iff its `meta` carries a `{...}`
@@ -158,6 +177,9 @@ export function extractScripts(tree) {
158
177
  };
159
178
  if (attrs.src)
160
179
  block.src = attrs.src;
180
+ const permissions = parsePermissions(attrs.permissions);
181
+ if (permissions)
182
+ block.permissions = permissions;
161
183
  // Bare-only, per `ScriptBlock.publish`'s doc comment and
162
184
  // `isBareAttribute`'s: a plain `attrs.publish === ''` check can't tell
163
185
  // `{publish}` from `{publish=""}` (both flatten to `''`), so this asks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/core",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Framework-agnostic reference parser for Mark (.mk.md): CommonMark + directives to mdast, and mdast to a sanitized hast tree. Zero React dependency.",
5
5
  "keywords": [
6
6
  "markdown",