@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.
Files changed (74) hide show
  1. package/CHANGELOG.md +785 -0
  2. package/LICENSE +21 -0
  3. package/README.md +62 -0
  4. package/README.ru.md +63 -0
  5. package/build/app.client.css +47 -0
  6. package/build/app.client.js +3 -0
  7. package/build/index.js +3993 -0
  8. package/build/index.js.map +7 -0
  9. package/build/lib.js +3374 -0
  10. package/build/lib.js.map +7 -0
  11. package/build/linter.js +1265 -0
  12. package/build/linter.js.map +7 -0
  13. package/package.json +126 -0
  14. package/src/cmd/build/index.ts +304 -0
  15. package/src/cmd/index.ts +4 -0
  16. package/src/cmd/publish/index.ts +92 -0
  17. package/src/cmd/publish/upload.ts +61 -0
  18. package/src/cmd/translate/index.ts +261 -0
  19. package/src/cmd/xliff/compose.ts +222 -0
  20. package/src/cmd/xliff/extract.ts +237 -0
  21. package/src/cmd/xliff/index.ts +27 -0
  22. package/src/constants.ts +122 -0
  23. package/src/globals.d.ts +1 -0
  24. package/src/index.ts +54 -0
  25. package/src/models.ts +249 -0
  26. package/src/packages/credentials/index.ts +1 -0
  27. package/src/packages/credentials/yandex-oauth.ts +42 -0
  28. package/src/resolvers/index.ts +3 -0
  29. package/src/resolvers/lintPage.ts +119 -0
  30. package/src/resolvers/md2html.ts +142 -0
  31. package/src/resolvers/md2md.ts +147 -0
  32. package/src/services/argv.ts +38 -0
  33. package/src/services/authors.ts +64 -0
  34. package/src/services/contributors.ts +104 -0
  35. package/src/services/includers/batteries/common.ts +34 -0
  36. package/src/services/includers/batteries/generic.ts +130 -0
  37. package/src/services/includers/batteries/index.ts +3 -0
  38. package/src/services/includers/batteries/sourcedocs.ts +33 -0
  39. package/src/services/includers/batteries/unarchive.ts +97 -0
  40. package/src/services/includers/index.ts +157 -0
  41. package/src/services/index.ts +6 -0
  42. package/src/services/leading.ts +88 -0
  43. package/src/services/metadata.ts +249 -0
  44. package/src/services/plugins.ts +76 -0
  45. package/src/services/preset.ts +55 -0
  46. package/src/services/tocs.ts +401 -0
  47. package/src/services/utils.ts +151 -0
  48. package/src/steps/index.ts +6 -0
  49. package/src/steps/processAssets.ts +36 -0
  50. package/src/steps/processExcludedFiles.ts +47 -0
  51. package/src/steps/processLinter.ts +100 -0
  52. package/src/steps/processLogs.ts +18 -0
  53. package/src/steps/processMapFile.ts +35 -0
  54. package/src/steps/processPages.ts +312 -0
  55. package/src/steps/processServiceFiles.ts +95 -0
  56. package/src/steps/publishFilesToS3.ts +47 -0
  57. package/src/utils/file.ts +17 -0
  58. package/src/utils/glob.ts +14 -0
  59. package/src/utils/index.ts +8 -0
  60. package/src/utils/logger.ts +42 -0
  61. package/src/utils/markup.ts +125 -0
  62. package/src/utils/path.ts +24 -0
  63. package/src/utils/presets.ts +20 -0
  64. package/src/utils/singlePage.ts +228 -0
  65. package/src/utils/toc.ts +87 -0
  66. package/src/utils/url.ts +3 -0
  67. package/src/utils/worker.ts +10 -0
  68. package/src/validator.ts +150 -0
  69. package/src/vcs-connector/client/github.ts +52 -0
  70. package/src/vcs-connector/connector-models.ts +76 -0
  71. package/src/vcs-connector/connector-validator.ts +114 -0
  72. package/src/vcs-connector/github.ts +333 -0
  73. package/src/vcs-connector/index.ts +15 -0
  74. 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,3 @@
1
+ export * as generic from './generic';
2
+ export * as sourcedocs from './sourcedocs';
3
+ export * as unarchive from './unarchive';
@@ -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};