@docusaurus/utils 2.0.0-beta.16 → 2.0.0-beta.19
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/lib/constants.d.ts +51 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +57 -10
- package/lib/constants.js.map +1 -1
- package/lib/dataFileUtils.d.ts +38 -2
- package/lib/dataFileUtils.d.ts.map +1 -1
- package/lib/dataFileUtils.js +39 -13
- package/lib/dataFileUtils.js.map +1 -1
- package/lib/emitUtils.d.ts +32 -0
- package/lib/emitUtils.d.ts.map +1 -0
- package/lib/emitUtils.js +80 -0
- package/lib/emitUtils.js.map +1 -0
- package/lib/gitUtils.d.ts +54 -5
- package/lib/gitUtils.d.ts.map +1 -1
- package/lib/gitUtils.js +17 -14
- package/lib/gitUtils.js.map +1 -1
- package/lib/globUtils.d.ts +28 -0
- package/lib/globUtils.d.ts.map +1 -1
- package/lib/globUtils.js +36 -13
- package/lib/globUtils.js.map +1 -1
- package/lib/hashUtils.d.ts +5 -4
- package/lib/hashUtils.d.ts.map +1 -1
- package/lib/hashUtils.js +7 -6
- package/lib/hashUtils.js.map +1 -1
- package/lib/i18nUtils.d.ts +51 -0
- package/lib/i18nUtils.d.ts.map +1 -0
- package/lib/i18nUtils.js +69 -0
- package/lib/i18nUtils.js.map +1 -0
- package/lib/index.d.ts +10 -54
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +56 -256
- package/lib/index.js.map +1 -1
- package/lib/jsUtils.d.ts +45 -0
- package/lib/jsUtils.d.ts.map +1 -0
- package/lib/jsUtils.js +94 -0
- package/lib/jsUtils.js.map +1 -0
- package/lib/markdownLinks.d.ts +48 -5
- package/lib/markdownLinks.d.ts.map +1 -1
- package/lib/markdownLinks.js +29 -13
- package/lib/markdownLinks.js.map +1 -1
- package/lib/markdownUtils.d.ts +112 -0
- package/lib/markdownUtils.d.ts.map +1 -0
- package/lib/markdownUtils.js +271 -0
- package/lib/markdownUtils.js.map +1 -0
- package/lib/pathUtils.d.ts +2 -1
- package/lib/pathUtils.d.ts.map +1 -1
- package/lib/pathUtils.js +16 -7
- package/lib/pathUtils.js.map +1 -1
- package/lib/slugger.d.ts +10 -0
- package/lib/slugger.d.ts.map +1 -1
- package/lib/slugger.js +6 -2
- package/lib/slugger.js.map +1 -1
- package/lib/tags.d.ts +42 -10
- package/lib/tags.d.ts.map +1 -1
- package/lib/tags.js +40 -26
- package/lib/tags.js.map +1 -1
- package/lib/urlUtils.d.ts +57 -0
- package/lib/urlUtils.d.ts.map +1 -1
- package/lib/urlUtils.js +132 -6
- package/lib/urlUtils.js.map +1 -1
- package/lib/webpackUtils.d.ts +5 -0
- package/lib/webpackUtils.d.ts.map +1 -1
- package/lib/webpackUtils.js +8 -5
- package/lib/webpackUtils.js.map +1 -1
- package/package.json +11 -11
- package/src/constants.ts +65 -9
- package/src/dataFileUtils.ts +44 -12
- package/src/emitUtils.ts +99 -0
- package/src/gitUtils.ts +77 -17
- package/src/globUtils.ts +34 -13
- package/src/hashUtils.ts +6 -5
- package/src/i18nUtils.ts +115 -0
- package/src/index.ts +43 -307
- package/src/jsUtils.ts +102 -0
- package/src/markdownLinks.ts +71 -28
- package/src/markdownUtils.ts +354 -0
- package/src/pathUtils.ts +15 -7
- package/src/slugger.ts +13 -1
- package/src/tags.ts +53 -28
- package/src/urlUtils.ts +145 -7
- package/src/webpackUtils.ts +11 -4
- package/lib/markdownParser.d.ts +0 -32
- package/lib/markdownParser.d.ts.map +0 -1
- package/lib/markdownParser.js +0 -161
- package/lib/markdownParser.js.map +0 -1
- package/src/markdownParser.ts +0 -201
package/src/constants.ts
CHANGED
|
@@ -5,34 +5,90 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/** Node major version, directly read from env. */
|
|
8
9
|
export const NODE_MAJOR_VERSION = parseInt(
|
|
9
|
-
process.versions.node.split('.')[0]
|
|
10
|
+
process.versions.node.split('.')[0]!,
|
|
10
11
|
10,
|
|
11
12
|
);
|
|
13
|
+
/** Node minor version, directly read from env. */
|
|
12
14
|
export const NODE_MINOR_VERSION = parseInt(
|
|
13
|
-
process.versions.node.split('.')[1]
|
|
15
|
+
process.versions.node.split('.')[1]!,
|
|
14
16
|
10,
|
|
15
17
|
);
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
/** Docusaurus core version. */
|
|
20
|
+
// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
|
|
21
|
+
export const DOCUSAURUS_VERSION = require('../package.json').version;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Can be overridden with cli option `--out-dir`. Code should generally use
|
|
25
|
+
* `context.outDir` instead (which is always absolute and localized).
|
|
26
|
+
*/
|
|
18
27
|
export const DEFAULT_BUILD_DIR_NAME = 'build';
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Can be overridden with cli option `--config`. Code should generally use
|
|
31
|
+
* `context.siteConfigPath` instead (which is always absolute).
|
|
32
|
+
*/
|
|
21
33
|
export const DEFAULT_CONFIG_FILE_NAME = 'docusaurus.config.js';
|
|
22
34
|
|
|
35
|
+
/** Can be absolute or relative to site directory. */
|
|
23
36
|
export const BABEL_CONFIG_FILE_NAME =
|
|
24
|
-
process.env.DOCUSAURUS_BABEL_CONFIG_FILE_NAME
|
|
37
|
+
process.env.DOCUSAURUS_BABEL_CONFIG_FILE_NAME ?? 'babel.config.js';
|
|
25
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Can be absolute or relative to site directory. Code should generally use
|
|
41
|
+
* `context.generatedFilesDir` instead (which is always absolute).
|
|
42
|
+
*/
|
|
26
43
|
export const GENERATED_FILES_DIR_NAME =
|
|
27
|
-
process.env.DOCUSAURUS_GENERATED_FILES_DIR_NAME
|
|
44
|
+
process.env.DOCUSAURUS_GENERATED_FILES_DIR_NAME ?? '.docusaurus';
|
|
28
45
|
|
|
46
|
+
/**
|
|
47
|
+
* We would assume all of the site's JS code lives in here and not outside.
|
|
48
|
+
* Relative to the site directory.
|
|
49
|
+
*/
|
|
29
50
|
export const SRC_DIR_NAME = 'src';
|
|
30
|
-
|
|
31
|
-
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Can be overridden with `config.staticDirectories`. Code should use
|
|
54
|
+
* `context.siteConfig.staticDirectories` instead (which is always absolute).
|
|
55
|
+
*/
|
|
56
|
+
export const DEFAULT_STATIC_DIR_NAME = 'static';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Files here are handled by webpack, hashed (can be cached aggressively).
|
|
60
|
+
* Relative to the build output folder.
|
|
61
|
+
*/
|
|
62
|
+
export const OUTPUT_STATIC_ASSETS_DIR_NAME = 'assets';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Components in this directory will receive the `@theme` alias and be able to
|
|
66
|
+
* shadow default theme components.
|
|
67
|
+
*/
|
|
32
68
|
export const THEME_PATH = `${SRC_DIR_NAME}/theme`;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* All translation-related data live here, relative to site directory. Content
|
|
72
|
+
* will be namespaced by locale.
|
|
73
|
+
*/
|
|
74
|
+
export const I18N_DIR_NAME = 'i18n';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Translations for React code.
|
|
78
|
+
*/
|
|
79
|
+
export const CODE_TRANSLATIONS_FILE_NAME = 'code.json';
|
|
80
|
+
|
|
81
|
+
/** Dev server opens on this port by default. */
|
|
33
82
|
export const DEFAULT_PORT = 3000;
|
|
83
|
+
|
|
84
|
+
/** Default plugin ID. */
|
|
34
85
|
export const DEFAULT_PLUGIN_ID = 'default';
|
|
35
86
|
|
|
36
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Allow overriding the limit after which the url loader will no longer inline
|
|
89
|
+
* assets.
|
|
90
|
+
*
|
|
91
|
+
* @see https://github.com/facebook/docusaurus/issues/5493
|
|
92
|
+
*/
|
|
37
93
|
export const WEBPACK_URL_LOADER_LIMIT =
|
|
38
94
|
process.env.WEBPACK_URL_LOADER_LIMIT ?? 10000;
|
package/src/dataFileUtils.ts
CHANGED
|
@@ -13,31 +13,47 @@ import type {ContentPaths} from './markdownLinks';
|
|
|
13
13
|
import logger from '@docusaurus/logger';
|
|
14
14
|
|
|
15
15
|
type DataFileParams = {
|
|
16
|
+
/** Path to the potential data file, relative to `contentPaths` */
|
|
16
17
|
filePath: string;
|
|
18
|
+
/**
|
|
19
|
+
* Includes the base path and localized path, both of which are eligible for
|
|
20
|
+
* sourcing data files. Both paths should be absolute.
|
|
21
|
+
*/
|
|
17
22
|
contentPaths: ContentPaths;
|
|
18
23
|
};
|
|
19
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Looks for a data file in the potential content paths; loads a localized data
|
|
27
|
+
* file in priority.
|
|
28
|
+
*
|
|
29
|
+
* @returns An absolute path to the data file, or `undefined` if there isn't one.
|
|
30
|
+
*/
|
|
20
31
|
export async function getDataFilePath({
|
|
21
32
|
filePath,
|
|
22
33
|
contentPaths,
|
|
23
34
|
}: DataFileParams): Promise<string | undefined> {
|
|
24
|
-
// Loads a localized data file in priority
|
|
25
35
|
const contentPath = await findFolderContainingFile(
|
|
26
36
|
getContentPathList(contentPaths),
|
|
27
37
|
filePath,
|
|
28
38
|
);
|
|
29
39
|
if (contentPath) {
|
|
30
|
-
return path.
|
|
40
|
+
return path.resolve(contentPath, filePath);
|
|
31
41
|
}
|
|
32
42
|
return undefined;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
/**
|
|
36
|
-
* Looks up for a data file in the content paths, returns the
|
|
37
|
-
*
|
|
46
|
+
* Looks up for a data file in the content paths, returns the object validated
|
|
47
|
+
* and normalized according to the `validate` callback.
|
|
48
|
+
*
|
|
49
|
+
* @returns `undefined` when file not found
|
|
50
|
+
* @throws Throws when validation fails, displaying a helpful context message.
|
|
38
51
|
*/
|
|
39
52
|
export async function getDataFileData<T>(
|
|
40
|
-
params: DataFileParams & {
|
|
53
|
+
params: DataFileParams & {
|
|
54
|
+
/** Used for the "The X file looks invalid" message. */
|
|
55
|
+
fileType: string;
|
|
56
|
+
},
|
|
41
57
|
validate: (content: unknown) => T,
|
|
42
58
|
): Promise<T | undefined> {
|
|
43
59
|
const filePath = await getDataFilePath(params);
|
|
@@ -49,18 +65,26 @@ export async function getDataFileData<T>(
|
|
|
49
65
|
const unsafeContent = Yaml.load(contentString);
|
|
50
66
|
return validate(unsafeContent);
|
|
51
67
|
} catch (err) {
|
|
52
|
-
// TODO replace later by error cause, see https://v8.dev/features/error-cause
|
|
53
68
|
logger.error`The ${params.fileType} file at path=${filePath} looks invalid.`;
|
|
54
69
|
throw err;
|
|
55
70
|
}
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Takes the `contentPaths` data structure and returns an ordered path list
|
|
75
|
+
* indicating their priorities. For all data, we look in the localized folder
|
|
76
|
+
* in priority.
|
|
77
|
+
*/
|
|
59
78
|
export function getContentPathList(contentPaths: ContentPaths): string[] {
|
|
60
79
|
return [contentPaths.contentPathLocalized, contentPaths.contentPath];
|
|
61
80
|
}
|
|
62
81
|
|
|
63
|
-
|
|
82
|
+
/**
|
|
83
|
+
* @param folderPaths a list of absolute paths.
|
|
84
|
+
* @param relativeFilePath file path relative to each `folderPaths`.
|
|
85
|
+
* @returns the first folder path in which the file exists, or `undefined` if
|
|
86
|
+
* none is found.
|
|
87
|
+
*/
|
|
64
88
|
export async function findFolderContainingFile(
|
|
65
89
|
folderPaths: string[],
|
|
66
90
|
relativeFilePath: string,
|
|
@@ -70,6 +94,16 @@ export async function findFolderContainingFile(
|
|
|
70
94
|
);
|
|
71
95
|
}
|
|
72
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Fail-fast alternative to `findFolderContainingFile`.
|
|
99
|
+
*
|
|
100
|
+
* @param folderPaths a list of absolute paths.
|
|
101
|
+
* @param relativeFilePath file path relative to each `folderPaths`.
|
|
102
|
+
* @returns the first folder path in which the file exists.
|
|
103
|
+
* @throws Throws if no file can be found. You should use this method only when
|
|
104
|
+
* you actually know the file exists (e.g. when the `relativeFilePath` is read
|
|
105
|
+
* with a glob and you are just trying to localize it)
|
|
106
|
+
*/
|
|
73
107
|
export async function getFolderContainingFile(
|
|
74
108
|
folderPaths: string[],
|
|
75
109
|
relativeFilePath: string,
|
|
@@ -78,12 +112,10 @@ export async function getFolderContainingFile(
|
|
|
78
112
|
folderPaths,
|
|
79
113
|
relativeFilePath,
|
|
80
114
|
);
|
|
81
|
-
// should never happen, as the source was read from the FS anyway...
|
|
82
115
|
if (!maybeFolderPath) {
|
|
83
116
|
throw new Error(
|
|
84
|
-
`File "${relativeFilePath}" does not exist in any of these folders
|
|
85
|
-
|
|
86
|
-
)}]`,
|
|
117
|
+
`File "${relativeFilePath}" does not exist in any of these folders:
|
|
118
|
+
- ${folderPaths.join('\n- ')}`,
|
|
87
119
|
);
|
|
88
120
|
}
|
|
89
121
|
return maybeFolderPath;
|
package/src/emitUtils.ts
ADDED
|
@@ -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 path from 'path';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import {createHash} from 'crypto';
|
|
11
|
+
import {findAsyncSequential} from './jsUtils';
|
|
12
|
+
|
|
13
|
+
const fileHash = new Map<string, string>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Outputs a file to the generated files directory. Only writes files if content
|
|
17
|
+
* differs from cache (for hot reload performance).
|
|
18
|
+
*
|
|
19
|
+
* @param generatedFilesDir Absolute path.
|
|
20
|
+
* @param file Path relative to `generatedFilesDir`. File will always be
|
|
21
|
+
* outputted; no need to ensure directory exists.
|
|
22
|
+
* @param content String content to write.
|
|
23
|
+
* @param skipCache If `true` (defaults as `true` for production), file is
|
|
24
|
+
* force-rewritten, skipping cache.
|
|
25
|
+
*/
|
|
26
|
+
export async function generate(
|
|
27
|
+
generatedFilesDir: string,
|
|
28
|
+
file: string,
|
|
29
|
+
content: string,
|
|
30
|
+
skipCache: boolean = process.env.NODE_ENV === 'production',
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
const filepath = path.resolve(generatedFilesDir, file);
|
|
33
|
+
|
|
34
|
+
if (skipCache) {
|
|
35
|
+
await fs.outputFile(filepath, content);
|
|
36
|
+
// Cache still needs to be reset, otherwise, writing "A", "B", and "A" where
|
|
37
|
+
// "B" skips cache will cause the last "A" not be able to overwrite as the
|
|
38
|
+
// first "A" remains in cache. But if the file never existed in cache, no
|
|
39
|
+
// need to register it.
|
|
40
|
+
if (fileHash.get(filepath)) {
|
|
41
|
+
fileHash.set(filepath, createHash('md5').update(content).digest('hex'));
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let lastHash = fileHash.get(filepath);
|
|
47
|
+
|
|
48
|
+
// If file already exists but it's not in runtime cache yet, we try to
|
|
49
|
+
// calculate the content hash and then compare. This is to avoid unnecessary
|
|
50
|
+
// overwriting and we can reuse old file.
|
|
51
|
+
if (!lastHash && (await fs.pathExists(filepath))) {
|
|
52
|
+
const lastContent = await fs.readFile(filepath, 'utf8');
|
|
53
|
+
lastHash = createHash('md5').update(lastContent).digest('hex');
|
|
54
|
+
fileHash.set(filepath, lastHash);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const currentHash = createHash('md5').update(content).digest('hex');
|
|
58
|
+
|
|
59
|
+
if (lastHash !== currentHash) {
|
|
60
|
+
await fs.outputFile(filepath, content);
|
|
61
|
+
fileHash.set(filepath, currentHash);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param permalink The URL that the HTML file corresponds to, without base URL
|
|
67
|
+
* @param outDir Full path to the output directory
|
|
68
|
+
* @param trailingSlash The site config option. If provided, only one path will
|
|
69
|
+
* be read.
|
|
70
|
+
* @returns This returns a buffer, which you have to decode string yourself if
|
|
71
|
+
* needed. (Not always necessary since the output isn't for human consumption
|
|
72
|
+
* anyways, and most HTML manipulation libs accept buffers)
|
|
73
|
+
* @throws Throws when the HTML file is not found at any of the potential paths.
|
|
74
|
+
* This should never happen as it would lead to a 404.
|
|
75
|
+
*/
|
|
76
|
+
export async function readOutputHTMLFile(
|
|
77
|
+
permalink: string,
|
|
78
|
+
outDir: string,
|
|
79
|
+
trailingSlash: boolean | undefined,
|
|
80
|
+
): Promise<Buffer> {
|
|
81
|
+
const withTrailingSlashPath = path.join(outDir, permalink, 'index.html');
|
|
82
|
+
const withoutTrailingSlashPath = path.join(
|
|
83
|
+
outDir,
|
|
84
|
+
`${permalink.replace(/\/$/, '')}.html`,
|
|
85
|
+
);
|
|
86
|
+
const HTMLPath = await findAsyncSequential(
|
|
87
|
+
[
|
|
88
|
+
trailingSlash !== false && withTrailingSlashPath,
|
|
89
|
+
trailingSlash !== true && withoutTrailingSlashPath,
|
|
90
|
+
].filter((p): p is string => Boolean(p)),
|
|
91
|
+
fs.pathExists,
|
|
92
|
+
);
|
|
93
|
+
if (!HTMLPath) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Expected output HTML file to be found at ${withTrailingSlashPath}.`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return fs.readFile(HTMLPath);
|
|
99
|
+
}
|
package/src/gitUtils.ts
CHANGED
|
@@ -8,9 +8,70 @@
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import shell from 'shelljs';
|
|
10
10
|
|
|
11
|
+
/** Custom error thrown when git is not found in `PATH`. */
|
|
11
12
|
export class GitNotFoundError extends Error {}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
/** Custom error thrown when the current file is not tracked by git. */
|
|
15
|
+
export class FileNotTrackedError extends Error {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fetches the git history of a file and returns a relevant commit date.
|
|
19
|
+
* It gets the commit date instead of author date so that amended commits
|
|
20
|
+
* can have their dates updated.
|
|
21
|
+
*
|
|
22
|
+
* @throws {@link GitNotFoundError} If git is not found in `PATH`.
|
|
23
|
+
* @throws {@link FileNotTrackedError} If the current file is not tracked by git.
|
|
24
|
+
* @throws Also throws when `git log` exited with non-zero, or when it outputs
|
|
25
|
+
* unexpected text.
|
|
26
|
+
*/
|
|
27
|
+
export function getFileCommitDate(
|
|
28
|
+
/** Absolute path to the file. */
|
|
29
|
+
file: string,
|
|
30
|
+
args: {
|
|
31
|
+
/**
|
|
32
|
+
* `"oldest"` is the commit that added the file, following renames;
|
|
33
|
+
* `"newest"` is the last commit that edited the file.
|
|
34
|
+
*/
|
|
35
|
+
age?: 'oldest' | 'newest';
|
|
36
|
+
/** Use `includeAuthor: true` to get the author information as well. */
|
|
37
|
+
includeAuthor?: false;
|
|
38
|
+
},
|
|
39
|
+
): {
|
|
40
|
+
/** Relevant commit date. */
|
|
41
|
+
date: Date;
|
|
42
|
+
/** Timestamp in **seconds**, as returned from git. */
|
|
43
|
+
timestamp: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Fetches the git history of a file and returns a relevant commit date.
|
|
47
|
+
* It gets the commit date instead of author date so that amended commits
|
|
48
|
+
* can have their dates updated.
|
|
49
|
+
*
|
|
50
|
+
* @throws {@link GitNotFoundError} If git is not found in `PATH`.
|
|
51
|
+
* @throws {@link FileNotTrackedError} If the current file is not tracked by git.
|
|
52
|
+
* @throws Also throws when `git log` exited with non-zero, or when it outputs
|
|
53
|
+
* unexpected text.
|
|
54
|
+
*/
|
|
55
|
+
export function getFileCommitDate(
|
|
56
|
+
/** Absolute path to the file. */
|
|
57
|
+
file: string,
|
|
58
|
+
args: {
|
|
59
|
+
/**
|
|
60
|
+
* `"oldest"` is the commit that added the file, following renames;
|
|
61
|
+
* `"newest"` is the last commit that edited the file.
|
|
62
|
+
*/
|
|
63
|
+
age?: 'oldest' | 'newest';
|
|
64
|
+
includeAuthor: true;
|
|
65
|
+
},
|
|
66
|
+
): {
|
|
67
|
+
/** Relevant commit date. */
|
|
68
|
+
date: Date;
|
|
69
|
+
/** Timestamp in **seconds**, as returned from git. */
|
|
70
|
+
timestamp: number;
|
|
71
|
+
/** The author's name, as returned from git. */
|
|
72
|
+
author: string;
|
|
73
|
+
};
|
|
74
|
+
export function getFileCommitDate(
|
|
14
75
|
file: string,
|
|
15
76
|
{
|
|
16
77
|
age = 'oldest',
|
|
@@ -23,7 +84,7 @@ export const getFileCommitDate = (
|
|
|
23
84
|
date: Date;
|
|
24
85
|
timestamp: number;
|
|
25
86
|
author?: string;
|
|
26
|
-
}
|
|
87
|
+
} {
|
|
27
88
|
if (!shell.which('git')) {
|
|
28
89
|
throw new GitNotFoundError(
|
|
29
90
|
`Failed to retrieve git history for "${file}" because git is not installed.`,
|
|
@@ -36,9 +97,6 @@ export const getFileCommitDate = (
|
|
|
36
97
|
);
|
|
37
98
|
}
|
|
38
99
|
|
|
39
|
-
const fileBasename = path.basename(file);
|
|
40
|
-
const fileDirname = path.dirname(file);
|
|
41
|
-
|
|
42
100
|
let formatArg = '--format=%ct';
|
|
43
101
|
if (includeAuthor) {
|
|
44
102
|
formatArg += ',%an';
|
|
@@ -52,10 +110,10 @@ export const getFileCommitDate = (
|
|
|
52
110
|
}
|
|
53
111
|
|
|
54
112
|
const result = shell.exec(
|
|
55
|
-
`git log ${extraArgs} ${formatArg} -- "${
|
|
113
|
+
`git log ${extraArgs} ${formatArg} -- "${path.basename(file)}"`,
|
|
56
114
|
{
|
|
57
|
-
// cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
|
|
58
|
-
cwd:
|
|
115
|
+
// Setting cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
|
|
116
|
+
cwd: path.dirname(file),
|
|
59
117
|
silent: true,
|
|
60
118
|
},
|
|
61
119
|
);
|
|
@@ -70,24 +128,26 @@ export const getFileCommitDate = (
|
|
|
70
128
|
}
|
|
71
129
|
|
|
72
130
|
const output = result.stdout.trim();
|
|
131
|
+
|
|
132
|
+
if (!output) {
|
|
133
|
+
throw new FileNotTrackedError(
|
|
134
|
+
`Failed to retrieve the git history for file "${file}" because the file is not tracked by git.`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
73
138
|
const match = output.match(regex);
|
|
74
139
|
|
|
75
|
-
if (
|
|
76
|
-
!match ||
|
|
77
|
-
!match.groups ||
|
|
78
|
-
!match.groups.timestamp ||
|
|
79
|
-
(includeAuthor && !match.groups.author)
|
|
80
|
-
) {
|
|
140
|
+
if (!match) {
|
|
81
141
|
throw new Error(
|
|
82
142
|
`Failed to retrieve the git history for file "${file}" with unexpected output: ${output}`,
|
|
83
143
|
);
|
|
84
144
|
}
|
|
85
145
|
|
|
86
|
-
const timestamp = Number(match.groups
|
|
146
|
+
const timestamp = Number(match.groups!.timestamp);
|
|
87
147
|
const date = new Date(timestamp * 1000);
|
|
88
148
|
|
|
89
149
|
if (includeAuthor) {
|
|
90
|
-
return {date, timestamp, author: match.groups
|
|
150
|
+
return {date, timestamp, author: match.groups!.author!};
|
|
91
151
|
}
|
|
92
152
|
return {date, timestamp};
|
|
93
|
-
}
|
|
153
|
+
}
|
package/src/globUtils.ts
CHANGED
|
@@ -10,35 +10,56 @@
|
|
|
10
10
|
import Micromatch from 'micromatch'; // Note: Micromatch is used by Globby
|
|
11
11
|
import path from 'path';
|
|
12
12
|
|
|
13
|
+
/** A re-export of the globby instance. */
|
|
13
14
|
export {default as Globby} from 'globby';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The default glob patterns we ignore when sourcing content.
|
|
18
|
+
* - Ignore files and folders starting with `_` recursively
|
|
19
|
+
* - Ignore tests
|
|
20
|
+
*/
|
|
17
21
|
export const GlobExcludeDefault = [
|
|
18
|
-
// Ignore files starting with _
|
|
19
22
|
'**/_*.{js,jsx,ts,tsx,md,mdx}',
|
|
20
|
-
|
|
21
|
-
// Ignore folders starting with _ (including folder content)
|
|
22
23
|
'**/_*/**',
|
|
23
|
-
|
|
24
|
-
// Ignore tests
|
|
25
24
|
'**/*.test.{js,jsx,ts,tsx}',
|
|
26
25
|
'**/__tests__/**',
|
|
27
26
|
];
|
|
28
27
|
|
|
29
28
|
type Matcher = (str: string) => boolean;
|
|
30
29
|
|
|
30
|
+
/**
|
|
31
|
+
* A very thin wrapper around `Micromatch.makeRe`.
|
|
32
|
+
*
|
|
33
|
+
* @see {@link createAbsoluteFilePathMatcher}
|
|
34
|
+
* @param patterns A list of glob patterns. If the list is empty, it defaults to
|
|
35
|
+
* matching none.
|
|
36
|
+
* @returns A matcher handle that tells if a file path is matched by any of the
|
|
37
|
+
* patterns.
|
|
38
|
+
*/
|
|
31
39
|
export function createMatcher(patterns: string[]): Matcher {
|
|
40
|
+
if (patterns.length === 0) {
|
|
41
|
+
// `/(?:)/.test("foo")` is `true`
|
|
42
|
+
return () => false;
|
|
43
|
+
}
|
|
32
44
|
const regexp = new RegExp(
|
|
33
45
|
patterns.map((pattern) => Micromatch.makeRe(pattern).source).join('|'),
|
|
34
46
|
);
|
|
35
47
|
return (str) => regexp.test(str);
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
/**
|
|
51
|
+
* We use match patterns like `"** /_* /**"` (ignore the spaces), where `"_*"`
|
|
52
|
+
* should only be matched within a subfolder. This function would:
|
|
53
|
+
* - Match `/user/sebastien/website/docs/_partials/xyz.md`
|
|
54
|
+
* - Ignore `/user/_sebastien/website/docs/partials/xyz.md`
|
|
55
|
+
*
|
|
56
|
+
* @param patterns A list of glob patterns.
|
|
57
|
+
* @param rootFolders A list of root folders to resolve the glob from.
|
|
58
|
+
* @returns A matcher handle that tells if a file path is matched by any of the
|
|
59
|
+
* patterns, resolved from the first root folder that contains the path.
|
|
60
|
+
* @throws Throws when the returned matcher receives a path that doesn't belong
|
|
61
|
+
* to any of the `rootFolders`.
|
|
62
|
+
*/
|
|
42
63
|
export function createAbsoluteFilePathMatcher(
|
|
43
64
|
patterns: string[],
|
|
44
65
|
rootFolders: string[],
|
|
@@ -51,8 +72,8 @@ export function createAbsoluteFilePathMatcher(
|
|
|
51
72
|
);
|
|
52
73
|
if (!rootFolder) {
|
|
53
74
|
throw new Error(
|
|
54
|
-
`createAbsoluteFilePathMatcher unexpected error, absoluteFilePath=${absoluteFilePath} was not contained in any of the root folders ${
|
|
55
|
-
|
|
75
|
+
`createAbsoluteFilePathMatcher unexpected error, absoluteFilePath=${absoluteFilePath} was not contained in any of the root folders: ${rootFolders.join(
|
|
76
|
+
', ',
|
|
56
77
|
)}`,
|
|
57
78
|
);
|
|
58
79
|
}
|
package/src/hashUtils.ts
CHANGED
|
@@ -9,20 +9,21 @@ import {createHash} from 'crypto';
|
|
|
9
9
|
import _ from 'lodash';
|
|
10
10
|
import {shortName, isNameTooLong} from './pathUtils';
|
|
11
11
|
|
|
12
|
+
/** Thin wrapper around `crypto.createHash("md5")`. */
|
|
12
13
|
export function md5Hash(str: string): string {
|
|
13
14
|
return createHash('md5').update(str).digest('hex');
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/** Creates an MD5 hash and truncates it to the given length. */
|
|
16
18
|
export function simpleHash(str: string, length: number): string {
|
|
17
|
-
return md5Hash(str).
|
|
19
|
+
return md5Hash(str).substring(0, length);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
// Based on https://github.com/gatsbyjs/gatsby/pull/21518/files
|
|
21
23
|
/**
|
|
22
|
-
* Given an input string, convert to kebab-case and append a hash
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* filename per OS. Avoids ERRNAMETOOLONG error.
|
|
24
|
+
* Given an input string, convert to kebab-case and append a hash, avoiding name
|
|
25
|
+
* collision. Also removes part of the string if its larger than the allowed
|
|
26
|
+
* filename per OS, avoiding `ERRNAMETOOLONG` error.
|
|
26
27
|
*/
|
|
27
28
|
export function docuHash(str: string): string {
|
|
28
29
|
if (str === '/') {
|
package/src/i18nUtils.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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 _ from 'lodash';
|
|
10
|
+
import type {
|
|
11
|
+
TranslationFileContent,
|
|
12
|
+
TranslationFile,
|
|
13
|
+
I18n,
|
|
14
|
+
} from '@docusaurus/types';
|
|
15
|
+
import {DEFAULT_PLUGIN_ID, I18N_DIR_NAME} from './constants';
|
|
16
|
+
import {normalizeUrl} from './urlUtils';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Takes a list of translation file contents, and shallow-merges them into one.
|
|
20
|
+
*/
|
|
21
|
+
export function mergeTranslations(
|
|
22
|
+
contents: TranslationFileContent[],
|
|
23
|
+
): TranslationFileContent {
|
|
24
|
+
return contents.reduce((acc, content) => ({...acc, ...content}), {});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Useful to update all the messages of a translation file. Used in tests to
|
|
29
|
+
* simulate translations.
|
|
30
|
+
*/
|
|
31
|
+
export function updateTranslationFileMessages(
|
|
32
|
+
translationFile: TranslationFile,
|
|
33
|
+
updateMessage: (message: string) => string,
|
|
34
|
+
): TranslationFile {
|
|
35
|
+
return {
|
|
36
|
+
...translationFile,
|
|
37
|
+
content: _.mapValues(translationFile.content, (translation) => ({
|
|
38
|
+
...translation,
|
|
39
|
+
message: updateMessage(translation.message),
|
|
40
|
+
})),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Takes everything needed and constructs a plugin i18n path. Plugins should
|
|
46
|
+
* expect everything it needs for translations to be found under this path.
|
|
47
|
+
*/
|
|
48
|
+
export function getPluginI18nPath({
|
|
49
|
+
siteDir,
|
|
50
|
+
locale,
|
|
51
|
+
pluginName,
|
|
52
|
+
pluginId = DEFAULT_PLUGIN_ID,
|
|
53
|
+
subPaths = [],
|
|
54
|
+
}: {
|
|
55
|
+
siteDir: string;
|
|
56
|
+
locale: string;
|
|
57
|
+
pluginName: string;
|
|
58
|
+
pluginId?: string | undefined;
|
|
59
|
+
subPaths?: string[];
|
|
60
|
+
}): string {
|
|
61
|
+
return path.join(
|
|
62
|
+
siteDir,
|
|
63
|
+
I18N_DIR_NAME,
|
|
64
|
+
// Namespace first by locale: convenient to work in a single folder for a
|
|
65
|
+
// translator
|
|
66
|
+
locale,
|
|
67
|
+
// Make it convenient to use for single-instance
|
|
68
|
+
// ie: return "docs", not "docs-default" nor "docs/default"
|
|
69
|
+
`${pluginName}${pluginId === DEFAULT_PLUGIN_ID ? '' : `-${pluginId}`}`,
|
|
70
|
+
...subPaths,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Takes a path and returns a localized a version (which is basically `path +
|
|
76
|
+
* i18n.currentLocale`).
|
|
77
|
+
*/
|
|
78
|
+
export function localizePath({
|
|
79
|
+
pathType,
|
|
80
|
+
path: originalPath,
|
|
81
|
+
i18n,
|
|
82
|
+
options = {},
|
|
83
|
+
}: {
|
|
84
|
+
/**
|
|
85
|
+
* FS paths will treat Windows specially; URL paths will always have a
|
|
86
|
+
* trailing slash to make it a valid base URL.
|
|
87
|
+
*/
|
|
88
|
+
pathType: 'fs' | 'url';
|
|
89
|
+
/** The path, URL or file path, to be localized. */
|
|
90
|
+
path: string;
|
|
91
|
+
/** The current i18n context. */
|
|
92
|
+
i18n: I18n;
|
|
93
|
+
options?: {
|
|
94
|
+
/**
|
|
95
|
+
* By default, we don't localize the path of defaultLocale. This option
|
|
96
|
+
* would override that behavior. Setting `false` is useful for `yarn build
|
|
97
|
+
* -l zh-Hans` to always emit into the root build directory.
|
|
98
|
+
*/
|
|
99
|
+
localizePath?: boolean;
|
|
100
|
+
};
|
|
101
|
+
}): string {
|
|
102
|
+
const shouldLocalizePath: boolean =
|
|
103
|
+
//
|
|
104
|
+
options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale;
|
|
105
|
+
|
|
106
|
+
if (!shouldLocalizePath) {
|
|
107
|
+
return originalPath;
|
|
108
|
+
}
|
|
109
|
+
// FS paths need special care, for Windows support
|
|
110
|
+
if (pathType === 'fs') {
|
|
111
|
+
return path.join(originalPath, i18n.currentLocale);
|
|
112
|
+
}
|
|
113
|
+
// Url paths; add a trailing slash so it's a valid base URL
|
|
114
|
+
return normalizeUrl([originalPath, i18n.currentLocale, '/']);
|
|
115
|
+
}
|