@travetto/doc 8.0.0-alpha.2 → 8.0.0-alpha.21
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 +41 -31
- package/__index__.ts +3 -3
- package/package.json +5 -3
- package/src/jsx.ts +53 -19
- package/src/mapping/library.ts +7 -4
- package/src/mapping/module.ts +177 -61
- package/src/render/code-highlight.ts +3 -5
- package/src/render/context.ts +28 -13
- package/src/render/html.ts +63 -22
- package/src/render/markdown.ts +37 -11
- package/src/render/renderer.ts +26 -24
- package/src/types.ts +7 -8
- package/src/util/file.ts +37 -37
- package/src/util/package.ts +19 -13
- package/src/util/resolve.ts +11 -12
- package/src/util/run.ts +14 -10
- package/src/util/types.ts +1 -1
- package/support/cli.doc.ts +12 -11
- package/support/jsx-runtime.ts +26 -15
package/src/render/markdown.ts
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { HELP_FLAG } from '@travetto/cli';
|
|
4
4
|
import { PackageUtil } from '@travetto/manifest';
|
|
5
|
+
import { CodecUtil, Runtime, RuntimeError, RuntimeIndex } from '@travetto/runtime';
|
|
5
6
|
|
|
6
|
-
import type { RenderProvider } from '../types.ts';
|
|
7
7
|
import { c, getComponentName } from '../jsx.ts';
|
|
8
|
-
import { MODULES } from '../mapping/module.ts';
|
|
9
8
|
import { LIBRARIES } from '../mapping/library.ts';
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
9
|
+
import { MODULES } from '../mapping/module.ts';
|
|
10
|
+
import type { RenderProvider } from '../types.ts';
|
|
12
11
|
import { PackageDocUtil } from '../util/package.ts';
|
|
12
|
+
import { DocResolveUtil } from '../util/resolve.ts';
|
|
13
|
+
import type { RenderContext } from './context.ts';
|
|
13
14
|
|
|
14
15
|
export const Markdown: RenderProvider<RenderContext> = {
|
|
15
16
|
ext: 'md',
|
|
16
17
|
finalize: (text, context) => {
|
|
17
18
|
const brand = `<!-- ${context.generatedStamp} -->\n<!-- ${context.rebuildStamp} -->`;
|
|
18
19
|
const cleaned = text
|
|
19
|
-
.replace(/(\[[^\]]+\]\([^)]+\))([A-Za-z0-9$]+)/g, (_, link, value) => value === 's' ? _ : `${link} ${value}`)
|
|
20
|
+
.replace(/(\[[^\]]+\]\([^)]+\))([A-Za-z0-9$]+)/g, (_, link, value) => (value === 's' ? _ : `${link} ${value}`))
|
|
20
21
|
.replace(/(\S)\n(#)/g, (_, left, right) => `${left}\n\n${right}`);
|
|
21
22
|
return `${brand}\n${cleaned}`;
|
|
22
23
|
},
|
|
@@ -29,7 +30,7 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
29
30
|
li: async ({ recurse, stack }) => {
|
|
30
31
|
const parent = stack.toReversed().find(node => node.type === 'ol' || node.type === 'ul');
|
|
31
32
|
const depth = stack.filter(node => node.type === 'ol' || node.type === 'ul').length;
|
|
32
|
-
return `${' '.repeat(depth)}${
|
|
33
|
+
return `${' '.repeat(depth)}${parent && parent.type === 'ol' ? '1.' : '* '} ${await recurse()}\n`;
|
|
33
34
|
},
|
|
34
35
|
table: async ({ recurse }) => `${await recurse()}`,
|
|
35
36
|
tbody: async ({ recurse }) => `${await recurse()}`,
|
|
@@ -44,8 +45,8 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
44
45
|
h4: async ({ recurse }) => `\n#### ${await recurse()}\n\n`,
|
|
45
46
|
Execution: async ({ context, node, props, createState }) => {
|
|
46
47
|
const output = await context.execute(node);
|
|
47
|
-
const displayCmd =
|
|
48
|
-
`${node.props.cmd} ${(node.props.args ?? []).join(' ')}`;
|
|
48
|
+
const displayCmd =
|
|
49
|
+
props.config?.formatCommand?.(props.cmd, props.args ?? []) ?? `${node.props.cmd} ${(node.props.args ?? []).join(' ')}`;
|
|
49
50
|
const state = createState('Terminal', {
|
|
50
51
|
language: 'bash',
|
|
51
52
|
title: node.props.title,
|
|
@@ -53,6 +54,31 @@ export const Markdown: RenderProvider<RenderContext> = {
|
|
|
53
54
|
});
|
|
54
55
|
return Markdown.Terminal(state);
|
|
55
56
|
},
|
|
57
|
+
CliHelpExecution: async ({ context, props, createState }) => {
|
|
58
|
+
const { name: command } = context.resolveCliCommandFromClass(props.commandClass);
|
|
59
|
+
return await Markdown.Execution(
|
|
60
|
+
createState('Execution', {
|
|
61
|
+
title: `Help for ${command}`,
|
|
62
|
+
cmd: 'trv',
|
|
63
|
+
args: [command, HELP_FLAG],
|
|
64
|
+
config: { ...props.config }
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
CliHelpDescription: async ({ context, props }) => {
|
|
69
|
+
const { description = '' } = context.resolveCliCommandFromClass(props.commandClass);
|
|
70
|
+
let text = description;
|
|
71
|
+
if (props.short) {
|
|
72
|
+
text = CodecUtil.readFirstLine(text);
|
|
73
|
+
}
|
|
74
|
+
return text;
|
|
75
|
+
},
|
|
76
|
+
CliHelpSection: async ({ context, props, recurse }) => {
|
|
77
|
+
const { name: command } = context.resolveCliCommandFromClass(props.commandClass);
|
|
78
|
+
const title = `CLI - ${command}`;
|
|
79
|
+
const body = await recurse();
|
|
80
|
+
return `\n## ${title}\n${body}`;
|
|
81
|
+
},
|
|
56
82
|
Install: async ({ context, node }) =>
|
|
57
83
|
`\n\n**Install: ${node.props.title}**
|
|
58
84
|
\`\`\`bash
|
|
@@ -101,8 +127,8 @@ ${context.cleanText(content.text)}
|
|
|
101
127
|
},
|
|
102
128
|
|
|
103
129
|
Image: async ({ props, context }) => {
|
|
104
|
-
if (!/^https?:/.test(props.href) && !(await fs.stat(props.href
|
|
105
|
-
throw new
|
|
130
|
+
if (!/^https?:/.test(props.href) && !(await fs.stat(props.href, { throwIfNoEntry: false }))) {
|
|
131
|
+
throw new RuntimeError(`${props.href} is not a valid location`);
|
|
106
132
|
}
|
|
107
133
|
return `})`;
|
|
108
134
|
},
|
package/src/render/renderer.ts
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
3
|
import { type ManifestContext, PackageUtil } from '@travetto/manifest';
|
|
4
|
-
import {
|
|
4
|
+
import { type Class, castTo, Runtime, RuntimeError } from '@travetto/runtime';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { isJSXElement, type JSXElement, JSXFragmentType } from '../../support/jsx-runtime.ts';
|
|
7
|
+
import { type c, EMPTY_ELEMENT, getComponentName, type JSXElementByFn } from '../jsx.ts';
|
|
7
8
|
import type { DocumentShape, RenderProvider, RenderState } from '../types.ts';
|
|
8
9
|
import { DocFileUtil } from '../util/file.ts';
|
|
9
|
-
|
|
10
10
|
import { RenderContext } from './context.ts';
|
|
11
11
|
import { Html } from './html.ts';
|
|
12
12
|
import { Markdown } from './markdown.ts';
|
|
13
13
|
|
|
14
|
-
import { isJSXElement, type JSXElement, JSXFragmentType } from '../../support/jsx-runtime.ts';
|
|
15
|
-
|
|
16
14
|
const providers = { [Html.ext]: Html, [Markdown.ext]: Markdown };
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* Doc Renderer
|
|
20
18
|
*/
|
|
21
19
|
export class DocRenderer {
|
|
22
|
-
|
|
23
20
|
static async get(file: string, manifest: Pick<ManifestContext, 'workspace'>): Promise<DocRenderer> {
|
|
24
21
|
const document = await Runtime.importFrom<DocumentShape>(file);
|
|
25
22
|
const pkg = PackageUtil.readPackage(manifest.workspace.path);
|
|
26
23
|
const repoBaseUrl = pkg.travetto?.doc?.baseUrl ?? manifest.workspace.path;
|
|
27
|
-
return new DocRenderer(
|
|
24
|
+
return new DocRenderer(
|
|
25
|
+
document,
|
|
28
26
|
new RenderContext(file, repoBaseUrl, path.resolve(pkg.travetto?.doc?.root ?? manifest.workspace.path))
|
|
29
27
|
);
|
|
30
28
|
}
|
|
@@ -38,11 +36,7 @@ export class DocRenderer {
|
|
|
38
36
|
this.#support = support;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
async #buildLink(
|
|
42
|
-
renderer: RenderProvider<RenderContext>,
|
|
43
|
-
cls: Class,
|
|
44
|
-
title?: string
|
|
45
|
-
) {
|
|
39
|
+
async #buildLink(renderer: RenderProvider<RenderContext>, cls: Class, title?: string) {
|
|
46
40
|
const source = DocFileUtil.readSource(cls);
|
|
47
41
|
if (source) {
|
|
48
42
|
title = DocFileUtil.isDecorator(cls.name, source.file) ? `@${title ?? cls.name}` : (title ?? cls.name);
|
|
@@ -53,7 +47,11 @@ export class DocRenderer {
|
|
|
53
47
|
});
|
|
54
48
|
// @ts-expect-error
|
|
55
49
|
const state: RenderState<JSXElementByFn<'CodeLink'>, RenderContext> = {
|
|
56
|
-
node,
|
|
50
|
+
node,
|
|
51
|
+
props: node.props,
|
|
52
|
+
recurse: async () => '',
|
|
53
|
+
context: this.#support,
|
|
54
|
+
stack: []
|
|
57
55
|
};
|
|
58
56
|
// @ts-expect-error
|
|
59
57
|
state.createState = (key, props) => this.createState(state, key, props);
|
|
@@ -66,7 +64,6 @@ export class DocRenderer {
|
|
|
66
64
|
input: JSXElement[] | JSXElement | string | bigint | object | number | boolean | null | undefined,
|
|
67
65
|
stack: JSXElement[] = []
|
|
68
66
|
): Promise<string | undefined> {
|
|
69
|
-
|
|
70
67
|
if (input === null || input === undefined) {
|
|
71
68
|
return '';
|
|
72
69
|
} else if (Array.isArray(input)) {
|
|
@@ -99,21 +96,27 @@ export class DocRenderer {
|
|
|
99
96
|
const recurse = () => this.#render(renderer, final.props.children ?? [], [...stack, final]);
|
|
100
97
|
// @ts-expect-error
|
|
101
98
|
const state: RenderState<JSXElement, RenderContext> = {
|
|
102
|
-
node: final,
|
|
99
|
+
node: final,
|
|
100
|
+
props: final.props,
|
|
101
|
+
recurse,
|
|
102
|
+
stack,
|
|
103
|
+
context: this.#support
|
|
103
104
|
};
|
|
104
105
|
state.createState = (key, props) => this.createState(state, key, props);
|
|
105
106
|
// @ts-expect-error
|
|
106
107
|
return renderer[name](state);
|
|
107
108
|
} else {
|
|
108
109
|
console.log(final);
|
|
109
|
-
throw new
|
|
110
|
+
throw new RuntimeError(`Unknown element: ${final.type}`);
|
|
110
111
|
}
|
|
111
112
|
} else {
|
|
112
113
|
switch (typeof input) {
|
|
113
|
-
case 'string':
|
|
114
|
+
case 'string':
|
|
115
|
+
return input.replace(/ /g, ' ');
|
|
114
116
|
case 'number':
|
|
115
117
|
case 'bigint':
|
|
116
|
-
case 'boolean':
|
|
118
|
+
case 'boolean':
|
|
119
|
+
return `${input}`;
|
|
117
120
|
case 'object': {
|
|
118
121
|
if (input) {
|
|
119
122
|
return await this.#buildLink(renderer, castTo(input.constructor), input.constructor.name.replace(/^[$]/, ''));
|
|
@@ -124,7 +127,7 @@ export class DocRenderer {
|
|
|
124
127
|
return await this.#buildLink(renderer, castTo(input));
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
|
-
throw new
|
|
130
|
+
throw new RuntimeError(`Unknown object type: ${typeof input}`);
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
|
@@ -144,18 +147,17 @@ export class DocRenderer {
|
|
|
144
147
|
*/
|
|
145
148
|
async render(format: keyof typeof providers): Promise<string> {
|
|
146
149
|
if (!providers[format]) {
|
|
147
|
-
throw new
|
|
150
|
+
throw new RuntimeError(`Unknown renderer with format: ${format}`);
|
|
148
151
|
}
|
|
149
152
|
if (!this.#rootNode) {
|
|
150
|
-
this.#rootNode =
|
|
151
|
-
this.#root.text : await (this.#root.text());
|
|
153
|
+
this.#rootNode = Array.isArray(this.#root.text) || isJSXElement(this.#root.text) ? this.#root.text : await this.#root.text();
|
|
152
154
|
}
|
|
153
155
|
|
|
154
156
|
const text = await this.#render(providers[format], this.#rootNode);
|
|
155
|
-
let cleaned = `${text?.replace(/\n{3,100}/
|
|
157
|
+
let cleaned = `${text?.replace(/\n{3,100}/gms, '\n\n').trim()}\n`;
|
|
156
158
|
if (this.#root.wrap?.[format]) {
|
|
157
159
|
cleaned = this.#root.wrap[format](cleaned);
|
|
158
160
|
}
|
|
159
161
|
return providers[format].finalize(cleaned, this.#support);
|
|
160
162
|
}
|
|
161
|
-
}
|
|
163
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { JSXElementByFn, c } from './jsx.ts';
|
|
2
1
|
import type { JSXElement, ValidHtmlTags } from '../support/jsx-runtime.ts';
|
|
2
|
+
import type { c, JSXElementByFn } from './jsx.ts';
|
|
3
3
|
|
|
4
4
|
export type Wrapper = Record<string, (cnt: string) => string>;
|
|
5
5
|
|
|
@@ -25,11 +25,10 @@ export type RenderState<T extends JSXElement, C> = {
|
|
|
25
25
|
/**
|
|
26
26
|
* Renderer
|
|
27
27
|
*/
|
|
28
|
-
export type RenderProvider<C> =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} &
|
|
33
|
-
{ [K in ValidHtmlTags]: (state: RenderState<JSXElement<K>, C>) => Promise<string>; } &
|
|
28
|
+
export type RenderProvider<C> = {
|
|
29
|
+
ext: string;
|
|
30
|
+
finalize: (text: string, ctx: C) => string;
|
|
31
|
+
} & { [K in ValidHtmlTags]: (state: RenderState<JSXElement<K>, C>) => Promise<string> } & {
|
|
34
32
|
// @ts-expect-error
|
|
35
|
-
|
|
33
|
+
[K in keyof typeof c]: (state: RenderState<JSXElementByFn<K>, C>) => Promise<string>;
|
|
34
|
+
};
|
package/src/util/file.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { RuntimeError, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
5
4
|
import { type ManifestModuleFileType, ManifestModuleUtil } from '@travetto/manifest';
|
|
5
|
+
import { Runtime, RuntimeError, RuntimeIndex } from '@travetto/runtime';
|
|
6
6
|
|
|
7
7
|
import type { CodeSourceInput } from './types.ts';
|
|
8
8
|
|
|
@@ -23,21 +23,20 @@ const MOD_FILE_TO_LANG: Record<ManifestModuleFileType, string | undefined> = {
|
|
|
23
23
|
const EXT_TO_LANG: Record<string, string> = {
|
|
24
24
|
'.yaml': 'yaml',
|
|
25
25
|
'.yml': 'yaml',
|
|
26
|
-
'.sh': 'bash'
|
|
26
|
+
'.sh': 'bash'
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
type SourceOutput = { content: string
|
|
30
|
-
type SnippetOutput = { file: string
|
|
29
|
+
type SourceOutput = { content: string; language: string; file: string };
|
|
30
|
+
type SnippetOutput = { file: string; startIdx: number; lines: string[]; language: string };
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Standard file utilities
|
|
34
34
|
*/
|
|
35
35
|
export class DocFileUtil {
|
|
36
|
-
|
|
37
36
|
static #decCache: Record<string, boolean> = {};
|
|
38
37
|
|
|
39
38
|
static isFile(file: string): boolean {
|
|
40
|
-
return /^[@:A-Za-z0-9
|
|
39
|
+
return /^[@:A-Za-z0-9/\\_.-]+[.]([a-z]{2,10})$/.test(file);
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
static readSource(input: Exclude<CodeSourceInput, Promise<string>>): SourceOutput {
|
|
@@ -66,16 +65,14 @@ export class DocFileUtil {
|
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
if (content) {
|
|
69
|
-
content = content
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
.replace(ENV_KEY, (_, key) => `'${key}'`)
|
|
73
|
-
)
|
|
68
|
+
content = content
|
|
69
|
+
.split(/\n/)
|
|
70
|
+
.map(line => line.replace(ESLINT_PATTERN, '').replace(ENV_KEY, (_, key) => `'${key}'`))
|
|
74
71
|
.join('\n')
|
|
75
|
-
.replace(/^\/\/# sourceMap.*$/
|
|
76
|
-
.replace(/[ ]*[/][/][ ]*@ts-expect-error[^\n]*\n/
|
|
77
|
-
.replace(/^[ ]*[/][/][ ]*[{][{][^\n]*\n/
|
|
78
|
-
.replace(/[ ]*[/][/][ ]*[{][{][^\n]*/
|
|
72
|
+
.replace(/^\/\/# sourceMap.*$/gms, '')
|
|
73
|
+
.replace(/[ ]*[/][/][ ]*@ts-expect-error[^\n]*\n/gms, '') // Excluding errors
|
|
74
|
+
.replace(/^[ ]*[/][/][ ]*[{][{][^\n]*\n/gms, '') // Excluding conditional comments, full-line
|
|
75
|
+
.replace(/[ ]*[/][/][ ]*[{][{][^\n]*/gms, ''); // Excluding conditional comments
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
if (file !== undefined) {
|
|
@@ -102,7 +99,6 @@ export class DocFileUtil {
|
|
|
102
99
|
* Determine if a file is a decorator
|
|
103
100
|
*/
|
|
104
101
|
static isDecorator(name: string, file: string): boolean {
|
|
105
|
-
|
|
106
102
|
const key = `${name}:${file}`;
|
|
107
103
|
if (key in this.#decCache) {
|
|
108
104
|
return this.#decCache[key];
|
|
@@ -131,32 +127,36 @@ export class DocFileUtil {
|
|
|
131
127
|
*/
|
|
132
128
|
static buildOutline(code: string): string {
|
|
133
129
|
let methodPrefix = '';
|
|
134
|
-
code = code
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
130
|
+
code = code
|
|
131
|
+
.split(/\n/)
|
|
132
|
+
.map(line => {
|
|
133
|
+
if (!methodPrefix) {
|
|
134
|
+
const info = line.match(
|
|
135
|
+
/^(\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})/
|
|
136
|
+
);
|
|
137
|
+
if (info) {
|
|
138
|
+
const [, space, __name, rest] = info;
|
|
139
|
+
if (!rest.endsWith(';')) {
|
|
140
|
+
if (/\s{0,50}[{]\s{0,50}return.{0,200}$/.test(line)) {
|
|
141
|
+
return line.replace(/\s{0,50}[{]\s{0,50}return.{0,200}$/, ';');
|
|
142
|
+
} else {
|
|
143
|
+
methodPrefix = space;
|
|
144
|
+
return line.replace(/\s{0,50}[{]\s{0,50}$/, ';');
|
|
145
|
+
}
|
|
145
146
|
}
|
|
146
147
|
}
|
|
148
|
+
return line;
|
|
149
|
+
} else {
|
|
150
|
+
if (line.startsWith(`${methodPrefix}}`)) {
|
|
151
|
+
methodPrefix = '';
|
|
152
|
+
}
|
|
153
|
+
return '';
|
|
147
154
|
}
|
|
148
|
-
|
|
149
|
-
} else {
|
|
150
|
-
if (line.startsWith(`${methodPrefix}}`)) {
|
|
151
|
-
methodPrefix = '';
|
|
152
|
-
}
|
|
153
|
-
return '';
|
|
154
|
-
}
|
|
155
|
-
})
|
|
155
|
+
})
|
|
156
156
|
.filter(line => !/#|(\b(private|protected)\b)/.test(line))
|
|
157
157
|
.filter(line => !!line)
|
|
158
158
|
.join('\n');
|
|
159
159
|
|
|
160
160
|
return code;
|
|
161
161
|
}
|
|
162
|
-
}
|
|
162
|
+
}
|
package/src/util/package.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type NodePackageManager, PACKAGE_MANAGERS } from '@travetto/manifest';
|
|
2
2
|
import { Runtime } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
export class PackageDocUtil {
|
|
@@ -8,8 +8,10 @@ export class PackageDocUtil {
|
|
|
8
8
|
static getPackageCommand(pkg: string, args: string[] = [], manager?: NodePackageManager): string {
|
|
9
9
|
switch (manager ?? Runtime.workspace.manager) {
|
|
10
10
|
case 'npm':
|
|
11
|
-
case 'yarn':
|
|
12
|
-
|
|
11
|
+
case 'yarn':
|
|
12
|
+
return `npx ${pkg} ${args.join(' ')}`.trim();
|
|
13
|
+
case 'pnpm':
|
|
14
|
+
return `pnpm ${pkg} ${args.join(' ')}`.trim();
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -18,9 +20,12 @@ export class PackageDocUtil {
|
|
|
18
20
|
*/
|
|
19
21
|
static getWorkspaceInitCommand(manager?: NodePackageManager): string {
|
|
20
22
|
switch (manager ?? Runtime.workspace.manager) {
|
|
21
|
-
case 'npm':
|
|
22
|
-
|
|
23
|
-
case '
|
|
23
|
+
case 'npm':
|
|
24
|
+
return 'npm init -f';
|
|
25
|
+
case 'yarn':
|
|
26
|
+
return 'yarn init -y';
|
|
27
|
+
case 'pnpm':
|
|
28
|
+
return 'pnpm init -y';
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
|
|
@@ -29,9 +34,12 @@ export class PackageDocUtil {
|
|
|
29
34
|
*/
|
|
30
35
|
static getInstallCommand(pkg: string, production = false, manager?: NodePackageManager): string {
|
|
31
36
|
switch (manager ?? Runtime.workspace.manager) {
|
|
32
|
-
case 'npm':
|
|
33
|
-
|
|
34
|
-
case '
|
|
37
|
+
case 'npm':
|
|
38
|
+
return `npm install ${production ? '' : '--save-dev '}${pkg}`;
|
|
39
|
+
case 'yarn':
|
|
40
|
+
return `yarn add ${production ? '' : '--dev '}${pkg}`;
|
|
41
|
+
case 'pnpm':
|
|
42
|
+
return `pnpm add ${production ? '' : '--dev '}${pkg}`;
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
|
|
@@ -39,8 +47,6 @@ export class PackageDocUtil {
|
|
|
39
47
|
* Get install example for a given package
|
|
40
48
|
*/
|
|
41
49
|
static getInstallInstructions(pkg: string, production = false): string {
|
|
42
|
-
return PACKAGE_MANAGERS
|
|
43
|
-
.map(manager => this.getInstallCommand(pkg, production, manager.type))
|
|
44
|
-
.join('\n\n# or\n\n');
|
|
50
|
+
return PACKAGE_MANAGERS.map(manager => this.getInstallCommand(pkg, production, manager.type)).join('\n\n# or\n\n');
|
|
45
51
|
}
|
|
46
|
-
}
|
|
52
|
+
}
|
package/src/util/resolve.ts
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { DocFileUtil } from './file.ts';
|
|
2
2
|
import type { CodeProps, CodeSourceInput } from './types.ts';
|
|
3
3
|
|
|
4
|
-
export type ResolvedRef = { title: string
|
|
5
|
-
export type ResolvedCode = { text: string
|
|
6
|
-
export type ResolvedSnippet = { text: string
|
|
7
|
-
export type ResolvedSnippetLink = { file: string
|
|
4
|
+
export type ResolvedRef = { title: string; file: string; line: number };
|
|
5
|
+
export type ResolvedCode = { text: string; language: string; file?: string };
|
|
6
|
+
export type ResolvedSnippet = { text: string; language: string; file: string; line: number };
|
|
7
|
+
export type ResolvedSnippetLink = { file: string; line: number };
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Resolve utilities
|
|
11
11
|
*/
|
|
12
12
|
export class DocResolveUtil {
|
|
13
|
-
|
|
14
13
|
static async resolveRef(title: string, file: string): Promise<ResolvedRef> {
|
|
15
|
-
|
|
16
14
|
let line = 0;
|
|
17
15
|
const result = DocFileUtil.readSource(file);
|
|
18
16
|
file = result.file;
|
|
19
17
|
|
|
20
18
|
if (result.content) {
|
|
21
|
-
line = result.content
|
|
19
|
+
line = result.content
|
|
20
|
+
.split(/\n/g)
|
|
22
21
|
.findIndex(lineText => new RegExp(`(class|interface|function)[ ]+${RegExp.escape(title)}`).test(lineText));
|
|
23
22
|
if (line < 0) {
|
|
24
23
|
line = 0;
|
|
@@ -67,9 +66,9 @@ export class DocResolveUtil {
|
|
|
67
66
|
|
|
68
67
|
static applyCodePropDefaults(props: CodeProps): void {
|
|
69
68
|
const type = typeof props.src === 'function' ? props.src : undefined;
|
|
70
|
-
props.startRe ??=
|
|
71
|
-
props.language ??=
|
|
72
|
-
props.endRe ??=
|
|
73
|
-
props.title ??= typeof props.src
|
|
69
|
+
props.startRe ??= type ? new RegExp(`^(export)?\\s*(interface|class)\\s+${RegExp.escape(type.name)}\\b`) : undefined;
|
|
70
|
+
props.language ??= type ? 'typescript' : undefined;
|
|
71
|
+
props.endRe ??= type ? /^[}]/ : undefined;
|
|
72
|
+
props.title ??= typeof props.src === 'function' ? props.src.name.replace(/^[$]/, '') : undefined;
|
|
74
73
|
}
|
|
75
|
-
}
|
|
74
|
+
}
|
package/src/util/run.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import util from 'node:util';
|
|
3
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import os from 'node:os';
|
|
4
3
|
import path from 'node:path';
|
|
4
|
+
import util from 'node:util';
|
|
5
5
|
|
|
6
6
|
import { Env, ExecUtil, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
7
|
+
|
|
7
8
|
import type { RunConfig } from './types.ts';
|
|
8
9
|
|
|
9
10
|
export const COMMON_DATE = new Date('2029-03-14T00:00:00.000-0400').getTime();
|
|
@@ -25,7 +26,8 @@ class DocState {
|
|
|
25
26
|
|
|
26
27
|
getId(id: string): string {
|
|
27
28
|
if (!this.ids[id]) {
|
|
28
|
-
this.ids[id] = ' '
|
|
29
|
+
this.ids[id] = ' '
|
|
30
|
+
.repeat(id.length)
|
|
29
31
|
.split('')
|
|
30
32
|
.map(_ => Math.trunc(this.rng() * 16).toString(16))
|
|
31
33
|
.join('');
|
|
@@ -42,7 +44,7 @@ export class DocRunUtil {
|
|
|
42
44
|
|
|
43
45
|
/** Build working directory from config */
|
|
44
46
|
static workingDirectory(config: RunConfig): string {
|
|
45
|
-
return path.resolve(config.module ? RuntimeIndex.getModule(config.module)?.sourcePath! : Runtime.mainSourcePath);
|
|
47
|
+
return path.resolve(config.module ? (RuntimeIndex.getModule(config.module)?.sourcePath ?? undefined!) : Runtime.mainSourcePath);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/**
|
|
@@ -50,20 +52,22 @@ export class DocRunUtil {
|
|
|
50
52
|
*/
|
|
51
53
|
static cleanRunOutput(text: string, config: RunConfig): string {
|
|
52
54
|
const rootPath = this.workingDirectory(config);
|
|
53
|
-
text = util
|
|
55
|
+
text = util
|
|
56
|
+
.stripVTControlCharacters(text.trim())
|
|
54
57
|
.replaceAll(rootPath, '.')
|
|
55
58
|
.replaceAll(os.tmpdir(), '/tmp')
|
|
56
59
|
.replaceAll(Runtime.workspace.path, '<workspace-root>')
|
|
57
|
-
.replace(/[/]tmp[/][a-z_A-Z0-9
|
|
60
|
+
.replace(/[/]tmp[/][a-z_A-Z0-9/-]+/g, '/tmp/<temp-folder>')
|
|
58
61
|
.replace(/^(\s*framework:\s*')(\d+[.]\d+)[^']*('[,]?\s*)$/gm, (_, pre, ver, post) => `${pre}${ver}.x${post}`)
|
|
59
62
|
.replace(/^(\s*nodeVersion:\s*'v)(\d+)[^']*('[,]?\s*)$/gm, (_, pre, ver, post) => `${pre}${ver}.x.x${post}`)
|
|
60
63
|
.replace(/^(.{1,4})?Compiling[.]*/, '') // Compiling message, remove
|
|
61
|
-
.replace(/[A-Za-z0-9_
|
|
64
|
+
.replace(/[A-Za-z0-9_./\\-]+\/travetto\/module\//g, '@travetto/')
|
|
62
65
|
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}([.]\d{3})?Z?/g, this.#docState.getDate.bind(this.#docState))
|
|
63
|
-
.replace(/\b[0-9a-f]{4}[0-9a-f
|
|
66
|
+
.replace(/\b[0-9a-f]{4}[0-9a-f-]{8,40}\b/gi, this.#docState.getId.bind(this.#docState))
|
|
64
67
|
.replace(/(\d+[.]\d+[.]\d+)-(alpha|rc)[.]\d+/g, (all, value) => value);
|
|
65
68
|
if (config.filter || config.rewrite) {
|
|
66
|
-
text = text
|
|
69
|
+
text = text
|
|
70
|
+
.split(/\n/g)
|
|
67
71
|
.filter(line => config.filter?.(line) ?? true)
|
|
68
72
|
.map(line => config.rewrite?.(line) ?? line)
|
|
69
73
|
.join('\n');
|
|
@@ -108,4 +112,4 @@ export class DocRunUtil {
|
|
|
108
112
|
|
|
109
113
|
return this.cleanRunOutput(final, config);
|
|
110
114
|
}
|
|
111
|
-
}
|
|
115
|
+
}
|
package/src/util/types.ts
CHANGED
|
@@ -11,4 +11,4 @@ export type RunConfig = {
|
|
|
11
11
|
|
|
12
12
|
export type CodeSourceInput = string | Promise<string> | Function;
|
|
13
13
|
|
|
14
|
-
export type CodeProps = { title?: string
|
|
14
|
+
export type CodeProps = { title?: string; src: CodeSourceInput; language?: string; outline?: boolean; startRe?: RegExp; endRe?: RegExp };
|
package/support/cli.doc.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { type CliCommandShape, CliCommand } from '@travetto/cli';
|
|
6
|
-
import { MinLength, Validator } from '@travetto/schema';
|
|
4
|
+
import { CliCommand, type CliCommandShape } from '@travetto/cli';
|
|
7
5
|
import { PackageUtil } from '@travetto/manifest';
|
|
6
|
+
import { Env, ExecUtil, Runtime, WatchUtil } from '@travetto/runtime';
|
|
7
|
+
import { MinLength, Validator } from '@travetto/schema';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Generate documentation outputs from a module `DOC.tsx` entry file.
|
|
11
|
+
*
|
|
12
|
+
* Supports multiple output formats/targets and optional watch mode for
|
|
13
|
+
* iterative documentation authoring.
|
|
11
14
|
*/
|
|
12
15
|
@CliCommand()
|
|
13
|
-
@Validator(async
|
|
16
|
+
@Validator(async cmd => {
|
|
14
17
|
const docFile = path.resolve(cmd.input);
|
|
15
|
-
if (!(await fs.stat(docFile
|
|
18
|
+
if (!(await fs.stat(docFile, { throwIfNoEntry: false }))) {
|
|
16
19
|
return { message: `input: ${cmd.input} does not exist`, path: 'input', source: 'flag', kind: 'invalid' };
|
|
17
20
|
}
|
|
18
21
|
})
|
|
19
22
|
export class DocCommand implements CliCommandShape {
|
|
20
|
-
|
|
21
23
|
/** Input File */
|
|
22
24
|
input = 'DOC.tsx';
|
|
23
25
|
|
|
@@ -33,7 +35,7 @@ export class DocCommand implements CliCommandShape {
|
|
|
33
35
|
Env.TRV_ROLE.set('doc');
|
|
34
36
|
Env.TRV_CLI_IPC.clear();
|
|
35
37
|
Env.TRV_LOG_PLAIN.set(true);
|
|
36
|
-
Env.FORCE_COLOR.set(false)
|
|
38
|
+
Env.FORCE_COLOR.set(false); // Prevent restarting
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
async runWatch(): Promise<void> {
|
|
@@ -54,8 +56,7 @@ export class DocCommand implements CliCommandShape {
|
|
|
54
56
|
const { DocRenderer } = await import('../src/render/renderer.ts');
|
|
55
57
|
const ctx = await DocRenderer.get(this.input, Runtime);
|
|
56
58
|
const outputs = this.outputs.map(output =>
|
|
57
|
-
output.includes('.') ? [path.extname(output).replace('.', ''), path.resolve(output)] :
|
|
58
|
-
[output, null] as const
|
|
59
|
+
output.includes('.') ? [path.extname(output).replace('.', ''), path.resolve(output)] : ([output, null] as const)
|
|
59
60
|
);
|
|
60
61
|
|
|
61
62
|
for (const [format, out] of outputs) {
|
|
@@ -75,4 +76,4 @@ export class DocCommand implements CliCommandShape {
|
|
|
75
76
|
|
|
76
77
|
return this.watch ? this.runWatch() : this.render();
|
|
77
78
|
}
|
|
78
|
-
}
|
|
79
|
+
}
|