@diplodoc/cli 4.8.0 → 4.9.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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "author": "Yandex Data UI Team <data-ui@yandex-team.ru>",
4
4
  "description": "Make documentation using yfm-docs in Markdown and HTML formats",
5
5
  "license": "MIT",
6
- "version": "4.8.0",
6
+ "version": "4.9.0",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git@github.com:diplodoc-platform/cli.git"
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "@aws-sdk/client-s3": "^3.369.0",
34
34
  "@diplodoc/client": "^2.0.0",
35
- "@diplodoc/latex-extension": "^1.0.2",
35
+ "@diplodoc/latex-extension": "^1.0.3",
36
36
  "@diplodoc/markdown-translation": "^1.0.4",
37
37
  "@diplodoc/mermaid-extension": "^1.2.1",
38
38
  "@diplodoc/openapi-extension": "^1.4.10",
package/src/models.ts CHANGED
@@ -19,6 +19,7 @@ export type NestedContributorsForPathFunction = (
19
19
  ) => void;
20
20
  export type UserByLoginFunction = (login: string) => Promise<Contributor | null>;
21
21
  export type CollectionOfPluginsFunction = (output: string, options: PluginOptions) => string;
22
+ export type GetModifiedTimeByPathFunction = (filepath: string) => number | undefined;
22
23
 
23
24
  interface YfmConfig {
24
25
  varsPreset: VarsPreset;
@@ -94,7 +94,10 @@ async function getContributorsForNestedFiles(
94
94
  return Object.assign({}, ...includesContributors);
95
95
  }
96
96
 
97
- function getRelativeIncludeFilePaths(fileData: FileData, includeContents: string[]): Set<string> {
97
+ function getRelativeIncludeFilePaths(
98
+ fileData: Pick<FileData, 'tmpInputFilePath'>,
99
+ includeContents: string[],
100
+ ): Set<string> {
98
101
  const {tmpInputFilePath} = fileData;
99
102
  const relativeIncludeFilePaths: Set<string> = new Set();
100
103
 
@@ -115,4 +118,45 @@ function getRelativeIncludeFilePaths(fileData: FileData, includeContents: string
115
118
  return relativeIncludeFilePaths;
116
119
  }
117
120
 
118
- export {getFileContributorsMetadata, getFileContributorsString};
121
+ async function getFileIncludes(
122
+ fileData: Pick<FileData, 'fileContent' | 'tmpInputFilePath' | 'inputFolderPathLength'>,
123
+ ) {
124
+ const {fileContent, tmpInputFilePath, inputFolderPathLength} = fileData;
125
+
126
+ const results = new Set<string>();
127
+
128
+ const includeContents = fileContent.match(REGEXP_INCLUDE_CONTENTS);
129
+ if (!includeContents || includeContents.length === 0) {
130
+ return [];
131
+ }
132
+ const relativeIncludeFilePaths: Set<string> = getRelativeIncludeFilePaths(
133
+ {tmpInputFilePath},
134
+ includeContents,
135
+ );
136
+ for (const relativeIncludeFilePath of relativeIncludeFilePaths.values()) {
137
+ const relativeFilePath = relativeIncludeFilePath.substring(inputFolderPathLength + 1);
138
+ if (results.has(relativeFilePath)) continue;
139
+ results.add(relativeFilePath);
140
+
141
+ let contentIncludeFile: string;
142
+ try {
143
+ contentIncludeFile = await readFile(relativeIncludeFilePath, 'utf8');
144
+ } catch (err) {
145
+ if (err.code === 'ENOENT') {
146
+ continue;
147
+ }
148
+ throw err;
149
+ }
150
+
151
+ const includedPaths = await getFileIncludes({
152
+ inputFolderPathLength,
153
+ fileContent: contentIncludeFile,
154
+ tmpInputFilePath: relativeIncludeFilePath,
155
+ });
156
+ includedPaths.forEach((path) => results.add(path));
157
+ }
158
+
159
+ return Array.from(results.values());
160
+ }
161
+
162
+ export {getFileContributorsMetadata, getFileContributorsString, getFileIncludes};
@@ -7,12 +7,16 @@ import {
7
7
  updateAuthorMetadataStringByAuthorLogin,
8
8
  updateAuthorMetadataStringByFilePath,
9
9
  } from './authors';
10
- import {getFileContributorsMetadata, getFileContributorsString} from './contributors';
10
+ import {
11
+ getFileContributorsMetadata,
12
+ getFileContributorsString,
13
+ getFileIncludes,
14
+ } from './contributors';
11
15
  import {isObject} from './utils';
12
16
  import {сarriage} from '../utils';
13
17
  import {REGEXP_AUTHOR, metadataBorder} from '../constants';
14
18
  import {dirname, relative, resolve} from 'path';
15
- import {ArgvService} from './index';
19
+ import {ArgvService, TocService} from './index';
16
20
 
17
21
  async function getContentWithUpdatedMetadata(
18
22
  fileContent: string,
@@ -105,6 +109,11 @@ async function getContentWithUpdatedDynamicMetadata(
105
109
  newMetadatas.push(contributorsMetaData);
106
110
  }
107
111
 
112
+ const mtimeMetadata = await getModifiedTimeMetadataString(options, fileContent);
113
+ if (mtimeMetadata) {
114
+ newMetadatas.push(mtimeMetadata);
115
+ }
116
+
108
117
  let authorMetadata = '';
109
118
  if (fileMetadata) {
110
119
  const matchAuthor = fileMetadata.match(REGEXP_AUTHOR);
@@ -188,6 +197,37 @@ async function getContributorsMetadataString(
188
197
  return undefined;
189
198
  }
190
199
 
200
+ async function getModifiedTimeMetadataString(options: MetaDataOptions, fileContent: string) {
201
+ const {isContributorsEnabled, vcsConnector, fileData} = options;
202
+
203
+ const {tmpInputFilePath, inputFolderPathLength} = fileData;
204
+
205
+ const relativeFilePath = tmpInputFilePath.substring(inputFolderPathLength + 1);
206
+
207
+ if (!isContributorsEnabled || !vcsConnector) {
208
+ return undefined;
209
+ }
210
+
211
+ const includedFiles = await getFileIncludes({...fileData, fileContent});
212
+ includedFiles.push(relativeFilePath);
213
+
214
+ const tocCopyFileMap = TocService.getCopyFileMap();
215
+
216
+ const mtimeList = includedFiles
217
+ .map((path) => {
218
+ const mappedPath = tocCopyFileMap.get(path) || path;
219
+ return vcsConnector.getModifiedTimeByPath(mappedPath);
220
+ })
221
+ .filter((v) => typeof v === 'number') as number[];
222
+
223
+ if (mtimeList.length) {
224
+ const mtime = Math.max(...mtimeList);
225
+ return `updatedAt: ${new Date(mtime * 1000).toISOString()}`;
226
+ }
227
+
228
+ return undefined;
229
+ }
230
+
191
231
  function getUpdatedMetadataString(newMetadatas: string[], defaultMetadata = ''): string {
192
232
  const newMetadata = newMetadatas.join(сarriage) + (newMetadatas.length ? сarriage : '');
193
233
  const preparedDefaultMetadata = defaultMetadata.trimRight();
@@ -24,6 +24,7 @@ export interface TocServiceData {
24
24
  const storage: TocServiceData['storage'] = new Map();
25
25
  let navigationPaths: TocServiceData['navigationPaths'] = [];
26
26
  const includedTocPaths: TocServiceData['includedTocPaths'] = new Set();
27
+ const tocFileCopyMap = new Map<string, string>();
27
28
 
28
29
  async function add(path: string) {
29
30
  const {
@@ -207,6 +208,10 @@ function _copyTocDir(tocPath: string, destDir: string) {
207
208
  } else {
208
209
  shell.cp(from, to);
209
210
  }
211
+
212
+ const relFrom = relative(inputFolderPath, from);
213
+ const relTo = relative(inputFolderPath, to);
214
+ tocFileCopyMap.set(relTo, relFrom);
210
215
  });
211
216
  }
212
217
 
@@ -396,6 +401,10 @@ function setNavigationPaths(paths: TocServiceData['navigationPaths']) {
396
401
  navigationPaths = paths;
397
402
  }
398
403
 
404
+ function getCopyFileMap() {
405
+ return tocFileCopyMap;
406
+ }
407
+
399
408
  export default {
400
409
  add,
401
410
  getForPath,
@@ -403,4 +412,5 @@ export default {
403
412
  getTocDir,
404
413
  getIncludedTocPaths,
405
414
  setNavigationPaths,
415
+ getCopyFileMap,
406
416
  };
@@ -2,6 +2,7 @@ import {
2
2
  Contributors,
3
3
  ContributorsByPathFunction,
4
4
  ExternalAuthorByPathFunction,
5
+ GetModifiedTimeByPathFunction,
5
6
  NestedContributorsForPathFunction,
6
7
  UserByLoginFunction,
7
8
  } from '../models';
@@ -31,6 +32,7 @@ export interface VCSConnector {
31
32
  addNestedContributorsForPath: NestedContributorsForPathFunction;
32
33
  getContributorsByPath: ContributorsByPathFunction;
33
34
  getUserByLogin: UserByLoginFunction;
35
+ getModifiedTimeByPath: GetModifiedTimeByPathFunction;
34
36
  }
35
37
 
36
38
  export interface VCSConnectorConfig {
@@ -33,9 +33,10 @@ const authorByPath: Map<string, Contributor | null> = new Map();
33
33
  const contributorsByPath: Map<string, FileContributors> = new Map();
34
34
  const contributorsData: Map<string, Contributor | null> = new Map();
35
35
  const loginUserMap: Map<string, Contributor | null> = new Map();
36
+ const pathMTime = new Map<string, number>();
36
37
 
37
38
  async function getGitHubVCSConnector(): Promise<VCSConnector | undefined> {
38
- const {contributors} = ArgvService.getConfig();
39
+ const {contributors, rootInput} = ArgvService.getConfig();
39
40
 
40
41
  const httpClientByToken = getHttpClientByToken();
41
42
  if (!httpClientByToken) {
@@ -49,6 +50,7 @@ async function getGitHubVCSConnector(): Promise<VCSConnector | undefined> {
49
50
  authorByPath.get(path) ?? null;
50
51
 
51
52
  if (contributors) {
53
+ await getFilesMTime(rootInput, pathMTime);
52
54
  await getAllContributorsTocFiles(httpClientByToken);
53
55
  addNestedContributorsForPath = (path: string, nestedContributors: Contributors) =>
54
56
  addNestedContributorsForPathFunction(path, nestedContributors);
@@ -60,6 +62,7 @@ async function getGitHubVCSConnector(): Promise<VCSConnector | undefined> {
60
62
  addNestedContributorsForPath,
61
63
  getContributorsByPath,
62
64
  getUserByLogin: (login: string) => getUserByLogin(httpClientByToken, login),
65
+ getModifiedTimeByPath: (filename: string) => pathMTime.get(filename),
63
66
  };
64
67
  }
65
68
 
@@ -364,4 +367,42 @@ function shouldAuthorBeIgnored({email, name}: ShouldAuthorBeIgnoredArgs) {
364
367
  return false;
365
368
  }
366
369
 
370
+ async function getFilesMTime(repoDir: string, pathMTime: Map<string, number>) {
371
+ const timeFiles = await simpleGit({
372
+ baseDir: repoDir,
373
+ }).raw(
374
+ 'log',
375
+ '--reverse',
376
+ '--before=now',
377
+ '--diff-filter=ADMR',
378
+ '--pretty=format:%ct',
379
+ '--name-status',
380
+ );
381
+
382
+ const parts = timeFiles.split(/\n\n/);
383
+ parts.forEach((part) => {
384
+ const lines = part.trim().split(/\n/);
385
+ const committerDate = lines.shift();
386
+ const unixtime = Number(committerDate);
387
+ lines.forEach((line) => {
388
+ const [status, from, to] = line.split(/\t/);
389
+ switch (status[0]) {
390
+ case 'R': {
391
+ pathMTime.delete(from);
392
+ pathMTime.set(to, unixtime);
393
+ break;
394
+ }
395
+ case 'D': {
396
+ pathMTime.delete(from);
397
+ break;
398
+ }
399
+ default: {
400
+ pathMTime.set(from, unixtime);
401
+ }
402
+ }
403
+ });
404
+ });
405
+ return pathMTime;
406
+ }
407
+
367
408
  export default getGitHubVCSConnector;