@travetto/doc 7.0.0-rc.1 → 7.0.0-rc.2
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 +3 -3
- package/src/jsx.ts +1 -1
- package/src/render/code-highlight.ts +5 -5
- package/src/render/html.ts +32 -32
- package/src/render/markdown.ts +29 -29
- package/src/render/prism.d.ts +1 -1
- package/src/render/renderer.ts +30 -30
- package/src/types.ts +1 -1
- package/src/util/file.ts +26 -26
- package/src/util/resolve.ts +2 -2
- package/src/util/run.ts +22 -19
- package/src/util/types.ts +1 -1
- package/support/cli.doc.ts +5 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/doc",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.2",
|
|
4
4
|
"description": "Documentation support for the Travetto framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"directory": "module/doc"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@travetto/runtime": "^7.0.0-rc.
|
|
27
|
+
"@travetto/runtime": "^7.0.0-rc.2",
|
|
28
28
|
"@types/prismjs": "^1.26.5",
|
|
29
29
|
"prismjs": "^1.30.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@travetto/cli": "^7.0.0-rc.
|
|
32
|
+
"@travetto/cli": "^7.0.0-rc.2"
|
|
33
33
|
},
|
|
34
34
|
"peerDependenciesMeta": {
|
|
35
35
|
"@travetto/cli": {
|
package/src/jsx.ts
CHANGED
|
@@ -64,7 +64,7 @@ export type JSXElements = { [K in keyof C]: JSXElementByFn<K>; };
|
|
|
64
64
|
|
|
65
65
|
export const EMPTY_ELEMENT = EMPTY;
|
|
66
66
|
|
|
67
|
-
const invertedC = new Map<Function, string>(TypedObject.entries(c).map<[Function, string]>(
|
|
67
|
+
const invertedC = new Map<Function, string>(TypedObject.entries(c).map<[Function, string]>(([name, cls]) => [cls, name]));
|
|
68
68
|
|
|
69
69
|
export function getComponentName(fn: Function | string): string {
|
|
70
70
|
if (typeof fn === 'string') {
|
|
@@ -34,18 +34,18 @@ export function highlight(text: string, lang: string): string | undefined {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
text = text
|
|
37
|
-
.replace(/&#(\d+);/g, (
|
|
37
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(code))
|
|
38
38
|
.replace(/&[a-z][^;]*;/g, a => tokenMapping[a] || a);
|
|
39
39
|
|
|
40
40
|
try {
|
|
41
41
|
return prismJs.highlight(text, prismJs.languages[lang], lang)
|
|
42
42
|
.replace(/(@\s*<span[^>]*)function("\s*>)/g, (a, pre, post) => `${pre}meta${post}`)
|
|
43
43
|
.replace(/[{}]/g, a => `{{'${a}'}}`);
|
|
44
|
-
} catch (
|
|
45
|
-
if (
|
|
46
|
-
console.error(
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
console.error(error.message, { error });
|
|
47
47
|
} else {
|
|
48
|
-
throw
|
|
48
|
+
throw error;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
package/src/render/html.ts
CHANGED
|
@@ -13,23 +13,23 @@ import { RenderContext } from './context.ts';
|
|
|
13
13
|
import { DocResolveUtil } from '../util/resolve.ts';
|
|
14
14
|
|
|
15
15
|
const ESCAPE_ENTITIES: Record<string, string> = { '<': '<', '>': '>', '&': '&', '{': "{{'{'}}", '}': "{{'}'}}" };
|
|
16
|
-
const
|
|
16
|
+
const ENTITY_REGEX = new RegExp(`[${Object.keys(ESCAPE_ENTITIES).join('')}]`, 'gm');
|
|
17
17
|
|
|
18
|
-
const stdInline = async ({ recurse,
|
|
19
|
-
`<${
|
|
18
|
+
const stdInline = async ({ recurse, node }: RenderState<JSXElement, RenderContext>): Promise<string> =>
|
|
19
|
+
`<${node.type}>${await recurse()}</${node.type}>`;
|
|
20
20
|
|
|
21
|
-
const std = async ({ recurse,
|
|
22
|
-
`<${
|
|
21
|
+
const std = async ({ recurse, node }: RenderState<JSXElement, RenderContext>): Promise<string> =>
|
|
22
|
+
`<${node.type}>${await recurse()}</${node.type}>\n`;
|
|
23
23
|
|
|
24
|
-
const stdFull = async ({ recurse,
|
|
25
|
-
`\n<${
|
|
24
|
+
const stdFull = async ({ recurse, node }: RenderState<JSXElement, RenderContext>): Promise<string> =>
|
|
25
|
+
`\n<${node.type}>${await recurse()}</${node.type}>\n`;
|
|
26
26
|
|
|
27
27
|
export const Html: RenderProvider<RenderContext> = {
|
|
28
28
|
ext: 'html',
|
|
29
29
|
finalize: (text, context) => {
|
|
30
30
|
const brand = `<!-- ${context.generatedStamp} -->\n<!-- ${context.rebuildStamp} -->`;
|
|
31
31
|
const cleaned = text
|
|
32
|
-
.replace(/(<[/](?:a)>)([A-Za-z0-9$])/g, (_, tag,
|
|
32
|
+
.replace(/(<[/](?:a)>)([A-Za-z0-9$])/g, (_, tag, value) => `${tag} ${value}`)
|
|
33
33
|
.replace(/(<[uo]l>)(<li>)/g, (_, a, b) => `${a} ${b}`);
|
|
34
34
|
return `${brand}\n${cleaned}`;
|
|
35
35
|
},
|
|
@@ -39,10 +39,10 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
39
39
|
h2: stdFull, h3: stdFull, h4: stdFull,
|
|
40
40
|
li: std, ol: stdFull, ul: stdFull,
|
|
41
41
|
table: stdFull, thead: std, tr: std, td: std, tbody: std,
|
|
42
|
-
Execution: async ({ context,
|
|
43
|
-
const output = await context.execute(
|
|
42
|
+
Execution: async ({ context, node, props, createState }) => {
|
|
43
|
+
const output = await context.execute(node);
|
|
44
44
|
const displayCmd = props.config?.formatCommand?.(props.cmd, props.args ?? []) ??
|
|
45
|
-
`${
|
|
45
|
+
`${node.props.cmd} ${(node.props.args ?? []).join(' ')}`;
|
|
46
46
|
const sub = createState('Terminal', {
|
|
47
47
|
language: 'bash',
|
|
48
48
|
title: props.title,
|
|
@@ -50,18 +50,18 @@ export const Html: RenderProvider<RenderContext> = {
|
|
|
50
50
|
});
|
|
51
51
|
return Html.Terminal(sub);
|
|
52
52
|
},
|
|
53
|
-
Install: async ({ context,
|
|
53
|
+
Install: async ({ context, node }) => {
|
|
54
54
|
const highlighted = highlight(`
|
|
55
|
-
npm install ${
|
|
55
|
+
npm install ${node.props.pkg}
|
|
56
56
|
|
|
57
57
|
# or
|
|
58
58
|
|
|
59
|
-
yarn add ${
|
|
59
|
+
yarn add ${node.props.pkg}
|
|
60
60
|
`, 'bash');
|
|
61
61
|
|
|
62
62
|
return `\n
|
|
63
63
|
<figure class="install">
|
|
64
|
-
<figcaption class="install">Install ${
|
|
64
|
+
<figcaption class="install">Install ${node.props.title}
|
|
65
65
|
|
|
66
66
|
</figcaption>
|
|
67
67
|
<pre><code class="language-bash">${highlighted}</code></pre>
|
|
@@ -70,11 +70,11 @@ yarn add ${el.props.pkg}
|
|
|
70
70
|
},
|
|
71
71
|
Terminal: state => Html.Code(state),
|
|
72
72
|
Config: state => Html.Code(state),
|
|
73
|
-
Code: async ({ context,
|
|
74
|
-
DocResolveUtil.applyCodePropDefaults(
|
|
73
|
+
Code: async ({ context, node, props }) => {
|
|
74
|
+
DocResolveUtil.applyCodePropDefaults(node.props);
|
|
75
75
|
|
|
76
|
-
const cls = getComponentName(
|
|
77
|
-
const content = await context.resolveCode(
|
|
76
|
+
const cls = getComponentName(node.type).replace(/^[A-Z]/g, value => value.toLowerCase());
|
|
77
|
+
const content = await context.resolveCode(node);
|
|
78
78
|
let link: string = '';
|
|
79
79
|
if ('src' in props && content.file) {
|
|
80
80
|
let linkCtx: { file: string, line?: number } = { file: content.file! };
|
|
@@ -85,9 +85,9 @@ yarn add ${el.props.pkg}
|
|
|
85
85
|
}
|
|
86
86
|
let lang = props.language ?? content.language;
|
|
87
87
|
if (!lang) {
|
|
88
|
-
if (
|
|
88
|
+
if (node.type === c.Terminal) {
|
|
89
89
|
lang = 'bash';
|
|
90
|
-
} else if (
|
|
90
|
+
} else if (node.type === c.Code) {
|
|
91
91
|
lang = 'typescript';
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -112,12 +112,12 @@ yarn add ${el.props.pkg}
|
|
|
112
112
|
Path: state => Html.Input(state),
|
|
113
113
|
Class: state => Html.Input(state),
|
|
114
114
|
Field: state => Html.Input(state),
|
|
115
|
-
Input: async ({
|
|
116
|
-
const cls = getComponentName(
|
|
117
|
-
return `<code class="item ${cls}">${context.cleanText(
|
|
115
|
+
Input: async ({ node, context }) => {
|
|
116
|
+
const cls = getComponentName(node.type).replace(/^[A-Z]/g, value => value.toLowerCase());
|
|
117
|
+
return `<code class="item ${cls}">${context.cleanText(node.props.name.replace(ENTITY_REGEX, key => ESCAPE_ENTITIES[key]))}</code>`;
|
|
118
118
|
},
|
|
119
|
-
CodeLink: async ({ context, props,
|
|
120
|
-
const target = await context.resolveCodeLink(
|
|
119
|
+
CodeLink: async ({ context, props, node }) => {
|
|
120
|
+
const target = await context.resolveCodeLink(node);
|
|
121
121
|
return `<a target="_blank" class="source-link" href="${context.link(target.file, target)}">${props.title}</a>`;
|
|
122
122
|
},
|
|
123
123
|
Anchor: async ({ context, props }) =>
|
|
@@ -134,24 +134,24 @@ yarn add ${el.props.pkg}
|
|
|
134
134
|
},
|
|
135
135
|
|
|
136
136
|
Mod: async ({ context, props }) => {
|
|
137
|
-
const
|
|
138
|
-
return `<a class="module-link" href="${context.link(
|
|
137
|
+
const config = MOD_MAPPING[props.name];
|
|
138
|
+
return `<a class="module-link" href="${context.link(config.folder, config)}" title="${config.description}">${config.displayName}</a>`;
|
|
139
139
|
},
|
|
140
140
|
Library: async ({ context, props }) => {
|
|
141
|
-
const
|
|
142
|
-
return `<a target="_blank" class="external-link" href="${context.link(
|
|
141
|
+
const config = LIB_MAPPING[props.name];
|
|
142
|
+
return `<a target="_blank" class="external-link" href="${context.link(config.href, config)}">${config.title}</a>`;
|
|
143
143
|
},
|
|
144
144
|
|
|
145
145
|
Note: async ({ recurse }) => `\n\n<p class="note"><strong>Note</strong> ${await recurse()}</p>\n`,
|
|
146
146
|
Header: async ({ props }) => `<h1>${props.title} ${props.description ? `\n<small>${props.description}</small>\n` : ''}</h1>\n`,
|
|
147
147
|
|
|
148
148
|
StdHeader: async state => {
|
|
149
|
-
const mod = state.
|
|
149
|
+
const mod = state.node.props.mod ?? Runtime.main.name;
|
|
150
150
|
const pkg = PackageUtil.readPackage(RuntimeIndex.getModule(mod)!.sourcePath);
|
|
151
151
|
const title = pkg.travetto?.displayName ?? pkg.name;
|
|
152
152
|
const desc = pkg.description;
|
|
153
153
|
let install = '';
|
|
154
|
-
if (state.
|
|
154
|
+
if (state.node.props.install !== false) {
|
|
155
155
|
const sub = state.createState('Install', {
|
|
156
156
|
title: pkg.name,
|
|
157
157
|
pkg: pkg.name,
|
package/src/render/markdown.ts
CHANGED
|
@@ -15,8 +15,8 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
15
15
|
finalize: (text, context) => {
|
|
16
16
|
const brand = `<!-- ${context.generatedStamp} -->\n<!-- ${context.rebuildStamp} -->`;
|
|
17
17
|
const cleaned = text
|
|
18
|
-
.replace(/(\[[^\]]+\]\([^)]+\))([A-Za-z0-9$]+)/g, (_, link,
|
|
19
|
-
.replace(/(\S)\n(#)/g, (_,
|
|
18
|
+
.replace(/(\[[^\]]+\]\([^)]+\))([A-Za-z0-9$]+)/g, (_, link, value) => value === 's' ? _ : `${link} ${value}`)
|
|
19
|
+
.replace(/(\S)\n(#)/g, (_, left, right) => `${left}\n\n${right}`);
|
|
20
20
|
return `${brand}\n${cleaned}`;
|
|
21
21
|
},
|
|
22
22
|
strong: async ({ recurse }) => `**${await recurse()}**`,
|
|
@@ -26,8 +26,8 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
26
26
|
ul: async ({ recurse }) => `\n${await recurse()}\n`,
|
|
27
27
|
ol: async ({ recurse }) => `\n${await recurse()}\n`,
|
|
28
28
|
li: async ({ recurse, stack }) => {
|
|
29
|
-
const parent = stack.toReversed().find(
|
|
30
|
-
const depth = stack.filter(
|
|
29
|
+
const parent = stack.toReversed().find(node => node.type === 'ol' || node.type === 'ul');
|
|
30
|
+
const depth = stack.filter(node => node.type === 'ol' || node.type === 'ul').length;
|
|
31
31
|
return `${' '.repeat(depth)}${(parent && parent.type === 'ol') ? '1.' : '* '} ${await recurse()}\n`;
|
|
32
32
|
},
|
|
33
33
|
table: async ({ recurse }) => `${await recurse()}`,
|
|
@@ -41,37 +41,37 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
41
41
|
h2: async ({ recurse }) => `\n## ${await recurse()}\n\n`,
|
|
42
42
|
h3: async ({ recurse }) => `\n### ${await recurse()}\n\n`,
|
|
43
43
|
h4: async ({ recurse }) => `\n#### ${await recurse()}\n\n`,
|
|
44
|
-
Execution: async ({ context,
|
|
45
|
-
const output = await context.execute(
|
|
44
|
+
Execution: async ({ context, node, props, createState }) => {
|
|
45
|
+
const output = await context.execute(node);
|
|
46
46
|
const displayCmd = props.config?.formatCommand?.(props.cmd, props.args ?? []) ??
|
|
47
|
-
`${
|
|
47
|
+
`${node.props.cmd} ${(node.props.args ?? []).join(' ')}`;
|
|
48
48
|
const state = createState('Terminal', {
|
|
49
49
|
language: 'bash',
|
|
50
|
-
title:
|
|
50
|
+
title: node.props.title,
|
|
51
51
|
src: [`$ ${displayCmd}`, '', context.cleanText(output)].join('\n')
|
|
52
52
|
});
|
|
53
53
|
return Markdown.Terminal(state);
|
|
54
54
|
},
|
|
55
|
-
Install: async ({ context,
|
|
56
|
-
`\n\n**Install: ${
|
|
55
|
+
Install: async ({ context, node }) =>
|
|
56
|
+
`\n\n**Install: ${node.props.title}**
|
|
57
57
|
\`\`\`bash
|
|
58
|
-
npm install ${
|
|
58
|
+
npm install ${node.props.pkg}
|
|
59
59
|
|
|
60
60
|
# or
|
|
61
61
|
|
|
62
|
-
yarn add ${
|
|
62
|
+
yarn add ${node.props.pkg}
|
|
63
63
|
\`\`\`
|
|
64
64
|
`,
|
|
65
|
-
Code: async ({ context,
|
|
66
|
-
DocResolveUtil.applyCodePropDefaults(
|
|
65
|
+
Code: async ({ context, node, props }) => {
|
|
66
|
+
DocResolveUtil.applyCodePropDefaults(node.props);
|
|
67
67
|
|
|
68
|
-
const name = getComponentName(
|
|
69
|
-
const content = await context.resolveCode(
|
|
68
|
+
const name = getComponentName(node.type);
|
|
69
|
+
const content = await context.resolveCode(node);
|
|
70
70
|
let lang = props.language ?? content.language;
|
|
71
71
|
if (!lang) {
|
|
72
|
-
if (
|
|
72
|
+
if (node.type === c.Terminal) {
|
|
73
73
|
lang = 'bash';
|
|
74
|
-
} else if (
|
|
74
|
+
} else if (node.type === c.Code) {
|
|
75
75
|
lang = 'typescript';
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -83,9 +83,9 @@ ${context.cleanText(content.text)}
|
|
|
83
83
|
Terminal: state => Markdown.Code(state),
|
|
84
84
|
Config: state => Markdown.Code(state),
|
|
85
85
|
|
|
86
|
-
Section: async ({
|
|
87
|
-
SubSection: async ({
|
|
88
|
-
SubSubSection: async ({
|
|
86
|
+
Section: async ({ node, recurse }) => `\n## ${node.props.title}\n${await recurse()}\n`,
|
|
87
|
+
SubSection: async ({ node, recurse }) => `\n### ${node.props.title}\n${await recurse()}\n`,
|
|
88
|
+
SubSubSection: async ({ node, recurse }) => `\n#### ${node.props.title}\n${await recurse()}\n`,
|
|
89
89
|
|
|
90
90
|
Command: state => Markdown.Input(state),
|
|
91
91
|
Method: state => Markdown.Input(state),
|
|
@@ -98,8 +98,8 @@ ${context.cleanText(content.text)}
|
|
|
98
98
|
File: state => Markdown.Ref(state),
|
|
99
99
|
Ref: async ({ context, props }) => `[${props.title}](${context.link(props.href, props)})`,
|
|
100
100
|
|
|
101
|
-
CodeLink: async ({ context, props,
|
|
102
|
-
const target = await context.resolveCodeLink(
|
|
101
|
+
CodeLink: async ({ context, props, node }) => {
|
|
102
|
+
const target = await context.resolveCodeLink(node);
|
|
103
103
|
return `[${props.title}](${context.link(target.file, target)})`;
|
|
104
104
|
},
|
|
105
105
|
|
|
@@ -113,23 +113,23 @@ ${context.cleanText(content.text)}
|
|
|
113
113
|
Header: async ({ props }) => `# ${props.title}\n${props.description ? `## ${props.description}\n` : ''}\n`,
|
|
114
114
|
|
|
115
115
|
StdHeader: async state => {
|
|
116
|
-
const mod = state.
|
|
116
|
+
const mod = state.node.props.mod ?? Runtime.main.name;
|
|
117
117
|
const pkg = PackageUtil.readPackage(RuntimeIndex.getModule(mod)!.sourcePath);
|
|
118
118
|
const title = pkg.travetto?.displayName ?? pkg.name;
|
|
119
119
|
const desc = pkg.description;
|
|
120
120
|
let install = '';
|
|
121
|
-
if (state.
|
|
121
|
+
if (state.node.props.install !== false) {
|
|
122
122
|
const sub = state.createState('Install', { title: pkg.name, pkg: pkg.name });
|
|
123
123
|
install = await Markdown.Install(sub);
|
|
124
124
|
}
|
|
125
125
|
return `# ${title}\n${desc ? `## ${desc}\n` : ''}${install}\n`;
|
|
126
126
|
},
|
|
127
127
|
Mod: async ({ props, context }) => {
|
|
128
|
-
const
|
|
129
|
-
return `[${
|
|
128
|
+
const config = MOD_MAPPING[props.name];
|
|
129
|
+
return `[${config.displayName}](${context.link(config.folder, config)}#readme "${config.description}")`;
|
|
130
130
|
},
|
|
131
131
|
Library: async ({ props }) => {
|
|
132
|
-
const
|
|
133
|
-
return `[${
|
|
132
|
+
const config = LIB_MAPPING[props.name];
|
|
133
|
+
return `[${config.title}](${config.href})`;
|
|
134
134
|
}
|
|
135
135
|
};
|
package/src/render/prism.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ declare namespace PrismAlt {
|
|
|
2
2
|
export interface Grammar { }
|
|
3
3
|
export const languages: { [language: string]: Grammar; };
|
|
4
4
|
export const plugins: Record<string, {
|
|
5
|
-
setDefaults(
|
|
5
|
+
setDefaults(config: Record<string, unknown>): void;
|
|
6
6
|
normalize(text: string, config?: unknown): string;
|
|
7
7
|
}>;
|
|
8
8
|
export function highlight(
|
package/src/render/renderer.ts
CHANGED
|
@@ -20,10 +20,10 @@ const providers = { [Html.ext]: Html, [Markdown.ext]: Markdown };
|
|
|
20
20
|
export class DocRenderer {
|
|
21
21
|
|
|
22
22
|
static async get(file: string, manifest: Pick<ManifestContext, 'workspace'>): Promise<DocRenderer> {
|
|
23
|
-
const
|
|
23
|
+
const document = await Runtime.importFrom<DocumentShape>(file);
|
|
24
24
|
const pkg = PackageUtil.readPackage(manifest.workspace.path);
|
|
25
25
|
const repoBaseUrl = pkg.travetto?.doc?.baseUrl ?? manifest.workspace.path;
|
|
26
|
-
return new DocRenderer(
|
|
26
|
+
return new DocRenderer(document,
|
|
27
27
|
new RenderContext(file, repoBaseUrl, path.resolve(pkg.travetto?.doc?.root ?? manifest.workspace.path))
|
|
28
28
|
);
|
|
29
29
|
}
|
|
@@ -45,14 +45,14 @@ export class DocRenderer {
|
|
|
45
45
|
const source = DocFileUtil.readSource(cls);
|
|
46
46
|
if (source) {
|
|
47
47
|
title = (await DocFileUtil.isDecorator(cls.name, source.file)) ? `@${title ?? cls.name}` : (title ?? cls.name);
|
|
48
|
-
const
|
|
48
|
+
const node = this.#support.createElement('CodeLink', {
|
|
49
49
|
src: source.file,
|
|
50
50
|
startRe: new RegExp(`(class|function|interface)\\s+(${cls.name.replaceAll('$', '\\$')})`),
|
|
51
51
|
title
|
|
52
52
|
});
|
|
53
53
|
// @ts-expect-error
|
|
54
54
|
const state: RenderState<JSXElementByFn<'CodeLink'>, RenderContext> = {
|
|
55
|
-
|
|
55
|
+
node, props: node.props, recurse: async () => '', context: this.#support, stack: []
|
|
56
56
|
};
|
|
57
57
|
// @ts-expect-error
|
|
58
58
|
state.createState = (key, props) => this.createState(state, key, props);
|
|
@@ -62,26 +62,26 @@ export class DocRenderer {
|
|
|
62
62
|
|
|
63
63
|
async #render(
|
|
64
64
|
renderer: RenderProvider<RenderContext>,
|
|
65
|
-
|
|
65
|
+
input: JSXElement[] | JSXElement | string | bigint | object | number | boolean | null | undefined,
|
|
66
66
|
stack: JSXElement[] = []
|
|
67
67
|
): Promise<string | undefined> {
|
|
68
68
|
|
|
69
|
-
if (
|
|
69
|
+
if (input === null || input === undefined) {
|
|
70
70
|
return '';
|
|
71
|
-
} else if (Array.isArray(
|
|
71
|
+
} else if (Array.isArray(input)) {
|
|
72
72
|
const out: string[] = [];
|
|
73
|
-
for (const
|
|
74
|
-
const sub = await this.#render(renderer,
|
|
73
|
+
for (const node of input) {
|
|
74
|
+
const sub = await this.#render(renderer, node, stack);
|
|
75
75
|
if (sub) {
|
|
76
76
|
out.push(sub);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
return out.join('');
|
|
80
|
-
} else if (isJSXElement(
|
|
81
|
-
let final: JSXElement =
|
|
80
|
+
} else if (isJSXElement(input)) {
|
|
81
|
+
let final: JSXElement = input;
|
|
82
82
|
// Render simple element if needed
|
|
83
|
-
if (typeof
|
|
84
|
-
const out = castTo<Function>(
|
|
83
|
+
if (typeof input.type === 'function' && input.type !== JSXFragmentType) {
|
|
84
|
+
const out = castTo<Function>(input.type)(input.props);
|
|
85
85
|
final = out !== EMPTY_ELEMENT ? out : final;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -98,7 +98,7 @@ export class DocRenderer {
|
|
|
98
98
|
const recurse = () => this.#render(renderer, final.props.children ?? [], [...stack, final]);
|
|
99
99
|
// @ts-expect-error
|
|
100
100
|
const state: RenderState<JSXElement, RenderContext> = {
|
|
101
|
-
|
|
101
|
+
node: final, props: final.props, recurse, stack, context: this.#support
|
|
102
102
|
};
|
|
103
103
|
state.createState = (key, props) => this.createState(state, key, props);
|
|
104
104
|
// @ts-expect-error
|
|
@@ -108,22 +108,22 @@ export class DocRenderer {
|
|
|
108
108
|
throw new Error(`Unknown element: ${final.type}`);
|
|
109
109
|
}
|
|
110
110
|
} else {
|
|
111
|
-
switch (typeof
|
|
112
|
-
case 'string': return
|
|
111
|
+
switch (typeof input) {
|
|
112
|
+
case 'string': return input.replace(/ /g, ' ');
|
|
113
113
|
case 'number':
|
|
114
114
|
case 'bigint':
|
|
115
|
-
case 'boolean': return `${
|
|
115
|
+
case 'boolean': return `${input}`;
|
|
116
116
|
case 'object': {
|
|
117
|
-
if (
|
|
118
|
-
return await this.#buildLink(renderer, castTo(
|
|
117
|
+
if (input) {
|
|
118
|
+
return await this.#buildLink(renderer, castTo(input.constructor), input.constructor.name.replace(/^[$]/, ''));
|
|
119
119
|
}
|
|
120
120
|
break;
|
|
121
121
|
}
|
|
122
122
|
case 'function': {
|
|
123
|
-
return await this.#buildLink(renderer, castTo(
|
|
123
|
+
return await this.#buildLink(renderer, castTo(input));
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
throw new Error(`Unknown object type: ${typeof
|
|
126
|
+
throw new Error(`Unknown object type: ${typeof input}`);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -133,28 +133,28 @@ export class DocRenderer {
|
|
|
133
133
|
props: JSXElementByFn<K>['props']
|
|
134
134
|
// @ts-expect-error
|
|
135
135
|
): RenderState<JSXElementByFn<K>, RenderContext> {
|
|
136
|
-
const
|
|
137
|
-
return { ...state,
|
|
136
|
+
const node = this.#support.createElement(key, props);
|
|
137
|
+
return { ...state, node, props: node.props };
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* Render a context given a specific renderer
|
|
142
142
|
* @param renderer
|
|
143
143
|
*/
|
|
144
|
-
async render(
|
|
145
|
-
if (!providers[
|
|
146
|
-
throw new Error(`Unknown renderer with format: ${
|
|
144
|
+
async render(format: keyof typeof providers): Promise<string> {
|
|
145
|
+
if (!providers[format]) {
|
|
146
|
+
throw new Error(`Unknown renderer with format: ${format}`);
|
|
147
147
|
}
|
|
148
148
|
if (!this.#rootNode) {
|
|
149
149
|
this.#rootNode = (Array.isArray(this.#root.text) || isJSXElement(this.#root.text)) ?
|
|
150
150
|
this.#root.text : await (this.#root.text());
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const text = await this.#render(providers[
|
|
153
|
+
const text = await this.#render(providers[format], this.#rootNode);
|
|
154
154
|
let cleaned = `${text?.replace(/\n{3,100}/msg, '\n\n').trim()}\n`;
|
|
155
|
-
if (this.#root.wrap?.[
|
|
156
|
-
cleaned = this.#root.wrap[
|
|
155
|
+
if (this.#root.wrap?.[format]) {
|
|
156
|
+
cleaned = this.#root.wrap[format](cleaned);
|
|
157
157
|
}
|
|
158
|
-
return providers[
|
|
158
|
+
return providers[format].finalize(cleaned, this.#support);
|
|
159
159
|
}
|
|
160
160
|
}
|
package/src/types.ts
CHANGED
package/src/util/file.ts
CHANGED
|
@@ -31,40 +31,40 @@ export class DocFileUtil {
|
|
|
31
31
|
|
|
32
32
|
static #decCache: Record<string, boolean> = {};
|
|
33
33
|
|
|
34
|
-
static isFile(
|
|
35
|
-
return /^[@:A-Za-z0-9\/\\\-_.]+[.]([a-z]{2,10})$/.test(
|
|
34
|
+
static isFile(file: string): boolean {
|
|
35
|
+
return /^[@:A-Za-z0-9\/\\\-_.]+[.]([a-z]{2,10})$/.test(file);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
static readSource(
|
|
38
|
+
static readSource(input: string | Function): { content: string, language: string, file: string } {
|
|
39
39
|
let file: string | undefined;
|
|
40
40
|
let content: string | undefined;
|
|
41
41
|
|
|
42
|
-
if (typeof
|
|
43
|
-
if (
|
|
44
|
-
content =
|
|
42
|
+
if (typeof input === 'string') {
|
|
43
|
+
if (input.includes('\n') || input.includes(' ')) {
|
|
44
|
+
content = input;
|
|
45
45
|
} else {
|
|
46
|
-
const resolved = path.resolve(
|
|
46
|
+
const resolved = path.resolve(input);
|
|
47
47
|
if (existsSync(resolved)) {
|
|
48
48
|
content = readFileSync(resolved, 'utf8');
|
|
49
49
|
file = resolved;
|
|
50
50
|
} else {
|
|
51
|
-
file = RuntimeIndex.getSourceFile(
|
|
51
|
+
file = RuntimeIndex.getSourceFile(input);
|
|
52
52
|
content = readFileSync(file, 'utf8');
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
} else {
|
|
56
|
-
file = Runtime.getSourceFile(
|
|
56
|
+
file = Runtime.getSourceFile(input);
|
|
57
57
|
if (!existsSync(file)) {
|
|
58
|
-
throw new AppError(`Unknown file: ${typeof
|
|
58
|
+
throw new AppError(`Unknown file: ${typeof input === 'string' ? input : input.name} => ${file}`);
|
|
59
59
|
}
|
|
60
60
|
content = readFileSync(file, 'utf8');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
if (content) {
|
|
64
64
|
content = content.split(/\n/)
|
|
65
|
-
.map(
|
|
65
|
+
.map(line => line
|
|
66
66
|
.replace(ESLINT_PATTERN, '')
|
|
67
|
-
.replace(ENV_KEY, (_,
|
|
67
|
+
.replace(ENV_KEY, (_, key) => `'${key}'`)
|
|
68
68
|
)
|
|
69
69
|
.join('\n')
|
|
70
70
|
.replace(/^\/\/# sourceMap.*$/gsm, '')
|
|
@@ -83,12 +83,12 @@ export class DocFileUtil {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
static async readCodeSnippet(
|
|
87
|
-
const result = this.readSource(
|
|
86
|
+
static async readCodeSnippet(input: string | Function, startPattern: RegExp): Promise<{ file: string, startIdx: number, lines: string[], language: string }> {
|
|
87
|
+
const result = this.readSource(input);
|
|
88
88
|
const lines = result.content.split(/\n/);
|
|
89
|
-
const startIdx = lines.findIndex(
|
|
89
|
+
const startIdx = lines.findIndex(line => startPattern.test(line));
|
|
90
90
|
if (startIdx < 0) {
|
|
91
|
-
throw new Error(`Pattern ${startPattern.source} not found in ${
|
|
91
|
+
throw new Error(`Pattern ${startPattern.source} not found in ${input}`);
|
|
92
92
|
}
|
|
93
93
|
return { file: result.file, startIdx, lines, language: result.language };
|
|
94
94
|
}
|
|
@@ -106,7 +106,7 @@ export class DocFileUtil {
|
|
|
106
106
|
const text = await this.readSource(file);
|
|
107
107
|
const lines = text.content.split(/\n/g);
|
|
108
108
|
|
|
109
|
-
const start = lines.findIndex(
|
|
109
|
+
const start = lines.findIndex(line => new RegExp(`function ${name}\\b`).test(line));
|
|
110
110
|
let found = false;
|
|
111
111
|
if (start > 0) {
|
|
112
112
|
for (let i = start - 1; i > start - 3; i--) {
|
|
@@ -126,30 +126,30 @@ export class DocFileUtil {
|
|
|
126
126
|
*/
|
|
127
127
|
static buildOutline(code: string): string {
|
|
128
128
|
let methodPrefix = '';
|
|
129
|
-
code = code.split(/\n/).map((
|
|
129
|
+
code = code.split(/\n/).map((line) => {
|
|
130
130
|
if (!methodPrefix) {
|
|
131
|
-
const info =
|
|
131
|
+
const info = line.match(/^(\s{0,50})(?:(private|public)\s{1,10})?(?:static\s{1,10})?(?:async\s{1,10})?(?:[*]\s{0,10})?(?:(?:get|set)\s{1,10})?(\S{1,200})[<(](.{0,500})/);
|
|
132
132
|
if (info) {
|
|
133
133
|
const [, space, __name, rest] = info;
|
|
134
134
|
if (!rest.endsWith(';')) {
|
|
135
|
-
if (/\s{0,50}[{]\s{0,50}return.{0,200}$/.test(
|
|
136
|
-
return
|
|
135
|
+
if (/\s{0,50}[{]\s{0,50}return.{0,200}$/.test(line)) {
|
|
136
|
+
return line.replace(/\s{0,50}[{]\s{0,50}return.{0,200}$/, ';');
|
|
137
137
|
} else {
|
|
138
138
|
methodPrefix = space;
|
|
139
|
-
return
|
|
139
|
+
return line.replace(/\s{0,50}[{]\s{0,50}$/, ';');
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
-
return
|
|
143
|
+
return line;
|
|
144
144
|
} else {
|
|
145
|
-
if (
|
|
145
|
+
if (line.startsWith(`${methodPrefix}}`)) {
|
|
146
146
|
methodPrefix = '';
|
|
147
147
|
}
|
|
148
148
|
return '';
|
|
149
149
|
}
|
|
150
150
|
})
|
|
151
|
-
.filter(
|
|
152
|
-
.filter(
|
|
151
|
+
.filter(line => !/#|(\b(private|protected)\b)/.test(line))
|
|
152
|
+
.filter(line => !!line)
|
|
153
153
|
.join('\n');
|
|
154
154
|
|
|
155
155
|
return code;
|
package/src/util/resolve.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class DocResolveUtil {
|
|
|
19
19
|
|
|
20
20
|
if (result.content) {
|
|
21
21
|
line = result.content.split(/\n/g)
|
|
22
|
-
.findIndex(
|
|
22
|
+
.findIndex(lineText => new RegExp(`(class|interface|function)[ ]+${title.replaceAll('$', '\\$')}`).test(lineText));
|
|
23
23
|
if (line < 0) {
|
|
24
24
|
line = 0;
|
|
25
25
|
} else {
|
|
@@ -50,7 +50,7 @@ export class DocResolveUtil {
|
|
|
50
50
|
static async resolveSnippet(file: Function | string, startPattern: RegExp, endPattern?: RegExp, outline = false): Promise<ResolvedSnippet> {
|
|
51
51
|
const { lines, startIdx, language, file: resolvedFile } = await DocFileUtil.readCodeSnippet(file, startPattern);
|
|
52
52
|
|
|
53
|
-
const endIdx = endPattern ? lines.findIndex((
|
|
53
|
+
const endIdx = endPattern ? lines.findIndex((line, i) => i > startIdx && endPattern.test(line)) : lines.length;
|
|
54
54
|
let text = lines.slice(startIdx, endIdx + 1).join('\n');
|
|
55
55
|
|
|
56
56
|
if (outline) {
|
package/src/util/run.ts
CHANGED
|
@@ -25,7 +25,10 @@ class DocState {
|
|
|
25
25
|
|
|
26
26
|
getId(id: string): string {
|
|
27
27
|
if (!this.ids[id]) {
|
|
28
|
-
this.ids[id] = ' '.repeat(id.length)
|
|
28
|
+
this.ids[id] = ' '.repeat(id.length)
|
|
29
|
+
.split('')
|
|
30
|
+
.map(_ => Math.trunc(this.rng() * 16).toString(16))
|
|
31
|
+
.join('');
|
|
29
32
|
}
|
|
30
33
|
return this.ids[id];
|
|
31
34
|
}
|
|
@@ -37,18 +40,18 @@ class DocState {
|
|
|
37
40
|
export class DocRunUtil {
|
|
38
41
|
static #docState = new DocState();
|
|
39
42
|
|
|
40
|
-
/** Build
|
|
41
|
-
static
|
|
42
|
-
return path.resolve(
|
|
43
|
+
/** Build working directory from config */
|
|
44
|
+
static workingDirectory(config: RunConfig): string {
|
|
45
|
+
return path.resolve(config.module ? RuntimeIndex.getModule(config.module)?.sourcePath! : Runtime.mainSourcePath);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Clean run output
|
|
47
50
|
*/
|
|
48
|
-
static cleanRunOutput(text: string,
|
|
49
|
-
const
|
|
51
|
+
static cleanRunOutput(text: string, config: RunConfig): string {
|
|
52
|
+
const rootPath = this.workingDirectory(config);
|
|
50
53
|
text = util.stripVTControlCharacters(text.trim())
|
|
51
|
-
.replaceAll(
|
|
54
|
+
.replaceAll(rootPath, '.')
|
|
52
55
|
.replaceAll(os.tmpdir(), '/tmp')
|
|
53
56
|
.replaceAll(Runtime.workspace.path, '<workspace-root>')
|
|
54
57
|
.replace(/[/]tmp[/][a-z_A-Z0-9\/\-]+/g, '/tmp/<temp-folder>')
|
|
@@ -58,12 +61,12 @@ export class DocRunUtil {
|
|
|
58
61
|
.replace(/[A-Za-z0-9_.\-\/\\]+\/travetto\/module\//g, '@travetto/')
|
|
59
62
|
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([.]\d{3})?Z?/g, this.#docState.getDate.bind(this.#docState))
|
|
60
63
|
.replace(/\b[0-9a-f]{4}[0-9a-f\-]{8,40}\b/ig, this.#docState.getId.bind(this.#docState))
|
|
61
|
-
.replace(/(\d+[.]\d+[.]\d+)-(alpha|rc)[.]\d+/g, (all,
|
|
62
|
-
if (
|
|
63
|
-
text = text.split(/\n/g).filter(
|
|
64
|
+
.replace(/(\d+[.]\d+[.]\d+)-(alpha|rc)[.]\d+/g, (all, value) => value);
|
|
65
|
+
if (config.filter) {
|
|
66
|
+
text = text.split(/\n/g).filter(config.filter).join('\n');
|
|
64
67
|
}
|
|
65
|
-
if (
|
|
66
|
-
text =
|
|
68
|
+
if (config.rewrite) {
|
|
69
|
+
text = config.rewrite(text);
|
|
67
70
|
}
|
|
68
71
|
return text;
|
|
69
72
|
}
|
|
@@ -73,7 +76,7 @@ export class DocRunUtil {
|
|
|
73
76
|
*/
|
|
74
77
|
static spawn(cmd: string, args: string[], config: RunConfig = {}): ChildProcess {
|
|
75
78
|
return spawn(cmd, args, {
|
|
76
|
-
cwd: config.
|
|
79
|
+
cwd: config.workingDirectory ?? this.workingDirectory(config),
|
|
77
80
|
env: {
|
|
78
81
|
...process.env,
|
|
79
82
|
...Env.DEBUG.export(false),
|
|
@@ -95,17 +98,17 @@ export class DocRunUtil {
|
|
|
95
98
|
static async run(cmd: string, args: string[], config: RunConfig = {}): Promise<string> {
|
|
96
99
|
let final: string;
|
|
97
100
|
try {
|
|
98
|
-
const
|
|
99
|
-
const result = await ExecUtil.getResult(
|
|
101
|
+
const subProcess = this.spawn(cmd, args, config);
|
|
102
|
+
const result = await ExecUtil.getResult(subProcess, { catch: true });
|
|
100
103
|
if (!result.valid) {
|
|
101
104
|
throw new Error(result.stderr);
|
|
102
105
|
}
|
|
103
106
|
final = util.stripVTControlCharacters(result.stdout).trim() || util.stripVTControlCharacters(result.stderr).trim();
|
|
104
|
-
} catch (
|
|
105
|
-
if (
|
|
106
|
-
final =
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error instanceof Error) {
|
|
109
|
+
final = error.message;
|
|
107
110
|
} else {
|
|
108
|
-
throw
|
|
111
|
+
throw error;
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
|
package/src/util/types.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type RunConfig = {
|
|
|
4
4
|
module?: string;
|
|
5
5
|
env?: Record<string, string>;
|
|
6
6
|
envName?: string;
|
|
7
|
-
|
|
7
|
+
workingDirectory?: string;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
export type CodeProps = { title?: string, src: string | Function, language?: string, outline?: boolean, startRe?: RegExp, endRe?: RegExp };
|
package/support/cli.doc.ts
CHANGED
|
@@ -52,15 +52,15 @@ export class DocCommand implements CliCommandShape {
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
const args = process.argv.slice(2).filter(
|
|
55
|
+
const args = process.argv.slice(2).filter(arg => !/(-w|--watch)/.test(arg));
|
|
56
56
|
for await (const { action, file } of watchCompiler({ restartOnExit: true })) {
|
|
57
57
|
if (action === 'update' && file === this.input) {
|
|
58
|
-
const
|
|
58
|
+
const subProcess = spawn('npx', ['trv', ...args], {
|
|
59
59
|
cwd: Runtime.mainSourcePath,
|
|
60
60
|
env: { ...process.env, ...Env.TRV_QUIET.export(true) },
|
|
61
61
|
stdio: 'inherit'
|
|
62
62
|
});
|
|
63
|
-
await ExecUtil.getResult(
|
|
63
|
+
await ExecUtil.getResult(subProcess, { catch: true });
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -73,8 +73,8 @@ export class DocCommand implements CliCommandShape {
|
|
|
73
73
|
[output, null] as const
|
|
74
74
|
);
|
|
75
75
|
|
|
76
|
-
for (const [
|
|
77
|
-
const result = await ctx.render(
|
|
76
|
+
for (const [format, out] of outputs) {
|
|
77
|
+
const result = await ctx.render(format);
|
|
78
78
|
if (out) {
|
|
79
79
|
const finalName = path.resolve(out);
|
|
80
80
|
await fs.writeFile(finalName, result, 'utf8');
|