@docusaurus/utils 3.9.2-canary-6436 → 3.9.2-canary-6439

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 (55) hide show
  1. package/lib/index.d.ts +3 -2
  2. package/lib/index.d.ts.map +1 -1
  3. package/lib/index.js +8 -5
  4. package/lib/index.js.map +1 -1
  5. package/lib/lastUpdateUtils.d.ts +2 -6
  6. package/lib/lastUpdateUtils.d.ts.map +1 -1
  7. package/lib/lastUpdateUtils.js +13 -60
  8. package/lib/lastUpdateUtils.js.map +1 -1
  9. package/lib/{gitUtils.d.ts → vcs/gitUtils.d.ts} +17 -0
  10. package/lib/vcs/gitUtils.d.ts.map +1 -0
  11. package/lib/vcs/gitUtils.js +330 -0
  12. package/lib/vcs/gitUtils.js.map +1 -0
  13. package/lib/vcs/vcs.d.ts +19 -0
  14. package/lib/vcs/vcs.d.ts.map +1 -0
  15. package/lib/vcs/vcs.js +46 -0
  16. package/lib/vcs/vcs.js.map +1 -0
  17. package/lib/vcs/vcsDefaultV1.d.ts +13 -0
  18. package/lib/vcs/vcsDefaultV1.d.ts.map +1 -0
  19. package/lib/vcs/vcsDefaultV1.js +33 -0
  20. package/lib/vcs/vcsDefaultV1.js.map +1 -0
  21. package/lib/vcs/vcsDefaultV2.d.ts +13 -0
  22. package/lib/vcs/vcsDefaultV2.d.ts.map +1 -0
  23. package/lib/vcs/vcsDefaultV2.js +33 -0
  24. package/lib/vcs/vcsDefaultV2.js.map +1 -0
  25. package/lib/vcs/vcsDisabled.d.ts +12 -0
  26. package/lib/vcs/vcsDisabled.d.ts.map +1 -0
  27. package/lib/vcs/vcsDisabled.js +24 -0
  28. package/lib/vcs/vcsDisabled.js.map +1 -0
  29. package/lib/vcs/vcsGitAdHoc.d.ts +16 -0
  30. package/lib/vcs/vcsGitAdHoc.d.ts.map +1 -0
  31. package/lib/vcs/vcsGitAdHoc.js +29 -0
  32. package/lib/vcs/vcsGitAdHoc.js.map +1 -0
  33. package/lib/vcs/vcsGitEager.d.ts +9 -0
  34. package/lib/vcs/vcsGitEager.d.ts.map +1 -0
  35. package/lib/vcs/vcsGitEager.js +68 -0
  36. package/lib/vcs/vcsGitEager.js.map +1 -0
  37. package/lib/vcs/vcsHardcoded.d.ts +17 -0
  38. package/lib/vcs/vcsHardcoded.d.ts.map +1 -0
  39. package/lib/vcs/vcsHardcoded.js +41 -0
  40. package/lib/vcs/vcsHardcoded.js.map +1 -0
  41. package/package.json +6 -6
  42. package/src/index.ts +5 -4
  43. package/src/lastUpdateUtils.ts +18 -76
  44. package/src/vcs/gitUtils.ts +524 -0
  45. package/src/vcs/vcs.ts +54 -0
  46. package/src/vcs/vcsDefaultV1.ts +33 -0
  47. package/src/vcs/vcsDefaultV2.ts +33 -0
  48. package/src/vcs/vcsDisabled.ts +25 -0
  49. package/src/vcs/vcsGitAdHoc.ts +30 -0
  50. package/src/vcs/vcsGitEager.ts +99 -0
  51. package/src/vcs/vcsHardcoded.ts +45 -0
  52. package/lib/gitUtils.d.ts.map +0 -1
  53. package/lib/gitUtils.js +0 -103
  54. package/lib/gitUtils.js.map +0 -1
  55. package/src/gitUtils.ts +0 -200
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import {resolve, basename} from 'node:path';
9
+ import logger, {PerfLogger} from '@docusaurus/logger';
10
+ import {getGitAllRepoRoots, getGitRepositoryFilesInfo} from './gitUtils';
11
+ import type {GitFileInfo, GitFileInfoMap} from './gitUtils';
12
+ import type {VcsConfig} from '@docusaurus/types';
13
+
14
+ // The Map keys should be absolute file paths, not relative Git paths
15
+ function resolveFileInfoMapPaths(
16
+ repoRoot: string,
17
+ filesInfo: GitFileInfoMap,
18
+ ): GitFileInfoMap {
19
+ function transformMapEntry(
20
+ entry: [string, GitFileInfo],
21
+ ): [string, GitFileInfo] {
22
+ // We just resolve the Git paths that are relative to the repo root
23
+ return [resolve(repoRoot, entry[0]), entry[1]];
24
+ }
25
+
26
+ return new Map(Array.from(filesInfo.entries()).map(transformMapEntry));
27
+ }
28
+
29
+ function mergeFileMaps(fileMaps: GitFileInfoMap[]): GitFileInfoMap {
30
+ return new Map(fileMaps.flatMap((m) => [...m]));
31
+ }
32
+
33
+ async function loadAllGitFilesInfoMap(cwd: string): Promise<GitFileInfoMap> {
34
+ const roots = await PerfLogger.async('Reading Git root dirs', () =>
35
+ getGitAllRepoRoots(cwd),
36
+ );
37
+
38
+ const allMaps: GitFileInfoMap[] = await Promise.all(
39
+ roots.map(async (root) => {
40
+ const map = await PerfLogger.async(
41
+ `Reading Git history for repo ${logger.path(basename(root))}`,
42
+ () => getGitRepositoryFilesInfo(root),
43
+ );
44
+ return resolveFileInfoMapPaths(root, map);
45
+ }),
46
+ );
47
+
48
+ return mergeFileMaps(allMaps);
49
+ }
50
+
51
+ function createGitVcsConfig(): VcsConfig {
52
+ let filesMapPromise: Promise<GitFileInfoMap> | null = null;
53
+
54
+ async function getGitFileInfo(filePath: string): Promise<GitFileInfo | null> {
55
+ const filesMap = await filesMapPromise;
56
+ return filesMap?.get(filePath) ?? null;
57
+ }
58
+
59
+ return {
60
+ initialize: ({siteDir}) => {
61
+ if (filesMapPromise) {
62
+ // We only initialize this VCS once!
63
+ // For i18n sites, this permits reading ahead of time for all locales
64
+ // so that it only slows down the first locale
65
+ // I assume this logic is fine, but we'll see if it causes trouble
66
+
67
+ // Note: we could also only call "initialize()" once from the outside,
68
+ // But maybe it could be useful for custom VCS implementations to be
69
+ // able to initialize once per locale?
70
+ PerfLogger.log(
71
+ 'Git Eager VCS strategy already initialized, skipping re-initialization',
72
+ );
73
+ return;
74
+ }
75
+
76
+ filesMapPromise = PerfLogger.async('Git Eager VCS init', () =>
77
+ loadAllGitFilesInfoMap(siteDir),
78
+ );
79
+ filesMapPromise.catch((error) => {
80
+ console.error(
81
+ 'Failed to initialize the Docusaurus Git Eager VCS strategy',
82
+ error,
83
+ );
84
+ });
85
+ },
86
+
87
+ getFileCreationInfo: async (filePath: string) => {
88
+ const fileInfo = await getGitFileInfo(filePath);
89
+ return fileInfo?.creation ?? null;
90
+ },
91
+
92
+ getFileLastUpdateInfo: async (filePath: string) => {
93
+ const fileInfo = await getGitFileInfo(filePath);
94
+ return fileInfo?.lastUpdate ?? null;
95
+ },
96
+ };
97
+ }
98
+
99
+ export const VscGitEager: VcsConfig = createGitVcsConfig();
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import type {VcsConfig, VcsChangeInfo} from '@docusaurus/types';
9
+
10
+ export const VCS_HARDCODED_CREATION_INFO: VcsChangeInfo = {
11
+ timestamp: 1490997600000, // 1st Apr 2017
12
+ author: 'Creator',
13
+ };
14
+
15
+ export const VCS_HARDCODED_LAST_UPDATE_INFO: VcsChangeInfo = {
16
+ timestamp: 1539502055000, // 14th Oct 2018
17
+ author: 'Author',
18
+ };
19
+
20
+ export const VCS_HARDCODED_UNTRACKED_FILE_PATH = `file/path/${Math.random()}.mdx`;
21
+
22
+ /**
23
+ * This VCS implementation always returns hardcoded values for testing purposes.
24
+ * It is also useful in dev environments where VCS info is not important.
25
+ * Reading information from the VCS can be slow and is not always necessary.
26
+ */
27
+ export const VcsHardcoded: VcsConfig = {
28
+ initialize: () => {
29
+ // Noop
30
+ },
31
+
32
+ getFileCreationInfo: async (filePath: string) => {
33
+ if (filePath === VCS_HARDCODED_UNTRACKED_FILE_PATH) {
34
+ return null;
35
+ }
36
+ return VCS_HARDCODED_CREATION_INFO;
37
+ },
38
+
39
+ getFileLastUpdateInfo: async (filePath: string) => {
40
+ if (filePath === VCS_HARDCODED_UNTRACKED_FILE_PATH) {
41
+ return null;
42
+ }
43
+ return VCS_HARDCODED_LAST_UPDATE_INFO;
44
+ },
45
+ };
@@ -1 +0,0 @@
1
- {"version":3,"file":"gitUtils.d.ts","sourceRoot":"","sources":["../src/gitUtils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6CH,2DAA2D;AAC3D,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C,uEAAuE;AACvE,qBAAa,mBAAoB,SAAQ,KAAK;CAAG;AAEjD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB;AACrC,iCAAiC;AACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IACJ;;;OAGG;IACH,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,uEAAuE;IACvE,aAAa,CAAC,EAAE,KAAK,CAAC;CACvB,GACA,OAAO,CAAC;IACT,4BAA4B;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC,CAAC;AACH;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB;AACrC,iCAAiC;AACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IACJ;;;OAGG;IACH,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,aAAa,EAAE,IAAI,CAAC;CACrB,GACA,OAAO,CAAC;IACT,4BAA4B;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC"}
package/lib/gitUtils.js DELETED
@@ -1,103 +0,0 @@
1
- "use strict";
2
- /**
3
- * Copyright (c) Facebook, Inc. and its affiliates.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.FileNotTrackedError = exports.GitNotFoundError = void 0;
10
- exports.getFileCommitDate = getFileCommitDate;
11
- const tslib_1 = require("tslib");
12
- const path_1 = tslib_1.__importDefault(require("path"));
13
- const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
14
- const os_1 = tslib_1.__importDefault(require("os"));
15
- const lodash_1 = tslib_1.__importDefault(require("lodash"));
16
- const execa_1 = tslib_1.__importDefault(require("execa"));
17
- const p_queue_1 = tslib_1.__importDefault(require("p-queue"));
18
- // Quite high/conservative concurrency value (it was previously "Infinity")
19
- // See https://github.com/facebook/docusaurus/pull/10915
20
- const DefaultGitCommandConcurrency =
21
- // TODO Docusaurus v4: bump node, availableParallelism() now always exists
22
- (typeof os_1.default.availableParallelism === 'function'
23
- ? os_1.default.availableParallelism()
24
- : os_1.default.cpus().length) * 4;
25
- const GitCommandConcurrencyEnv = process.env.DOCUSAURUS_GIT_COMMAND_CONCURRENCY
26
- ? parseInt(process.env.DOCUSAURUS_GIT_COMMAND_CONCURRENCY, 10)
27
- : undefined;
28
- const GitCommandConcurrency = GitCommandConcurrencyEnv && GitCommandConcurrencyEnv > 0
29
- ? GitCommandConcurrencyEnv
30
- : DefaultGitCommandConcurrency;
31
- // We use a queue to avoid running too many concurrent Git commands at once
32
- // See https://github.com/facebook/docusaurus/issues/10348
33
- const GitCommandQueue = new p_queue_1.default({
34
- concurrency: GitCommandConcurrency,
35
- });
36
- const realHasGitFn = () => {
37
- try {
38
- return execa_1.default.sync('git', ['--version']).exitCode === 0;
39
- }
40
- catch (error) {
41
- return false;
42
- }
43
- };
44
- // The hasGit call is synchronous IO so we memoize it
45
- // The user won't install Git in the middle of a build anyway...
46
- const hasGit = process.env.NODE_ENV === 'test' ? realHasGitFn : lodash_1.default.memoize(realHasGitFn);
47
- /** Custom error thrown when git is not found in `PATH`. */
48
- class GitNotFoundError extends Error {
49
- }
50
- exports.GitNotFoundError = GitNotFoundError;
51
- /** Custom error thrown when the current file is not tracked by git. */
52
- class FileNotTrackedError extends Error {
53
- }
54
- exports.FileNotTrackedError = FileNotTrackedError;
55
- async function getFileCommitDate(file, { age = 'oldest', includeAuthor = false, }) {
56
- if (!hasGit()) {
57
- throw new GitNotFoundError(`Failed to retrieve git history for "${file}" because git is not installed.`);
58
- }
59
- if (!(await fs_extra_1.default.pathExists(file))) {
60
- throw new Error(`Failed to retrieve git history for "${file}" because the file does not exist.`);
61
- }
62
- // We add a "RESULT:" prefix to make parsing easier
63
- // See why: https://github.com/facebook/docusaurus/pull/10022
64
- const resultFormat = includeAuthor ? 'RESULT:%ct,%an' : 'RESULT:%ct';
65
- const args = [
66
- `--format=${resultFormat}`,
67
- '--max-count=1',
68
- age === 'oldest' ? '--follow --diff-filter=A' : undefined,
69
- ]
70
- .filter(Boolean)
71
- .join(' ');
72
- const command = `git -c log.showSignature=false log ${args} -- "${path_1.default.basename(file)}"`;
73
- const result = (await GitCommandQueue.add(() => {
74
- return (0, execa_1.default)(command, {
75
- cwd: path_1.default.dirname(file),
76
- shell: true,
77
- });
78
- }));
79
- if (result.exitCode !== 0) {
80
- throw new Error(`Failed to retrieve the git history for file "${file}" with exit code ${result.exitCode}: ${result.stderr}`);
81
- }
82
- // We only parse the output line starting with our "RESULT:" prefix
83
- // See why https://github.com/facebook/docusaurus/pull/10022
84
- const regex = includeAuthor
85
- ? /(?:^|\n)RESULT:(?<timestamp>\d+),(?<author>.+)(?:$|\n)/
86
- : /(?:^|\n)RESULT:(?<timestamp>\d+)(?:$|\n)/;
87
- const output = result.stdout.trim();
88
- if (!output) {
89
- throw new FileNotTrackedError(`Failed to retrieve the git history for file "${file}" because the file is not tracked by git.`);
90
- }
91
- const match = output.match(regex);
92
- if (!match) {
93
- throw new Error(`Failed to retrieve the git history for file "${file}" with unexpected output: ${output}`);
94
- }
95
- const timestampInSeconds = Number(match.groups.timestamp);
96
- const timestamp = timestampInSeconds * 1000;
97
- const date = new Date(timestamp);
98
- if (includeAuthor) {
99
- return { date, timestamp, author: match.groups.author };
100
- }
101
- return { date, timestamp };
102
- }
103
- //# sourceMappingURL=gitUtils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gitUtils.js","sourceRoot":"","sources":["../src/gitUtils.ts"],"names":[],"mappings":"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"}
package/src/gitUtils.ts DELETED
@@ -1,200 +0,0 @@
1
- /**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
- import path from 'path';
9
- import fs from 'fs-extra';
10
- import os from 'os';
11
- import _ from 'lodash';
12
- import execa from 'execa';
13
- import PQueue from 'p-queue';
14
-
15
- // Quite high/conservative concurrency value (it was previously "Infinity")
16
- // See https://github.com/facebook/docusaurus/pull/10915
17
- const DefaultGitCommandConcurrency =
18
- // TODO Docusaurus v4: bump node, availableParallelism() now always exists
19
- (typeof os.availableParallelism === 'function'
20
- ? os.availableParallelism()
21
- : os.cpus().length) * 4;
22
-
23
- const GitCommandConcurrencyEnv = process.env.DOCUSAURUS_GIT_COMMAND_CONCURRENCY
24
- ? parseInt(process.env.DOCUSAURUS_GIT_COMMAND_CONCURRENCY, 10)
25
- : undefined;
26
-
27
- const GitCommandConcurrency =
28
- GitCommandConcurrencyEnv && GitCommandConcurrencyEnv > 0
29
- ? GitCommandConcurrencyEnv
30
- : DefaultGitCommandConcurrency;
31
-
32
- // We use a queue to avoid running too many concurrent Git commands at once
33
- // See https://github.com/facebook/docusaurus/issues/10348
34
- const GitCommandQueue = new PQueue({
35
- concurrency: GitCommandConcurrency,
36
- });
37
-
38
- const realHasGitFn = () => {
39
- try {
40
- return execa.sync('git', ['--version']).exitCode === 0;
41
- } catch (error) {
42
- return false;
43
- }
44
- };
45
-
46
- // The hasGit call is synchronous IO so we memoize it
47
- // The user won't install Git in the middle of a build anyway...
48
- const hasGit =
49
- process.env.NODE_ENV === 'test' ? realHasGitFn : _.memoize(realHasGitFn);
50
-
51
- /** Custom error thrown when git is not found in `PATH`. */
52
- export class GitNotFoundError extends Error {}
53
-
54
- /** Custom error thrown when the current file is not tracked by git. */
55
- export class FileNotTrackedError extends Error {}
56
-
57
- /**
58
- * Fetches the git history of a file and returns a relevant commit date.
59
- * It gets the commit date instead of author date so that amended commits
60
- * can have their dates updated.
61
- *
62
- * @throws {@link GitNotFoundError} If git is not found in `PATH`.
63
- * @throws {@link FileNotTrackedError} If the current file is not tracked by git.
64
- * @throws Also throws when `git log` exited with non-zero, or when it outputs
65
- * unexpected text.
66
- */
67
- export async function getFileCommitDate(
68
- /** Absolute path to the file. */
69
- file: string,
70
- args: {
71
- /**
72
- * `"oldest"` is the commit that added the file, following renames;
73
- * `"newest"` is the last commit that edited the file.
74
- */
75
- age?: 'oldest' | 'newest';
76
- /** Use `includeAuthor: true` to get the author information as well. */
77
- includeAuthor?: false;
78
- },
79
- ): Promise<{
80
- /** Relevant commit date. */
81
- date: Date;
82
- /** Timestamp returned from git, converted to **milliseconds**. */
83
- timestamp: number;
84
- }>;
85
- /**
86
- * Fetches the git history of a file and returns a relevant commit date.
87
- * It gets the commit date instead of author date so that amended commits
88
- * can have their dates updated.
89
- *
90
- * @throws {@link GitNotFoundError} If git is not found in `PATH`.
91
- * @throws {@link FileNotTrackedError} If the current file is not tracked by git.
92
- * @throws Also throws when `git log` exited with non-zero, or when it outputs
93
- * unexpected text.
94
- */
95
- export async function getFileCommitDate(
96
- /** Absolute path to the file. */
97
- file: string,
98
- args: {
99
- /**
100
- * `"oldest"` is the commit that added the file, following renames;
101
- * `"newest"` is the last commit that edited the file.
102
- */
103
- age?: 'oldest' | 'newest';
104
- includeAuthor: true;
105
- },
106
- ): Promise<{
107
- /** Relevant commit date. */
108
- date: Date;
109
- /** Timestamp returned from git, converted to **milliseconds**. */
110
- timestamp: number;
111
- /** The author's name, as returned from git. */
112
- author: string;
113
- }>;
114
-
115
- export async function getFileCommitDate(
116
- file: string,
117
- {
118
- age = 'oldest',
119
- includeAuthor = false,
120
- }: {
121
- age?: 'oldest' | 'newest';
122
- includeAuthor?: boolean;
123
- },
124
- ): Promise<{
125
- date: Date;
126
- timestamp: number;
127
- author?: string;
128
- }> {
129
- if (!hasGit()) {
130
- throw new GitNotFoundError(
131
- `Failed to retrieve git history for "${file}" because git is not installed.`,
132
- );
133
- }
134
-
135
- if (!(await fs.pathExists(file))) {
136
- throw new Error(
137
- `Failed to retrieve git history for "${file}" because the file does not exist.`,
138
- );
139
- }
140
-
141
- // We add a "RESULT:" prefix to make parsing easier
142
- // See why: https://github.com/facebook/docusaurus/pull/10022
143
- const resultFormat = includeAuthor ? 'RESULT:%ct,%an' : 'RESULT:%ct';
144
-
145
- const args = [
146
- `--format=${resultFormat}`,
147
- '--max-count=1',
148
- age === 'oldest' ? '--follow --diff-filter=A' : undefined,
149
- ]
150
- .filter(Boolean)
151
- .join(' ');
152
-
153
- const command = `git -c log.showSignature=false log ${args} -- "${path.basename(
154
- file,
155
- )}"`;
156
-
157
- const result = (await GitCommandQueue.add(() => {
158
- return execa(command, {
159
- cwd: path.dirname(file),
160
- shell: true,
161
- });
162
- }))!;
163
-
164
- if (result.exitCode !== 0) {
165
- throw new Error(
166
- `Failed to retrieve the git history for file "${file}" with exit code ${result.exitCode}: ${result.stderr}`,
167
- );
168
- }
169
-
170
- // We only parse the output line starting with our "RESULT:" prefix
171
- // See why https://github.com/facebook/docusaurus/pull/10022
172
- const regex = includeAuthor
173
- ? /(?:^|\n)RESULT:(?<timestamp>\d+),(?<author>.+)(?:$|\n)/
174
- : /(?:^|\n)RESULT:(?<timestamp>\d+)(?:$|\n)/;
175
-
176
- const output = result.stdout.trim();
177
-
178
- if (!output) {
179
- throw new FileNotTrackedError(
180
- `Failed to retrieve the git history for file "${file}" because the file is not tracked by git.`,
181
- );
182
- }
183
-
184
- const match = output.match(regex);
185
-
186
- if (!match) {
187
- throw new Error(
188
- `Failed to retrieve the git history for file "${file}" with unexpected output: ${output}`,
189
- );
190
- }
191
-
192
- const timestampInSeconds = Number(match.groups!.timestamp);
193
- const timestamp = timestampInSeconds * 1_000;
194
- const date = new Date(timestamp);
195
-
196
- if (includeAuthor) {
197
- return {date, timestamp, author: match.groups!.author!};
198
- }
199
- return {date, timestamp};
200
- }