react-lib-tools 0.0.1

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/package.json ADDED
@@ -0,0 +1,110 @@
1
+ {
2
+ "name": "react-lib-tools",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "author": "Brian Vaughn <brian.david.vaughn@gmail.com> (https://github.com/bvaughn/)",
6
+ "contributors": [
7
+ "Brian Vaughn <brian.david.vaughn@gmail.com> (https://github.com/bvaughn/)"
8
+ ],
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/bvaughn/react-lib-tools.git"
13
+ },
14
+ "main": "dist/react-lib-tools.cjs",
15
+ "module": "dist/react-lib-tools.js",
16
+ "types": "dist/react-lib-tools.d.ts",
17
+ "files": [
18
+ "dist",
19
+ "scripts",
20
+ "styles.css"
21
+ ],
22
+ "scripts": {
23
+ "dev": "vite",
24
+ "build": "pnpm run build:lib && pnpm run build:docs",
25
+ "build:docs": "TARGET=docs vite build",
26
+ "build:lib": "TARGET=lib vite build",
27
+ "lint": "eslint .",
28
+ "prerelease": "rm -rf dist && pnpm run build:lib",
29
+ "prettier": "prettier --write \"**/*.{css,html,js,json,jsx,ts,tsx}\"",
30
+ "prettier:ci": "prettier --check \"**/*.{css,html,js,json,jsx,ts,tsx}\"",
31
+ "preview": "vite preview",
32
+ "test": "vitest",
33
+ "test:ci": "vitest run",
34
+ "test:debug": "vitest --inspect-brk=127.0.0.1:3000 --no-file-parallelism",
35
+ "tsc": "tsc -b"
36
+ },
37
+ "lint-staged": {
38
+ "**/*": "prettier --write --ignore-unknown"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "^18.0.0 || ^19.0.0",
42
+ "react-dom": "^18.0.0 || ^19.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@codemirror/lang-css": "latest",
46
+ "@codemirror/lang-html": "latest",
47
+ "@codemirror/lang-javascript": "latest",
48
+ "@codemirror/lang-markdown": "latest",
49
+ "@codemirror/language": "latest",
50
+ "@codemirror/state": "latest",
51
+ "@csstools/postcss-oklab-function": "^4.0.11",
52
+ "@eslint/js": "^9.30.1",
53
+ "@headlessui/react": "^2.2.4",
54
+ "@headlessui/tailwindcss": "^0.2.2",
55
+ "@heroicons/react": "^2.2.0",
56
+ "@lezer/highlight": "latest",
57
+ "@tailwindcss/vite": "^4.1.11",
58
+ "@tailwindplus/elements": "^1.0.5",
59
+ "@testing-library/jest-dom": "^6.6.4",
60
+ "@testing-library/react": "^16.3.0",
61
+ "@testing-library/user-event": "^14.6.1",
62
+ "@ts-ast-parser/core": "^0.8.0",
63
+ "@types/compression": "^1.8.1",
64
+ "@types/express": "^5.0.5",
65
+ "@types/markdown-it": "^14.1.2",
66
+ "@types/node": "^24.2.0",
67
+ "@types/react": "^19.1.8",
68
+ "@types/react-dom": "^19.2.3",
69
+ "@vitejs/plugin-react-swc": "^3.10.2",
70
+ "clsx": "^2.1.1",
71
+ "compression": "^1.8.1",
72
+ "csstype": "^3.1.3",
73
+ "eslint": "^9.30.1",
74
+ "eslint-plugin-react-hooks": "^5.2.0",
75
+ "eslint-plugin-react-refresh": "^0.4.20",
76
+ "express": "^5.1.0",
77
+ "globals": "^16.3.0",
78
+ "husky": "^9.1.7",
79
+ "jsdom": "^26.1.0",
80
+ "lint-staged": "^16.1.4",
81
+ "markdown-it": "^14.1.0",
82
+ "marked": "^16.4.1",
83
+ "postcss": "^8.5.6",
84
+ "prettier": "3.6.2",
85
+ "prettier-plugin-tailwindcss": "^0.7.1",
86
+ "react": "^19.2.3",
87
+ "react-docgen-typescript": "^2.4.0",
88
+ "react-dom": "^19.2.3",
89
+ "react-error-boundary": "^6.0.0",
90
+ "react-router-dom": "^7.6.3",
91
+ "rollup-plugin-terser": "^7.0.2",
92
+ "rollup-plugin-visualizer": "^6.0.3",
93
+ "rollup-preserve-directives": "^1.1.3",
94
+ "sirv": "^3.0.2",
95
+ "tailwind-merge": "^3.3.1",
96
+ "tailwindcss": "^4.1.11",
97
+ "terser": "^5.43.1",
98
+ "ts-blank-space": "^0.6.2",
99
+ "ts-node": "^10.9.2",
100
+ "typescript": "~5.8.3",
101
+ "typescript-eslint": "^8.35.1",
102
+ "typescript-json-schema": "^0.65.1",
103
+ "vite": "^7.0.4",
104
+ "vitest-fail-on-console": "^0.10.1",
105
+ "vite-plugin-dts": "^4.5.4",
106
+ "vite-plugin-svgr": "^4.3.0",
107
+ "vitest": "^3.2.4",
108
+ "zustand": "^5.0.7"
109
+ }
110
+ }
@@ -0,0 +1,22 @@
1
+ import { compileComponents } from "./utils/docs/compileComponents.ts";
2
+ import { compileImperativeHandles } from "./utils/docs/compileImperativeHandles.ts";
3
+
4
+ export async function compileDocs({
5
+ componentNames,
6
+ imperativeHandleNames,
7
+ outputDirName = "docs"
8
+ }: {
9
+ componentNames: string[];
10
+ imperativeHandleNames: string[];
11
+ outputDirName?: string | undefined;
12
+ }) {
13
+ await compileComponents({
14
+ componentNames,
15
+ outputDirName
16
+ });
17
+
18
+ await compileImperativeHandles({
19
+ names: imperativeHandleNames,
20
+ outputDirName
21
+ });
22
+ }
@@ -0,0 +1,71 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ import { initialize } from "./utils/initialize.ts";
4
+ import { syntaxHighlight } from "./utils/syntax-highlight.ts";
5
+ import { trimExcludedText } from "./utils/examples/trimExcludedText.ts";
6
+
7
+ export async function compileExamples({
8
+ fileExtensions = [".html", ".ts", ".tsx"],
9
+ inputPath = ["src", "routes"],
10
+ outputDirName = "examples"
11
+ }: {
12
+ fileExtensions?: string[] | undefined;
13
+ inputPath?: string[] | undefined;
14
+ outputDirName?: string | undefined;
15
+ } = {}) {
16
+ const { files, outputDir } = await initialize({
17
+ fileExtensions,
18
+ inputPath,
19
+ outputDirName
20
+ });
21
+
22
+ for (const file of files) {
23
+ const buffer = await readFile(file);
24
+
25
+ let rawText = buffer.toString();
26
+
27
+ {
28
+ // Remove special comments and directives before syntax highlighting
29
+ rawText = trimExcludedText(rawText);
30
+
31
+ rawText = rawText
32
+ .split("\n")
33
+ .filter(
34
+ (line) =>
35
+ !line.includes("prettier-ignore") &&
36
+ !line.includes("eslint-disable-next-line") &&
37
+ !line.includes("@ts-expect-error") &&
38
+ !line.includes("// hidden")
39
+ )
40
+ .join("\n");
41
+ }
42
+
43
+ let html;
44
+ if (file.endsWith(".html")) {
45
+ html = await syntaxHighlight(rawText, "HTML");
46
+ } else if (file.endsWith(".js") || file.endsWith(".jsx")) {
47
+ html = await syntaxHighlight(
48
+ rawText,
49
+ file.endsWith("jsx") ? "JSX" : "JS"
50
+ );
51
+ } else if (file.endsWith(".ts") || file.endsWith(".tsx")) {
52
+ html = await syntaxHighlight(
53
+ rawText,
54
+ file.endsWith("tsx") ? "TSX" : "TS"
55
+ );
56
+ } else {
57
+ throw Error(`Unsupported file type: ${file}`);
58
+ }
59
+
60
+ const fileName = basename(file);
61
+
62
+ const outputFile = join(
63
+ outputDir,
64
+ fileName.replace(/\.example\..+$/, ".json")
65
+ );
66
+
67
+ console.debug("Writing to", outputFile);
68
+
69
+ await writeFile(outputFile, JSON.stringify({ html }, null, 2));
70
+ }
71
+ }
@@ -0,0 +1,116 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ import { cwd } from "node:process";
4
+ import { type FileParser, type PropItem } from "react-docgen-typescript";
5
+ import type { ComponentMetadata } from "../../../lib/types.ts";
6
+ import { assert } from "../../../lib/utils/assert.ts";
7
+ import { syntaxHighlight } from "../syntax-highlight.ts";
8
+ import { getPropTypeText } from "./getPropTypeText.ts";
9
+ import { parseDescription } from "./parseDescription.ts";
10
+ import { propsToTable } from "./propsToTable.ts";
11
+
12
+ const TOKEN_TO_REPLACE = "TOKEN_TO_REPLACE";
13
+
14
+ export async function compileComponent({
15
+ filePath,
16
+ outputDir,
17
+ parser
18
+ }: {
19
+ filePath: string;
20
+ outputDir: string;
21
+ parser: FileParser;
22
+ }) {
23
+ const parsed = parser.parse(filePath);
24
+ assert(
25
+ parsed.length === 1,
26
+ `Expected 1 parsed component but found ${parsed.length}`
27
+ );
28
+
29
+ const component = parsed[0];
30
+
31
+ // Convert to local paths
32
+ component.filePath = relative(cwd(), filePath);
33
+
34
+ // Filter inherited HTML attributes
35
+ for (const key in component.props) {
36
+ const prop = component.props[key];
37
+ if (
38
+ prop.declarations?.filter(
39
+ (declaration) => !declaration.fileName.includes("node_modules")
40
+ ).length === 0
41
+ ) {
42
+ delete component.props[key];
43
+ }
44
+ }
45
+
46
+ // Generate syntax highlighted HTML for prop types
47
+ {
48
+ const componentMetadata: ComponentMetadata = {
49
+ description: await parseDescription(component.description),
50
+ filePath: component.filePath,
51
+ name: component.displayName,
52
+ props: {}
53
+ };
54
+
55
+ for (const name in component.props) {
56
+ const prop = component.props[name];
57
+
58
+ let textToFormat = getPropTypeText(prop);
59
+
60
+ if (prop.defaultValue?.value) {
61
+ const formattedValue =
62
+ typeof prop.defaultValue.value === "string"
63
+ ? `"${prop.defaultValue.value}"`
64
+ : prop.defaultValue.value;
65
+
66
+ textToFormat = `${textToFormat} = ${formattedValue}`;
67
+ }
68
+
69
+ // Format with a placeholder token so we can replace it with a formatted string
70
+ textToFormat = `${TOKEN_TO_REPLACE}${prop.required ? "" : "?"}: ${textToFormat}`;
71
+
72
+ try {
73
+ let html = await syntaxHighlight(textToFormat, "TS");
74
+ html = html.replace(
75
+ TOKEN_TO_REPLACE,
76
+ `<span class="tok-propertyName">${name}</span>`
77
+ );
78
+
79
+ componentMetadata.props[name] = {
80
+ description: await parseDescription(prop.description),
81
+ html,
82
+ name,
83
+ required: prop.required
84
+ };
85
+ } catch (error) {
86
+ console.error(error);
87
+ }
88
+ }
89
+
90
+ const outputFile = join(outputDir, `${component.displayName}.json`);
91
+
92
+ console.debug("Writing to", outputFile);
93
+
94
+ await writeFile(outputFile, JSON.stringify(componentMetadata, null, 2));
95
+ }
96
+
97
+ // Generate markdown for prop types
98
+ const requiredProps: PropItem[] = [];
99
+ const optionalProps: PropItem[] = [];
100
+
101
+ for (const propName in component.props) {
102
+ const prop = component.props[propName];
103
+ if (prop.required) {
104
+ requiredProps.push(prop);
105
+ } else {
106
+ optionalProps.push(prop);
107
+ }
108
+ }
109
+
110
+ return {
111
+ componentName: component.displayName,
112
+ description: component.description,
113
+ optionalPropsTable: await propsToTable(optionalProps),
114
+ requiredPropsTable: await propsToTable(requiredProps)
115
+ };
116
+ }
@@ -0,0 +1,74 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { cwd } from "node:process";
4
+ import { withCustomConfig } from "react-docgen-typescript";
5
+ import { initialize } from "../initialize.ts";
6
+ import { compileComponent } from "./compileComponent.ts";
7
+ import { insertPropsMarkdown } from "./insertPropsMarkdown.ts";
8
+
9
+ export async function compileComponents({
10
+ componentNames,
11
+ outputDirName
12
+ }: {
13
+ componentNames: string[];
14
+ outputDirName: string;
15
+ }) {
16
+ const parser = withCustomConfig("./tsconfig.json", {
17
+ savePropValueAsString: true,
18
+ shouldExtractLiteralValuesFromEnum: true,
19
+ shouldExtractValuesFromUnion: true,
20
+ shouldRemoveUndefinedFromOptional: true
21
+ });
22
+
23
+ const { files, outputDir } = await initialize({
24
+ fileExtensions: [".ts", ".tsx"],
25
+ fileFilter: (file) =>
26
+ componentNames.some((componentName) => file.endsWith(componentName)),
27
+ inputPath: ["lib", "components"],
28
+ outputDirName
29
+ });
30
+
31
+ const markdownPath = join(cwd(), "README.md");
32
+
33
+ let markdown = await readFile(markdownPath, { encoding: "utf-8" });
34
+
35
+ await Promise.all(
36
+ files.map((filePath) =>
37
+ compileComponent({
38
+ filePath,
39
+ outputDir,
40
+ parser
41
+ }).then(
42
+ ({
43
+ componentName,
44
+ description,
45
+ optionalPropsTable,
46
+ requiredPropsTable
47
+ }) => {
48
+ markdown = insertPropsMarkdown({
49
+ componentMarkdown: description,
50
+ componentName,
51
+ markdown,
52
+ section: "description"
53
+ });
54
+
55
+ markdown = insertPropsMarkdown({
56
+ componentMarkdown: requiredPropsTable,
57
+ componentName,
58
+ markdown,
59
+ section: "required-props"
60
+ });
61
+
62
+ markdown = insertPropsMarkdown({
63
+ componentMarkdown: optionalPropsTable,
64
+ componentName,
65
+ markdown,
66
+ section: "optional-props"
67
+ });
68
+ }
69
+ )
70
+ )
71
+ );
72
+
73
+ await writeFile(markdownPath, markdown);
74
+ }
@@ -0,0 +1,48 @@
1
+ import type { InterfaceNode } from "@ts-ast-parser/core";
2
+ import assert from "node:assert";
3
+ import { writeFile } from "node:fs/promises";
4
+ import { join } from "path";
5
+ import type { ImperativeHandleMetadata } from "../../../lib/types.ts";
6
+ import { syntaxHighlight } from "../syntax-highlight.ts";
7
+ import { parseDescription } from "./parseDescription.ts";
8
+
9
+ export async function compileImperativeHandle({
10
+ filePath,
11
+ interfaceNode,
12
+ outputDir
13
+ }: {
14
+ filePath: string;
15
+ interfaceNode: InterfaceNode;
16
+ outputDir: string;
17
+ }) {
18
+ const name = interfaceNode.getName();
19
+
20
+ const json: ImperativeHandleMetadata = {
21
+ description: await parseDescription(
22
+ "" + interfaceNode.getJSDoc().getTag("description")?.text
23
+ ),
24
+ filePath,
25
+ methods: [],
26
+ name
27
+ };
28
+
29
+ const methods = interfaceNode.getMethods();
30
+ for (const method of methods) {
31
+ const jsDoc = method.getJSDoc();
32
+ assert(jsDoc);
33
+
34
+ json.methods.push({
35
+ description: await parseDescription(
36
+ "" + jsDoc.getTag("description")?.text
37
+ ),
38
+ html: await syntaxHighlight(method.getTSNode().getText(), "TS"),
39
+ name: method.getName()
40
+ });
41
+ }
42
+
43
+ const outputFile = join(outputDir, `${name}.json`);
44
+
45
+ console.debug("Writing to", outputFile);
46
+
47
+ await writeFile(outputFile, JSON.stringify(json, null, 2));
48
+ }
@@ -0,0 +1,45 @@
1
+ import { parseFromProject, type InterfaceNode } from "@ts-ast-parser/core";
2
+ import tsConfig from "../../../tsconfig.json" with { type: "json" };
3
+ import { compileImperativeHandle } from "./compileImperativeHandle.ts";
4
+ import { join } from "node:path";
5
+ import { cwd } from "node:process";
6
+
7
+ export async function compileImperativeHandles({
8
+ names,
9
+ outputDirName
10
+ }: {
11
+ names: string[];
12
+ outputDirName: string;
13
+ }) {
14
+ const outputDir = join(cwd(), "public", "generated", outputDirName);
15
+
16
+ const result = await parseFromProject(tsConfig);
17
+ const reflectedModules = result.project?.getModules() ?? [];
18
+
19
+ const nodes: {
20
+ filePath: string;
21
+ node: InterfaceNode;
22
+ }[] = [];
23
+
24
+ names.forEach((name) => {
25
+ reflectedModules.forEach((reflectedModule) => {
26
+ const node = reflectedModule.getDeclarationByName(name);
27
+ if (node) {
28
+ nodes.push({
29
+ filePath: reflectedModule.getSourcePath(),
30
+ node: node as unknown as InterfaceNode
31
+ });
32
+ }
33
+ });
34
+ });
35
+
36
+ await Promise.all(
37
+ nodes.map(({ filePath, node }) =>
38
+ compileImperativeHandle({
39
+ filePath,
40
+ interfaceNode: node,
41
+ outputDir
42
+ })
43
+ )
44
+ );
45
+ }
@@ -0,0 +1,11 @@
1
+ import Markdown from "markdown-it";
2
+
3
+ let processor: Markdown | undefined = undefined;
4
+
5
+ export function formatDescriptionText(text: string) {
6
+ if (processor === undefined) {
7
+ processor = new Markdown();
8
+ }
9
+
10
+ return processor.render(text);
11
+ }
@@ -0,0 +1,28 @@
1
+ import type { PropItem } from "react-docgen-typescript";
2
+
3
+ export function getPropTypeText(prop: PropItem) {
4
+ let textToFormat = prop.type.raw;
5
+ if (!textToFormat && prop.type.name.includes(":")) {
6
+ // Edge case where some prop types aren't registered as containing raw TS
7
+ textToFormat = prop.type.name;
8
+
9
+ // List/Grid and rowComponent/cellComponent are annotated with a return type of ReactElement instead of ReactNode
10
+ // As a result of this change the generated docs are significantly less readable, so tidy them up here
11
+ // See github.com/bvaughn/react-window/issues/875
12
+ textToFormat = textToFormat.replace(
13
+ "ReactElement<unknown, string | JSXElementConstructor<...>>",
14
+ "ReactNode"
15
+ );
16
+ }
17
+
18
+ if (!textToFormat) {
19
+ textToFormat = `${prop.type.name}`;
20
+
21
+ const match = textToFormat.match(/ExcludeForbiddenKeys<([^>]+)>/);
22
+ if (match) {
23
+ textToFormat = match[1];
24
+ }
25
+ }
26
+
27
+ return textToFormat;
28
+ }
@@ -0,0 +1,29 @@
1
+ export function insertPropsMarkdown({
2
+ componentMarkdown,
3
+ componentName,
4
+ markdown,
5
+ section
6
+ }: {
7
+ componentMarkdown: string;
8
+ componentName: string;
9
+ markdown: string;
10
+ section: string;
11
+ }) {
12
+ const flag = `${componentName}:${section}`;
13
+ const startToken = `<!-- ${flag}:begin -->`;
14
+ const stopToken = `<!-- ${flag}:end -->`;
15
+
16
+ const startIndex = markdown.indexOf(startToken) + startToken.length;
17
+ const stopIndex = markdown.indexOf(stopToken);
18
+ if (startIndex < 0 || stopIndex < 0) {
19
+ throw Error("Parsing README failed");
20
+ }
21
+
22
+ return (
23
+ markdown.substring(0, startIndex) +
24
+ "\n" +
25
+ (componentMarkdown || "None") +
26
+ "\n" +
27
+ markdown.substring(stopIndex)
28
+ );
29
+ }
@@ -0,0 +1,59 @@
1
+ import type { Intent, Section } from "../../../lib/types.ts";
2
+ import { formatDescriptionText } from "./formatDescriptionText.ts";
3
+ import { syntaxHighlight, type Language } from "../syntax-highlight.ts";
4
+
5
+ export async function parseDescription(rawText: string) {
6
+ const sections: Section[] = [];
7
+
8
+ // Paper over differences between "@ts-ast-parser/core" and "react-docgen-typescript"
9
+ let text = rawText;
10
+ Object.keys(INTENT_FLAGS).forEach((flag) => {
11
+ text = text
12
+ .split(`\n${flag}`)
13
+ .join(`\n\n${flag}`)
14
+ .replaceAll("\n\n\n", "\n\n");
15
+ });
16
+
17
+ for (const chunk of text.split("\n\n")) {
18
+ let content = "";
19
+ let intent: Intent | undefined = undefined;
20
+
21
+ if (chunk.startsWith("```")) {
22
+ const match = chunk.match(/^```([a-z]+)/)!;
23
+ const language = match[1].toUpperCase() as Language;
24
+
25
+ content = await syntaxHighlight(
26
+ chunk.substring(language.length + 3, chunk.length - 3).trim(),
27
+ language
28
+ );
29
+ } else {
30
+ content = formatDescriptionText(chunk.trim());
31
+
32
+ for (const char in INTENT_FLAGS) {
33
+ if (content.startsWith(`<p>${char} `)) {
34
+ intent = INTENT_FLAGS[char as keyof typeof INTENT_FLAGS] as Intent;
35
+ content = content.replace(`<p>${char} `, "<p>");
36
+ }
37
+ }
38
+ }
39
+
40
+ // Strip TSDoc comments
41
+ content = content.replace(/\n@param.+/, "");
42
+ content = content.replace(/\n@return.+/, "");
43
+
44
+ sections.push({
45
+ content,
46
+ intent
47
+ });
48
+ }
49
+
50
+ return sections;
51
+ }
52
+
53
+ const INTENT_FLAGS = {
54
+ "❌": "danger",
55
+ "NOTE:": "none",
56
+ ℹ️: "primary",
57
+ "✅": "success",
58
+ "⚠️": "warning"
59
+ };
@@ -0,0 +1,48 @@
1
+ import { marked } from "marked";
2
+ import type { PropItem } from "react-docgen-typescript";
3
+ import { getPropTypeText } from "./getPropTypeText.ts";
4
+
5
+ const TABLE_TAG_START = `
6
+ <table>
7
+ <thead>
8
+ <tr>
9
+ <th>Name</th>
10
+ <th>Description</th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>`;
14
+
15
+ const PROP_ROW = `
16
+ <tr>
17
+ <td>[[name]]</td>
18
+ <td>[[description]]</td>
19
+ </tr>`;
20
+
21
+ const TABLE_TAG_STOP = `
22
+ </tbody>
23
+ </table>
24
+ `;
25
+
26
+ export async function propsToTable(props: PropItem[]) {
27
+ const htmlStrings = [];
28
+
29
+ if (props.length > 0) {
30
+ htmlStrings.push(TABLE_TAG_START);
31
+
32
+ for (const prop of props) {
33
+ const type = getPropTypeText(prop);
34
+
35
+ const description = await marked(prop.description);
36
+
37
+ htmlStrings.push(
38
+ PROP_ROW.replace("[[name]]", prop.name)
39
+ .replace("[[type]]", type)
40
+ .replace("[[description]]", description)
41
+ );
42
+ }
43
+
44
+ htmlStrings.push(TABLE_TAG_STOP);
45
+ }
46
+
47
+ return htmlStrings.join("");
48
+ }
@@ -0,0 +1,13 @@
1
+ export function trimExcludedText(rawText: string) {
2
+ {
3
+ const pieces = rawText.split("// <begin>");
4
+ rawText = pieces[pieces.length - 1].trim();
5
+ }
6
+
7
+ {
8
+ const pieces = rawText.split("// <end>");
9
+ rawText = pieces[0].trim();
10
+ }
11
+
12
+ return rawText;
13
+ }