@temelj/mdx 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
  <br/>
27
27
  <br/>
28
28
 
29
- # Installation
29
+ ## Installation
30
30
 
31
31
  ```sh
32
32
  # npm
@@ -35,7 +35,82 @@ $ npm install @temelj/mdx
35
35
  $ deno add jsr:@temelj/mdx # or jsr add @temelj/mdx
36
36
  ```
37
37
 
38
- # About
38
+ ## Compile
39
+
40
+ ```ts
41
+ import { assert, assertEquals } from "@std/assert";
42
+ import {
43
+ type HastElement,
44
+ headingIdPlugin,
45
+ MdxCompiler,
46
+ syntaxHighlightPlugin,
47
+ treeProcessorPlugin,
48
+ } from "@temelj/mdx";
49
+ import { z } from "zod";
50
+
51
+ const compiler = new MdxCompiler()
52
+ .withRehypePlugin(headingIdPlugin, {
53
+ prefix: "h-",
54
+ })
55
+ .withRehypePlugin(syntaxHighlightPlugin, {
56
+ highlight: {},
57
+ lineNumbers: {},
58
+ commandLine: {},
59
+ shikiHastOptions: {
60
+ // themes: {
61
+ // light: lightTheme,
62
+ // dark: darkTheme,
63
+ // },
64
+ },
65
+ });
66
+
67
+ const source = `
68
+ ---
69
+ title: Demo
70
+ ---
71
+ # Hello
72
+
73
+ This will compile MDX to a render-able artifact.
74
+ With '@temelj/mdx-react' you can render it in an React app.
75
+
76
+ You can validate the frontmatter with a zod schema.
77
+
78
+ There are a few useful plugins:
79
+ - Use 'headingIdPlugin' to update heading tags (h1, h2, etc.) with IDs generated by slugging their text content.
80
+ This is useful for linking to headings in a page.
81
+ - Use 'syntaxHighlightPlugin' to highlight code blocks using [Shiki](https://github.com/shikijs/shiki).
82
+ You can highlight individual lines, show line numbers, or display a prompt.
83
+ - Use 'treeProcessorPlugin' to modify the HAST tree.
84
+ For example, change image URLs to point to a CDN.
85
+
86
+ \`\`\`bash {"commandLine": "0"}
87
+ whoamai
88
+ @temelj/mdx
89
+ \`\`\`
90
+ `.trim();
91
+ const artifact = await compiler.compile(source, {
92
+ frontmatterOnly: false,
93
+ frontmatterSchema: z.object({
94
+ title: z.string(),
95
+ }),
96
+ mdxOptions: {
97
+ rehypePlugins: [
98
+ [
99
+ treeProcessorPlugin,
100
+ {
101
+ process: (node: HastElement) => assert(typeof node.type === "string"),
102
+ },
103
+ ],
104
+ ],
105
+ },
106
+ });
107
+ assert(artifact.compiled !== undefined);
108
+ assertEquals(artifact.frontmatter, {
109
+ title: "Demo",
110
+ });
111
+ ```
112
+
113
+ ## About
39
114
 
40
115
  This package is part of [Temelj](https://github.com/flinect/temelj) - a big
41
116
  standard library for TypeScript.
package/esm/compiler.js CHANGED
@@ -4,6 +4,9 @@ import remarkGfmPlugin from "remark-gfm";
4
4
  import { VFile } from "vfile";
5
5
  import { matter } from "vfile-matter";
6
6
  export { remarkFrontmatterPlugin, remarkGfmPlugin };
7
+ /**
8
+ * A reusable compiler for compiling MDX.
9
+ */
7
10
  export class MdxCompiler {
8
11
  constructor(mdxOptions = {}) {
9
12
  Object.defineProperty(this, "mdxOptions", {
@@ -46,7 +49,7 @@ export class MdxCompiler {
46
49
  }
47
50
  return this;
48
51
  }
49
- async compile(source, { frontmatterOnly = false, mdxOptions } = {}) {
52
+ async compile(source, { frontmatterOnly = false, frontmatterSchema, mdxOptions } = {}) {
50
53
  const vfile = new VFile({
51
54
  value: source,
52
55
  // Needed so vfile doesn't need to access fs.
@@ -66,17 +69,21 @@ export class MdxCompiler {
66
69
  }
67
70
  let compiled;
68
71
  if (!frontmatterOnly) {
69
- compiled = await compileMdxJs(vfile, {
72
+ const compiledFile = await compileMdxJs(vfile, {
70
73
  outputFormat: "function-body",
71
74
  ...(this.mdxOptions ?? {}),
72
75
  ...(mdxOptions ?? {}),
73
76
  remarkPlugins,
74
77
  rehypePlugins,
75
78
  });
79
+ compiled = compiledFile.value.toString();
76
80
  }
81
+ const frontmatter = frontmatterSchema === undefined
82
+ ? (vfile.data.matter ?? {})
83
+ : frontmatterSchema.parse(vfile.data.matter ?? {});
77
84
  return {
78
- compiled: compiled?.value.toString(),
79
- frontmatter: (vfile.data.matter ?? {}),
85
+ compiled,
86
+ frontmatter,
80
87
  };
81
88
  }
82
89
  }
@@ -6,8 +6,8 @@ export function extractCodeMeta(node) {
6
6
  }
7
7
  const metaSchema = z.object({
8
8
  highlight: z.string().optional(),
9
- showLineNumbers: z.boolean().default(false),
10
- commandLine: z.string().optional(),
9
+ showLineNumbers: z.boolean().or(z.string()).optional(),
10
+ commandLine: z.boolean().or(z.string()).optional(),
11
11
  fileName: z.string().optional(),
12
12
  });
13
13
  function parseMetaString(meta) {
@@ -43,7 +43,7 @@ function parseMetaString(meta) {
43
43
  options.commandLine = { commandRange: [] };
44
44
  }
45
45
  }
46
- if (typeof parsed.fileName === "string") {
46
+ if (parsed.fileName !== undefined) {
47
47
  options.fileName = parsed.fileName.trim();
48
48
  }
49
49
  return options;
@@ -4,6 +4,9 @@ import { visit } from "unist-util-visit";
4
4
  import { transformerNotationHighlight, } from "@shikijs/transformers";
5
5
  import { numericRangeContains } from "@temelj/iterator";
6
6
  import { extractCodeMeta } from "./code-meta.js";
7
+ /**
8
+ * A plugin that highlights code blocks using Shiki.
9
+ */
7
10
  export function syntaxHighlightPlugin(options = {}) {
8
11
  return async (tree) => {
9
12
  const promises = [];
@@ -31,7 +34,7 @@ export function syntaxHighlightPlugin(options = {}) {
31
34
  light: "github-light-default",
32
35
  dark: "github-dark-default",
33
36
  },
34
- ...(options.shikiCodeToHastOptions ?? {}),
37
+ ...(options.shikiHastOptions ?? {}),
35
38
  };
36
39
  if (hastOptions.transformers === undefined) {
37
40
  hastOptions.transformers = [];
@@ -62,7 +65,8 @@ export function syntaxHighlightPlugin(options = {}) {
62
65
  }
63
66
  if (options.commandLine &&
64
67
  meta.commandLine &&
65
- numericRangeContains(meta.commandLine.commandRange, lineIndex)) {
68
+ (meta.commandLine.commandRange.length === 0 ||
69
+ numericRangeContains(meta.commandLine.commandRange, lineIndex))) {
66
70
  this.addClassToHast(line, options.commandLine.className ?? "line-command");
67
71
  }
68
72
  lineIndex++;
@@ -1,4 +1,8 @@
1
1
  import { visit } from "unist-util-visit";
2
+ /**
3
+ * A plugin that allows to process MDX tree nodes.
4
+ * It can be async or sync.
5
+ */
2
6
  export function treeProcessorPlugin({ process } = { process: () => { } }) {
3
7
  return async (tree) => {
4
8
  const promises = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temelj/mdx",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Utilities for working with MDX.",
5
5
  "keywords": [
6
6
  "mdx"
@@ -1,17 +1,32 @@
1
1
  import { type CompileOptions as MdxJsCompileOptions } from "@mdx-js/mdx";
2
2
  import remarkFrontmatterPlugin from "remark-frontmatter";
3
3
  import remarkGfmPlugin from "remark-gfm";
4
+ import type { z } from "zod";
4
5
  import type { PluginFactory } from "./types.js";
5
6
  export { remarkFrontmatterPlugin, remarkGfmPlugin };
7
+ /**
8
+ * Represents the source content for an MDX document.
9
+ */
6
10
  export type MdxSource = string | Uint8Array;
7
- export interface MdxCompileOptions {
11
+ /**
12
+ * Options for compiling an MDX document.
13
+ */
14
+ export interface MdxCompileOptions<TFrontmatter extends z.ZodSchema> {
8
15
  frontmatterOnly?: boolean | undefined;
16
+ frontmatterSchema?: TFrontmatter;
9
17
  mdxOptions?: MdxJsCompileOptions;
10
18
  }
19
+ /**
20
+ * The result of compiling an MDX document.
21
+ * Compiled content is optional, because sometimes we only need the frontmatter.
22
+ */
11
23
  export interface MdxArtifact<TFrontmatter = Record<string, unknown>> {
12
24
  compiled?: string | undefined;
13
25
  frontmatter: TFrontmatter;
14
26
  }
27
+ /**
28
+ * A reusable compiler for compiling MDX.
29
+ */
15
30
  export declare class MdxCompiler {
16
31
  private readonly mdxOptions;
17
32
  private readonly remarkPlugins;
@@ -19,6 +34,6 @@ export declare class MdxCompiler {
19
34
  constructor(mdxOptions?: MdxJsCompileOptions);
20
35
  withRemarkPlugin<TOptions>(plugin: (options?: TOptions) => PluginFactory, options?: TOptions): MdxCompiler;
21
36
  withRehypePlugin<TOptions>(plugin: (options?: TOptions) => PluginFactory, options?: TOptions): MdxCompiler;
22
- compile<TFrontmatter = Record<string, unknown>>(source: MdxSource, { frontmatterOnly, mdxOptions }?: MdxCompileOptions): Promise<MdxArtifact<TFrontmatter>>;
37
+ compile<TFrontmatterSchema extends z.ZodSchema>(source: MdxSource, { frontmatterOnly, frontmatterSchema, mdxOptions }?: MdxCompileOptions<TFrontmatterSchema>): Promise<MdxArtifact<z.output<TFrontmatterSchema>>>;
23
38
  }
24
39
  //# sourceMappingURL=compiler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,IAAI,mBAAmB,EAC3C,MAAM,aAAa,CAAC;AACrB,OAAO,uBAAuB,MAAM,oBAAoB,CAAC;AACzD,OAAO,eAAe,MAAM,YAAY,CAAC;AAKzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,CAAC;AAEpD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAE5C,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,WAAW,CAAC,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,EAAE,YAAY,CAAC;CAC3B;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;gBAG5C,UAAU,GAAE,mBAAwB;IAO/B,gBAAgB,CAAC,QAAQ,EAC9B,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,KAAK,aAAa,EAC7C,OAAO,CAAC,EAAE,QAAQ,GACjB,WAAW;IASP,gBAAgB,CAAC,QAAQ,EAC9B,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,KAAK,aAAa,EAC7C,OAAO,CAAC,EAAE,QAAQ,GACjB,WAAW;IASD,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,MAAM,EAAE,SAAS,EACjB,EAAE,eAAuB,EAAE,UAAU,EAAE,GAAE,iBAAsB,GAC9D,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;CAqCtC"}
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../src/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,IAAI,mBAAmB,EAC3C,MAAM,aAAa,CAAC;AACrB,OAAO,uBAAuB,MAAM,oBAAoB,CAAC;AACzD,OAAO,eAAe,MAAM,YAAY,CAAC;AAIzC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAChC,YAAY,SAAS,CAAC,CAAC,SAAS;IAEhC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAEtC,iBAAiB,CAAC,EAAE,YAAY,CAAC;IACjC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,EAAE,YAAY,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;gBAG5C,UAAU,GAAE,mBAAwB;IAO/B,gBAAgB,CAAC,QAAQ,EAC9B,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,KAAK,aAAa,EAC7C,OAAO,CAAC,EAAE,QAAQ,GACjB,WAAW;IASP,gBAAgB,CAAC,QAAQ,EAC9B,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,KAAK,aAAa,EAC7C,OAAO,CAAC,EAAE,QAAQ,GACjB,WAAW;IASD,OAAO,CAClB,kBAAkB,SAAS,CAAC,CAAC,SAAS,EAEtC,MAAM,EAAE,SAAS,EACjB,EAAE,eAAuB,EAAE,iBAAiB,EAAE,UAAU,EAAE,GACxD,iBAAiB,CAAC,kBAAkB,CAAM,GAC3C,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC;CA0CtD"}
@@ -1,24 +1,52 @@
1
1
  import { type BundledLanguage, type BundledTheme, type CodeToHastOptions } from "shiki";
2
2
  import { type TransformerNotationHighlightOptions } from "@shikijs/transformers";
3
3
  import type { PluginFactory } from "../../types.js";
4
- type ShikiCodeToHastOptions = Partial<CodeToHastOptions<BundledLanguage, BundledTheme>>;
4
+ type ShikiHastOptions = Partial<CodeToHastOptions<BundledLanguage, BundledTheme>>;
5
5
  /**
6
6
  * Options for {@linkcode syntaxHighlightPlugin}.
7
7
  */
8
8
  export interface SyntaxHighlightPluginOptions {
9
+ /**
10
+ * A prefix for the language class name on the code tag.
11
+ */
9
12
  languageClassNamePrefix?: string;
13
+ /**
14
+ * Options for the highlight transformer.
15
+ */
10
16
  highlight?: {
11
17
  transformer?: TransformerNotationHighlightOptions;
12
18
  };
19
+ /**
20
+ * Options for line numbers.
21
+ */
13
22
  lineNumbers?: {
23
+ /**
24
+ * The name of the variable that contains the line number width.
25
+ * This is used for padding the line numbers so that they align.
26
+ */
14
27
  widthVariableName?: string;
28
+ /**
29
+ * A class name to be added to the line numbers.
30
+ */
15
31
  className?: string;
16
32
  };
33
+ /**
34
+ * Options for command line.
35
+ */
17
36
  commandLine?: {
37
+ /**
38
+ * A class name to be added to the prompt line.
39
+ */
18
40
  className?: string;
19
41
  };
20
- shikiCodeToHastOptions?: ShikiCodeToHastOptions;
42
+ /**
43
+ * Extra options for the Shiki hast transformer.
44
+ */
45
+ shikiHastOptions?: ShikiHastOptions;
21
46
  }
47
+ /**
48
+ * A plugin that highlights code blocks using Shiki.
49
+ */
22
50
  export declare function syntaxHighlightPlugin(options?: SyntaxHighlightPluginOptions): PluginFactory;
23
51
  export {};
24
52
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/syntax-highlight/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EACvB,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,KAAK,mCAAmC,EACzC,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAyB,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG3E,KAAK,sBAAsB,GAAG,OAAO,CACnC,iBAAiB,CAAC,eAAe,EAAE,YAAY,CAAC,CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,WAAW,CAAC,EAAE,mCAAmC,CAAC;KACnD,CAAC;IACF,WAAW,CAAC,EAAE;QACZ,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,WAAW,CAAC,EAAE;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACjD;AAED,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,aAAa,CAqJf"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/syntax-highlight/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EACvB,MAAM,OAAO,CAAC;AAEf,OAAO,EAEL,KAAK,mCAAmC,EACzC,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAyB,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG3E,KAAK,gBAAgB,GAAG,OAAO,CAC7B,iBAAiB,CAAC,eAAe,EAAE,YAAY,CAAC,CACjD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;OAEG;IACH,SAAS,CAAC,EAAE;QACV,WAAW,CAAC,EAAE,mCAAmC,CAAC;KACnD,CAAC;IACF;;OAEG;IACH,WAAW,CAAC,EAAE;QACZ;;;WAGG;QACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B;;WAEG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;OAEG;IACH,WAAW,CAAC,EAAE;QACZ;;WAEG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,4BAAiC,GACzC,aAAa,CAsJf"}
@@ -7,6 +7,10 @@ type TreeProcessorPluginOptions = {
7
7
  /** The processor. */
8
8
  process: MdxTreeProcessor;
9
9
  };
10
+ /**
11
+ * A plugin that allows to process MDX tree nodes.
12
+ * It can be async or sync.
13
+ */
10
14
  export declare function treeProcessorPlugin({ process }?: TreeProcessorPluginOptions): PluginFactory;
11
15
  export {};
12
16
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/tree-processor/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAY,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;GAEG;AACH,KAAK,0BAA0B,GAAG;IAChC,qBAAqB;IACrB,OAAO,EAAE,gBAAgB,CAAC;CAC3B,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,EAAE,OAAO,EAAE,GAAE,0BAAkD,GAC9D,aAAa,CAuBf"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/tree-processor/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAY,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;GAEG;AACH,KAAK,0BAA0B,GAAG;IAChC,qBAAqB;IACrB,OAAO,EAAE,gBAAgB,CAAC;CAC3B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,OAAO,EAAE,GAAE,0BAAkD,GAC9D,aAAa,CAuBf"}