@diplodoc/cli 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +785 -0
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/README.ru.md +63 -0
- package/build/app.client.css +47 -0
- package/build/app.client.js +3 -0
- package/build/index.js +3993 -0
- package/build/index.js.map +7 -0
- package/build/lib.js +3374 -0
- package/build/lib.js.map +7 -0
- package/build/linter.js +1265 -0
- package/build/linter.js.map +7 -0
- package/package.json +126 -0
- package/src/cmd/build/index.ts +304 -0
- package/src/cmd/index.ts +4 -0
- package/src/cmd/publish/index.ts +92 -0
- package/src/cmd/publish/upload.ts +61 -0
- package/src/cmd/translate/index.ts +261 -0
- package/src/cmd/xliff/compose.ts +222 -0
- package/src/cmd/xliff/extract.ts +237 -0
- package/src/cmd/xliff/index.ts +27 -0
- package/src/constants.ts +122 -0
- package/src/globals.d.ts +1 -0
- package/src/index.ts +54 -0
- package/src/models.ts +249 -0
- package/src/packages/credentials/index.ts +1 -0
- package/src/packages/credentials/yandex-oauth.ts +42 -0
- package/src/resolvers/index.ts +3 -0
- package/src/resolvers/lintPage.ts +119 -0
- package/src/resolvers/md2html.ts +142 -0
- package/src/resolvers/md2md.ts +147 -0
- package/src/services/argv.ts +38 -0
- package/src/services/authors.ts +64 -0
- package/src/services/contributors.ts +104 -0
- package/src/services/includers/batteries/common.ts +34 -0
- package/src/services/includers/batteries/generic.ts +130 -0
- package/src/services/includers/batteries/index.ts +3 -0
- package/src/services/includers/batteries/sourcedocs.ts +33 -0
- package/src/services/includers/batteries/unarchive.ts +97 -0
- package/src/services/includers/index.ts +157 -0
- package/src/services/index.ts +6 -0
- package/src/services/leading.ts +88 -0
- package/src/services/metadata.ts +249 -0
- package/src/services/plugins.ts +76 -0
- package/src/services/preset.ts +55 -0
- package/src/services/tocs.ts +401 -0
- package/src/services/utils.ts +151 -0
- package/src/steps/index.ts +6 -0
- package/src/steps/processAssets.ts +36 -0
- package/src/steps/processExcludedFiles.ts +47 -0
- package/src/steps/processLinter.ts +100 -0
- package/src/steps/processLogs.ts +18 -0
- package/src/steps/processMapFile.ts +35 -0
- package/src/steps/processPages.ts +312 -0
- package/src/steps/processServiceFiles.ts +95 -0
- package/src/steps/publishFilesToS3.ts +47 -0
- package/src/utils/file.ts +17 -0
- package/src/utils/glob.ts +14 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +42 -0
- package/src/utils/markup.ts +125 -0
- package/src/utils/path.ts +24 -0
- package/src/utils/presets.ts +20 -0
- package/src/utils/singlePage.ts +228 -0
- package/src/utils/toc.ts +87 -0
- package/src/utils/url.ts +3 -0
- package/src/utils/worker.ts +10 -0
- package/src/validator.ts +150 -0
- package/src/vcs-connector/client/github.ts +52 -0
- package/src/vcs-connector/connector-models.ts +76 -0
- package/src/vcs-connector/connector-validator.ts +114 -0
- package/src/vcs-connector/github.ts +333 -0
- package/src/vcs-connector/index.ts +15 -0
- package/src/workers/linter/index.ts +62 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {basename, dirname, join, relative, resolve, sep} from 'path';
|
|
2
|
+
import {readFileSync, writeFileSync} from 'fs';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
|
|
5
|
+
import transform, {Output} from '@doc-tools/transform';
|
|
6
|
+
import log from '@doc-tools/transform/lib/log';
|
|
7
|
+
import liquid from '@doc-tools/transform/lib/liquid';
|
|
8
|
+
|
|
9
|
+
import {ResolverOptions, YfmToc, ResolveMd2HTMLResult, LeadingPage} from '../models';
|
|
10
|
+
import {ArgvService, TocService, PluginService} from '../services';
|
|
11
|
+
import {generateStaticMarkup, logger, transformToc, getVarsPerFile, getVarsPerRelativeFile} from '../utils';
|
|
12
|
+
import {PROCESSING_FINISHED, Lang} from '../constants';
|
|
13
|
+
import {getAssetsPublicPath, getUpdatedMetadata} from '../services/metadata';
|
|
14
|
+
import {MarkdownItPluginCb} from '@doc-tools/transform/lib/plugins/typings';
|
|
15
|
+
|
|
16
|
+
export interface FileTransformOptions {
|
|
17
|
+
path: string;
|
|
18
|
+
root?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const FileTransformer: Record<string, Function> = {
|
|
22
|
+
'.yaml': YamlFileTransformer,
|
|
23
|
+
'.md': MdFileTransformer,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const fixRelativePath = (relativeTo: string) => (path: string) => {
|
|
27
|
+
return join(getAssetsPublicPath(relativeTo), path);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function resolveMd2HTML(options: ResolverOptions): Promise<ResolveMd2HTMLResult> {
|
|
31
|
+
const {inputPath, fileExtension, outputPath, outputBundlePath, metadata} = options;
|
|
32
|
+
|
|
33
|
+
const pathToDir: string = dirname(inputPath);
|
|
34
|
+
const toc: YfmToc|null = TocService.getForPath(inputPath) || null;
|
|
35
|
+
const tocBase: string = toc && toc.base ? toc.base : '';
|
|
36
|
+
const pathToFileDir: string = pathToDir === tocBase ? '' : pathToDir.replace(`${tocBase}${sep}`, '');
|
|
37
|
+
const relativePathToIndex = relative(pathToDir, `${tocBase}${sep}`);
|
|
38
|
+
|
|
39
|
+
const {input, lang, allowCustomResources} = ArgvService.getConfig();
|
|
40
|
+
const resolvedPath: string = resolve(input, inputPath);
|
|
41
|
+
const content: string = readFileSync(resolvedPath, 'utf8');
|
|
42
|
+
|
|
43
|
+
const transformFn: Function = FileTransformer[fileExtension];
|
|
44
|
+
const {result} = transformFn(content, {path: inputPath});
|
|
45
|
+
|
|
46
|
+
const updatedMetadata = metadata && metadata.isContributorsEnabled
|
|
47
|
+
? await getUpdatedMetadata(metadata, content, result?.meta)
|
|
48
|
+
: result.meta;
|
|
49
|
+
|
|
50
|
+
const fileMeta = fileExtension === '.yaml' ? (result.data.meta ?? {}) : updatedMetadata;
|
|
51
|
+
|
|
52
|
+
if (allowCustomResources) {
|
|
53
|
+
const {script, style} = metadata?.resources || {};
|
|
54
|
+
fileMeta.style = (fileMeta.style || []).concat(style || [])
|
|
55
|
+
.map(fixRelativePath(inputPath));
|
|
56
|
+
fileMeta.script = (fileMeta.script || []).concat(script || [])
|
|
57
|
+
.map(fixRelativePath(inputPath));
|
|
58
|
+
} else {
|
|
59
|
+
fileMeta.style = [];
|
|
60
|
+
fileMeta.script = [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const props = {
|
|
64
|
+
data: {
|
|
65
|
+
leading: inputPath.endsWith('.yaml'),
|
|
66
|
+
toc: transformToc(toc, pathToDir) || {},
|
|
67
|
+
...result,
|
|
68
|
+
meta: fileMeta,
|
|
69
|
+
},
|
|
70
|
+
router: {
|
|
71
|
+
pathname: join(relativePathToIndex, pathToFileDir, basename(outputPath)),
|
|
72
|
+
},
|
|
73
|
+
lang: lang || Lang.RU,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const outputDir = dirname(outputPath);
|
|
77
|
+
const relativePathToBundle: string = relative(resolve(outputDir), resolve(outputBundlePath));
|
|
78
|
+
|
|
79
|
+
const outputFileContent = generateStaticMarkup(props, relativePathToBundle);
|
|
80
|
+
writeFileSync(outputPath, outputFileContent);
|
|
81
|
+
logger.info(inputPath, PROCESSING_FINISHED);
|
|
82
|
+
|
|
83
|
+
return props;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function YamlFileTransformer(content: string): Object {
|
|
87
|
+
let data: LeadingPage | null = null;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
data = yaml.load(content) as LeadingPage;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
log.error(`Yaml transform has been failed. Error: ${error}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!data) {
|
|
96
|
+
return {
|
|
97
|
+
result: {data: {}},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const links = data?.links?.map(
|
|
102
|
+
(link) =>
|
|
103
|
+
link.href ? ({...link, href: link.href.replace(/.md$/gmu, '.html')}) : link,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (links) { data.links = links; }
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
result: {data},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function liquidMd2Html(input: string, vars: Record<string, unknown>, path: string) {
|
|
114
|
+
const {conditionsInCode} = ArgvService.getConfig();
|
|
115
|
+
|
|
116
|
+
return liquid(input, vars, path, {
|
|
117
|
+
conditionsInCode,
|
|
118
|
+
withSourceMap: true,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function MdFileTransformer(content: string, transformOptions: FileTransformOptions): Output {
|
|
123
|
+
const {input, ...options} = ArgvService.getConfig();
|
|
124
|
+
const {path: filePath} = transformOptions;
|
|
125
|
+
|
|
126
|
+
const plugins = PluginService.getPlugins();
|
|
127
|
+
const vars = getVarsPerFile(filePath);
|
|
128
|
+
const root = resolve(input);
|
|
129
|
+
const path: string = resolve(input, filePath);
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
return transform(content, {
|
|
133
|
+
...options,
|
|
134
|
+
plugins: plugins as MarkdownItPluginCb<unknown>[],
|
|
135
|
+
vars,
|
|
136
|
+
root,
|
|
137
|
+
path,
|
|
138
|
+
assetsPublicPath: getAssetsPublicPath(filePath),
|
|
139
|
+
getVarsPerFile: getVarsPerRelativeFile,
|
|
140
|
+
extractTitle: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {existsSync, readFileSync, writeFileSync} from 'fs';
|
|
2
|
+
import {dirname, resolve, join, basename, extname} from 'path';
|
|
3
|
+
import shell from 'shelljs';
|
|
4
|
+
import log from '@doc-tools/transform/lib/log';
|
|
5
|
+
import liquid from '@doc-tools/transform/lib/liquid';
|
|
6
|
+
|
|
7
|
+
import {ArgvService, PluginService} from '../services';
|
|
8
|
+
import {logger, getVarsPerFile} from '../utils';
|
|
9
|
+
import {PluginOptions, ResolveMd2MdOptions} from '../models';
|
|
10
|
+
import {PROCESSING_FINISHED} from '../constants';
|
|
11
|
+
import {getContentWithUpdatedMetadata} from '../services/metadata';
|
|
12
|
+
import {ChangelogItem} from '@doc-tools/transform/lib/plugins/changelog/types';
|
|
13
|
+
|
|
14
|
+
export async function resolveMd2Md(options: ResolveMd2MdOptions): Promise<void> {
|
|
15
|
+
const {inputPath, outputPath, metadata} = options;
|
|
16
|
+
const {input, output} = ArgvService.getConfig();
|
|
17
|
+
const resolvedInputPath = resolve(input, inputPath);
|
|
18
|
+
const vars = getVarsPerFile(inputPath);
|
|
19
|
+
|
|
20
|
+
const content = await getContentWithUpdatedMetadata(
|
|
21
|
+
readFileSync(resolvedInputPath, 'utf8'),
|
|
22
|
+
metadata,
|
|
23
|
+
vars.__system,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const {result, changelogs} = transformMd2Md(content, {
|
|
27
|
+
path: resolvedInputPath,
|
|
28
|
+
destPath: outputPath,
|
|
29
|
+
root: resolve(input),
|
|
30
|
+
destRoot: resolve(output),
|
|
31
|
+
collectOfPlugins: PluginService.getCollectOfPlugins(),
|
|
32
|
+
vars,
|
|
33
|
+
log,
|
|
34
|
+
copyFile,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
writeFileSync(outputPath, result);
|
|
38
|
+
|
|
39
|
+
if (changelogs?.length) {
|
|
40
|
+
const mdFilename = basename(outputPath, extname(outputPath));
|
|
41
|
+
const outputDir = dirname(outputPath);
|
|
42
|
+
changelogs.forEach((changes, index) => {
|
|
43
|
+
let changesName;
|
|
44
|
+
const changesDate = changes.date as string | undefined;
|
|
45
|
+
const changesIdx = changes.index as number | undefined;
|
|
46
|
+
if (typeof changesIdx === 'number') {
|
|
47
|
+
changesName = String(changesIdx);
|
|
48
|
+
}
|
|
49
|
+
if (!changesName && changesDate && /^\d{4}/.test(changesDate)) {
|
|
50
|
+
changesName = Math.trunc(new Date(changesDate).getTime() / 1000);
|
|
51
|
+
}
|
|
52
|
+
if (!changesName) {
|
|
53
|
+
changesName = `name-${mdFilename}-${String(changelogs.length - index).padStart(3, '0')}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const changesPath = join(outputDir, `changes-${changesName}.json`);
|
|
57
|
+
|
|
58
|
+
if (existsSync(changesPath)) {
|
|
59
|
+
throw new Error(`Changelog ${changesPath} already exists!`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
writeFileSync(changesPath, JSON.stringify({
|
|
63
|
+
...changes,
|
|
64
|
+
source: mdFilename,
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
logger.info(inputPath, PROCESSING_FINISHED);
|
|
70
|
+
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function copyFile(targetPath: string, targetDestPath: string, options?: PluginOptions) {
|
|
75
|
+
shell.mkdir('-p', dirname(targetDestPath));
|
|
76
|
+
|
|
77
|
+
if (options) {
|
|
78
|
+
const sourceIncludeContent = readFileSync(targetPath, 'utf8');
|
|
79
|
+
const {result} = transformMd2Md(sourceIncludeContent, options);
|
|
80
|
+
|
|
81
|
+
writeFileSync(targetDestPath, result);
|
|
82
|
+
} else {
|
|
83
|
+
shell.cp(targetPath, targetDestPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function liquidMd2Md(input: string, vars: Record<string, unknown>, path: string) {
|
|
88
|
+
const {
|
|
89
|
+
applyPresets,
|
|
90
|
+
resolveConditions,
|
|
91
|
+
conditionsInCode,
|
|
92
|
+
} = ArgvService.getConfig();
|
|
93
|
+
|
|
94
|
+
return liquid(input, vars, path, {
|
|
95
|
+
conditions: resolveConditions,
|
|
96
|
+
substitutions: applyPresets,
|
|
97
|
+
conditionsInCode,
|
|
98
|
+
withSourceMap: true,
|
|
99
|
+
keepNotVar: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function transformMd2Md(input: string, options: PluginOptions) {
|
|
104
|
+
const {
|
|
105
|
+
disableLiquid,
|
|
106
|
+
} = ArgvService.getConfig();
|
|
107
|
+
const {
|
|
108
|
+
vars = {},
|
|
109
|
+
path,
|
|
110
|
+
root,
|
|
111
|
+
destPath,
|
|
112
|
+
destRoot,
|
|
113
|
+
collectOfPlugins,
|
|
114
|
+
log: pluginLog,
|
|
115
|
+
copyFile: pluginCopyFile,
|
|
116
|
+
} = options;
|
|
117
|
+
|
|
118
|
+
let output = input;
|
|
119
|
+
const changelogs: ChangelogItem[] = [];
|
|
120
|
+
|
|
121
|
+
if (!disableLiquid) {
|
|
122
|
+
const liquidResult = liquidMd2Md(input, vars, path);
|
|
123
|
+
|
|
124
|
+
output = liquidResult.output;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (collectOfPlugins) {
|
|
128
|
+
output = collectOfPlugins(output, {
|
|
129
|
+
vars,
|
|
130
|
+
path,
|
|
131
|
+
root,
|
|
132
|
+
destPath,
|
|
133
|
+
destRoot,
|
|
134
|
+
log: pluginLog,
|
|
135
|
+
copyFile: pluginCopyFile,
|
|
136
|
+
collectOfPlugins,
|
|
137
|
+
changelogs,
|
|
138
|
+
extractChangelogs: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
result: output,
|
|
144
|
+
changelogs,
|
|
145
|
+
logs: pluginLog.get(),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {YfmArgv} from '../models';
|
|
2
|
+
import {join} from 'path';
|
|
3
|
+
import {readFileSync} from 'fs';
|
|
4
|
+
|
|
5
|
+
let _argv!: YfmArgv;
|
|
6
|
+
|
|
7
|
+
function getConfig() {
|
|
8
|
+
return _argv;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
function init(argv: any) {
|
|
13
|
+
_argv = {
|
|
14
|
+
...argv,
|
|
15
|
+
ignore: Array.isArray(argv.ignore) ? argv.ignore : [],
|
|
16
|
+
} as YfmArgv;
|
|
17
|
+
|
|
18
|
+
if (argv.vars) {
|
|
19
|
+
_argv.vars = JSON.parse(argv.vars);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const ignorefile = readFileSync(join(_argv.rootInput, '.yfmignore'), 'utf8');
|
|
24
|
+
const ignore = ignorefile.split('\n');
|
|
25
|
+
|
|
26
|
+
_argv.ignore = _argv.ignore.concat(ignore);
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function set(argv: YfmArgv) {
|
|
31
|
+
_argv = argv;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
getConfig,
|
|
36
|
+
init,
|
|
37
|
+
set,
|
|
38
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {replaceDoubleToSingleQuotes} from '../utils';
|
|
2
|
+
import {VCSConnector} from '../vcs-connector/connector-models';
|
|
3
|
+
|
|
4
|
+
async function updateAuthorMetadataStringByAuthorLogin(
|
|
5
|
+
authorLogin: string,
|
|
6
|
+
vcsConnector?: VCSConnector,
|
|
7
|
+
): Promise<string> {
|
|
8
|
+
if (!vcsConnector) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const user = await getAuthorDetails(vcsConnector, authorLogin);
|
|
13
|
+
|
|
14
|
+
if (user) {
|
|
15
|
+
return user;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async function updateAuthorMetadataStringByFilePath(
|
|
23
|
+
filePath: string,
|
|
24
|
+
vcsConnector?: VCSConnector,
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
if (!vcsConnector) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const user = vcsConnector.getExternalAuthorByPath(filePath);
|
|
31
|
+
|
|
32
|
+
if (user) {
|
|
33
|
+
const author = replaceDoubleToSingleQuotes(JSON.stringify(user));
|
|
34
|
+
return author;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function getAuthorDetails(vcsConnector: VCSConnector, author: string | object): Promise<string | null> {
|
|
41
|
+
if (typeof author === 'object') {
|
|
42
|
+
// Avoiding problems when adding to html markup
|
|
43
|
+
return replaceDoubleToSingleQuotes(JSON.stringify(author));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
JSON.parse(author);
|
|
48
|
+
return replaceDoubleToSingleQuotes(author);
|
|
49
|
+
} catch {
|
|
50
|
+
const user = await vcsConnector.getUserByLogin(author);
|
|
51
|
+
|
|
52
|
+
if (user) {
|
|
53
|
+
return replaceDoubleToSingleQuotes(JSON.stringify(user));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
updateAuthorMetadataStringByAuthorLogin,
|
|
62
|
+
updateAuthorMetadataStringByFilePath,
|
|
63
|
+
getAuthorDetails,
|
|
64
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {readFile} from 'fs/promises';
|
|
2
|
+
import {dirname, join} from 'path';
|
|
3
|
+
|
|
4
|
+
import {replaceDoubleToSingleQuotes} from '../utils';
|
|
5
|
+
import {REGEXP_INCLUDE_CONTENTS, REGEXP_INCLUDE_FILE_PATH} from '../constants';
|
|
6
|
+
import {Contributor, Contributors, FileData} from '../models';
|
|
7
|
+
import {FileContributors, VCSConnector} from '../vcs-connector/connector-models';
|
|
8
|
+
|
|
9
|
+
async function getFileContributorsMetadata(fileData: FileData, vcsConnector: VCSConnector): Promise<string> {
|
|
10
|
+
const contributors = await getFileContributorsString(fileData, vcsConnector);
|
|
11
|
+
|
|
12
|
+
return `contributors: ${contributors}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getFileContributorsString(fileData: FileData, vcsConnector: VCSConnector): Promise<string> {
|
|
16
|
+
const {tmpInputFilePath, inputFolderPathLength} = fileData;
|
|
17
|
+
|
|
18
|
+
const relativeFilePath = tmpInputFilePath.substring(inputFolderPathLength);
|
|
19
|
+
const fileContributors: FileContributors = await vcsConnector.getContributorsByPath(relativeFilePath);
|
|
20
|
+
let nestedContributors: Contributors = {};
|
|
21
|
+
|
|
22
|
+
if (!fileContributors.hasIncludes) {
|
|
23
|
+
nestedContributors = await getContributorsForNestedFiles(fileData, vcsConnector);
|
|
24
|
+
vcsConnector.addNestedContributorsForPath(relativeFilePath, nestedContributors);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const fileContributorsWithContributorsIncludedFiles: Contributors = {
|
|
28
|
+
...fileContributors.contributors,
|
|
29
|
+
...nestedContributors,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const contributorsArray: Contributor[] =
|
|
33
|
+
Object.entries(fileContributorsWithContributorsIncludedFiles).map(([, contributor]) => contributor);
|
|
34
|
+
|
|
35
|
+
return replaceDoubleToSingleQuotes(JSON.stringify(contributorsArray));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getContributorsForNestedFiles(fileData: FileData, vcsConnector: VCSConnector): Promise<Contributors> {
|
|
39
|
+
const {fileContent, inputFolderPathLength} = fileData;
|
|
40
|
+
|
|
41
|
+
const includeContents = fileContent.match(REGEXP_INCLUDE_CONTENTS);
|
|
42
|
+
if (!includeContents || includeContents.length === 0) {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const includesContributors: Contributors[] = [];
|
|
47
|
+
const relativeIncludeFilePaths: Set<string> = getRelativeIncludeFilePaths(fileData, includeContents);
|
|
48
|
+
|
|
49
|
+
for (const relativeIncludeFilePath of relativeIncludeFilePaths.values()) {
|
|
50
|
+
const relativeFilePath = relativeIncludeFilePath.substring(inputFolderPathLength);
|
|
51
|
+
const includeContributors = await vcsConnector.getContributorsByPath(relativeFilePath);
|
|
52
|
+
|
|
53
|
+
let nestedContributors: Contributors = {};
|
|
54
|
+
|
|
55
|
+
if (!includeContributors.hasIncludes) {
|
|
56
|
+
let contentIncludeFile: string;
|
|
57
|
+
try {
|
|
58
|
+
contentIncludeFile = await readFile(relativeIncludeFilePath, 'utf8');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err.code === 'ENOENT') {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const newFileData: FileData = {
|
|
67
|
+
...fileData,
|
|
68
|
+
fileContent: contentIncludeFile,
|
|
69
|
+
tmpInputFilePath: relativeIncludeFilePath,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
nestedContributors = await getContributorsForNestedFiles(newFileData, vcsConnector);
|
|
73
|
+
vcsConnector.addNestedContributorsForPath(relativeFilePath, nestedContributors);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
includesContributors.push(includeContributors.contributors);
|
|
77
|
+
includesContributors.push(nestedContributors);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Object.assign({}, ...includesContributors);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getRelativeIncludeFilePaths(fileData: FileData, includeContents: string[]): Set<string> {
|
|
84
|
+
const {tmpInputFilePath} = fileData;
|
|
85
|
+
const relativeIncludeFilePaths: Set<string> = new Set();
|
|
86
|
+
|
|
87
|
+
includeContents.forEach((includeContent: string) => {
|
|
88
|
+
const relativeIncludeFilePath = includeContent.match(REGEXP_INCLUDE_FILE_PATH);
|
|
89
|
+
|
|
90
|
+
if (relativeIncludeFilePath && relativeIncludeFilePath.length !== 0) {
|
|
91
|
+
const relativeIncludeFilePathWithoutHash = relativeIncludeFilePath[0].split('#');
|
|
92
|
+
const includeFilePath = join(dirname(tmpInputFilePath), relativeIncludeFilePathWithoutHash[0]);
|
|
93
|
+
|
|
94
|
+
relativeIncludeFilePaths.add(includeFilePath);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
return relativeIncludeFilePaths;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
getFileContributorsMetadata,
|
|
103
|
+
getFileContributorsString,
|
|
104
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
const {promises: {readdir}} = require('fs');
|
|
3
|
+
|
|
4
|
+
async function getDirs(path: string) {
|
|
5
|
+
const isDir = (i: any) => i.isDirectory();
|
|
6
|
+
|
|
7
|
+
return readdir(path, {withFileTypes: true}).then((list: any) => list.filter(isDir));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function getFiles(path: string) {
|
|
11
|
+
const isFile = (i: any) => i.isFile();
|
|
12
|
+
|
|
13
|
+
return readdir(path, {withFileTypes: true}).then((list: any) => list.filter(isFile));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const complement = (fn: Function) => (x: any) => !fn(x);
|
|
17
|
+
|
|
18
|
+
const isMdExtension = (str: string): boolean => /.md$/gmu.test(str);
|
|
19
|
+
|
|
20
|
+
const isHidden = (str: string) => /^\./gmu.test(str);
|
|
21
|
+
|
|
22
|
+
const allPass = (predicates: Function[]) => (arg: any) =>
|
|
23
|
+
predicates.map((fn) => fn(arg)).reduce((p, c) => p && c, true);
|
|
24
|
+
|
|
25
|
+
const compose = <R>(fn1: (a: R) => R, ...fns: Array<(a: R) => R>) =>
|
|
26
|
+
fns.reduce((prevFn, nextFn) => (value) => prevFn(nextFn(value)), fn1);
|
|
27
|
+
|
|
28
|
+
const prop = (string: string) => (object: Object) => object[string as keyof typeof object];
|
|
29
|
+
|
|
30
|
+
function concatNewLine(prefix: string, suffix: string) {
|
|
31
|
+
return prefix.trim().length ? `${prefix}<br>${suffix}` : suffix;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {complement, isMdExtension, isHidden, allPass, compose, prop, getDirs, getFiles, concatNewLine};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import {readFile, writeFile, mkdir} from 'fs/promises';
|
|
2
|
+
import {parse, join, dirname} from 'path';
|
|
3
|
+
|
|
4
|
+
import {updateWith} from 'lodash';
|
|
5
|
+
import {dump} from 'js-yaml';
|
|
6
|
+
|
|
7
|
+
import {glob} from '../../../utils/glob';
|
|
8
|
+
|
|
9
|
+
import {IncluderFunctionParams} from '../../../models';
|
|
10
|
+
|
|
11
|
+
class GenericIncluderError extends Error {
|
|
12
|
+
path: string;
|
|
13
|
+
|
|
14
|
+
constructor(message: string, path: string) {
|
|
15
|
+
super(message);
|
|
16
|
+
|
|
17
|
+
this.name = 'GenericIncluderError';
|
|
18
|
+
this.path = path;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const name = 'generic';
|
|
23
|
+
|
|
24
|
+
const MD_GLOB = '**/*.md';
|
|
25
|
+
|
|
26
|
+
type Params = {
|
|
27
|
+
input: string;
|
|
28
|
+
leadingPage: {
|
|
29
|
+
name?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function includerFunction(params: IncluderFunctionParams<Params>) {
|
|
34
|
+
const {readBasePath, writeBasePath, tocPath, item, passedParams: {input, leadingPage}, index} = params;
|
|
35
|
+
|
|
36
|
+
if (!input?.length || !item.include?.path) {
|
|
37
|
+
throw new GenericIncluderError('provide includer with input parameter', tocPath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const leadingPageName = leadingPage?.name ?? 'Overview';
|
|
42
|
+
|
|
43
|
+
const tocDirPath = dirname(tocPath);
|
|
44
|
+
|
|
45
|
+
const contentPath = index === 0
|
|
46
|
+
? join(writeBasePath, tocDirPath, input)
|
|
47
|
+
: join(readBasePath, tocDirPath, input);
|
|
48
|
+
|
|
49
|
+
let cache = {};
|
|
50
|
+
let found = [];
|
|
51
|
+
|
|
52
|
+
({state: {found, cache}} = await glob(MD_GLOB, {
|
|
53
|
+
cwd: contentPath,
|
|
54
|
+
nosort: true,
|
|
55
|
+
nocase: true,
|
|
56
|
+
cache,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
const writePath = join(writeBasePath, tocDirPath, item.include.path);
|
|
60
|
+
|
|
61
|
+
await mkdir(writePath, {recursive: true});
|
|
62
|
+
|
|
63
|
+
for (const filePath of found) {
|
|
64
|
+
const file = await readFile(join(contentPath, filePath));
|
|
65
|
+
|
|
66
|
+
await mkdir(dirname(join(writePath, filePath)), {recursive: true});
|
|
67
|
+
await writeFile(join(writePath, filePath), file);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const graph = createGraphFromPaths(found);
|
|
71
|
+
|
|
72
|
+
const toc = createToc(leadingPageName, item.include.path)(graph, []);
|
|
73
|
+
|
|
74
|
+
await writeFile(join(writePath, 'toc.yaml'), dump(toc));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new GenericIncluderError(err.toString(), tocPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createGraphFromPaths(paths: string[]) {
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
+
const graph: Record<string, any> = {};
|
|
83
|
+
|
|
84
|
+
for (const path of paths) {
|
|
85
|
+
const chunks = path.split('/').filter(Boolean);
|
|
86
|
+
if (chunks.length < 2) {
|
|
87
|
+
if (chunks.length === 1) {
|
|
88
|
+
graph.files = chunks;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const file = chunks.pop();
|
|
95
|
+
|
|
96
|
+
updateWith(graph, chunks, (old) => {
|
|
97
|
+
return old ? {files: [...old.files, file]} : {files: [file]};
|
|
98
|
+
}, Object);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return graph;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createToc(leadingPageName: string, tocName: string) {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
106
|
+
return function createTocRec(graph: Record<string, any>, cursor: string[]): Record<string, any> {
|
|
107
|
+
const handler = (file: string) => ({
|
|
108
|
+
name: parse(file).name === 'index' ? leadingPageName : file,
|
|
109
|
+
href: join(...cursor, file),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const recurse = (key: string) => createTocRec(graph[key], [...cursor, key]);
|
|
113
|
+
|
|
114
|
+
const current = {
|
|
115
|
+
name: cursor[cursor.length - 1] ?? tocName,
|
|
116
|
+
items: [
|
|
117
|
+
...(graph.files ?? []).map(handler),
|
|
118
|
+
...Object.keys(graph)
|
|
119
|
+
.filter((key) => key !== 'files')
|
|
120
|
+
.map(recurse),
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return current;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {name, includerFunction};
|
|
129
|
+
|
|
130
|
+
export default {name, includerFunction};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {logger} from '../../../utils/logger';
|
|
2
|
+
|
|
3
|
+
import generic from './generic';
|
|
4
|
+
|
|
5
|
+
import {IncluderFunctionParams} from '../../../models';
|
|
6
|
+
|
|
7
|
+
const name = 'sourcedocs';
|
|
8
|
+
|
|
9
|
+
const usage = `include:
|
|
10
|
+
path: <path-where-to-include>
|
|
11
|
+
includers:
|
|
12
|
+
- name: generic
|
|
13
|
+
input: <path-to-directory-with-markdown>
|
|
14
|
+
leadingPage:
|
|
15
|
+
name: <leading-page-name>
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
type Params = {
|
|
19
|
+
input: string;
|
|
20
|
+
leadingPage: {
|
|
21
|
+
name?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
async function includerFunction(params: IncluderFunctionParams<Params>) {
|
|
26
|
+
logger.warn(params.tocPath, `sourcedocs inlcuder is getting depricated in favor of generic includer\n${usage}`);
|
|
27
|
+
|
|
28
|
+
await generic.includerFunction(params);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export {name, includerFunction};
|
|
32
|
+
|
|
33
|
+
export default {name, includerFunction};
|