Skip to content

Commit 378a0d6

Browse files
committed
DON'T TOUCH ANYTHING
1 parent e6a3144 commit 378a0d6

File tree

1 file changed

+167
-176
lines changed

1 file changed

+167
-176
lines changed
+167-176
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import logger from '@docusaurus/logger';
22
import { visitParents } from 'unist-util-visit-parents';
33
import { filter } from 'unist-util-filter';
4-
import fs from 'fs';
5-
import path from 'path';
4+
import fs from 'node:fs';
5+
import path from 'node:path';
66
import { Node, Parent } from 'unist';
77
import type { InlineCode, Link, LinkReference, Text } from 'mdast';
88
import { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
@@ -20,22 +20,176 @@ const fileContent = new Map<
2020
string,
2121
{ promise: Promise<Parent>; resolve?: (value: Parent) => void }
2222
>();
23-
const structureDefinitions = new Map<string, string>();
24-
const modifiers = new Set<Promise<void>>();
23+
24+
const EXCLUDE_LIST = ['browser-window-options', 'web-preferences'];
2525

2626
export default function attacher() {
2727
return transformer;
2828
}
2929

3030
async function transformer(tree: Parent, file: VFile) {
31-
// While transforming API structures, put the unmodified content
32-
// (we don't want previews on previews or deadlocks) into a map
33-
// so that when other docs are processed they can grab the content
34-
// which Docusaurus has already processed (important for links).
35-
// Since we don't want to depend on the order in which docs are
36-
// transformed, if other docs are waiting on the content of an
37-
// API structure there will be a promise resolver in the map and
38-
// the other docs will be awaiting the associated promise.
31+
const structureDefinitions = new Map<string, string>();
32+
const mutations = new Set<Promise<void>>();
33+
/**
34+
* This function is the test function for the first pass of the tree visitor.
35+
* Any values returning 'true' will run replaceLinkWithPreview().
36+
*
37+
* As a side effect, this function also puts all reference-style links (definitions)
38+
* for API structures into a Map, which will be used on the second pass.
39+
*/
40+
const checkLinksandDefinitions = (node: Node): node is Link => {
41+
if (isDefinition(node) && node.url.includes('/api/structures/')) {
42+
structureDefinitions.set(node.identifier, node.url);
43+
}
44+
if (isLink(node) && node.url.includes('/api/structures/')) {
45+
return EXCLUDE_LIST.every(
46+
(excludedFile) => !node.url.endsWith(`/api/structures/${excludedFile}`)
47+
);
48+
}
49+
50+
return false;
51+
};
52+
53+
/**
54+
* This function is the test function from the second pass of the tree visitor.
55+
* Any values returning 'true' will run replaceLinkWithPreview().
56+
*/
57+
function isStructureLinkReference(node: Node): node is LinkReference {
58+
return isLinkReference(node) && structureDefinitions.has(node.identifier);
59+
}
60+
61+
function replaceLinkWithPreview(
62+
node: Link | LinkReference,
63+
parents: Parent[]
64+
) {
65+
// depending on if the node is a direct link or a reference-style link,
66+
// we get its URL differently.
67+
let relativeStructureUrl: string;
68+
let isInline = false;
69+
if (isLink(node)) {
70+
relativeStructureUrl = node.url;
71+
} else if (isLinkReference(node)) {
72+
relativeStructureUrl = structureDefinitions.get(node.identifier);
73+
} else {
74+
return;
75+
}
76+
77+
if (relativeStructureUrl.endsWith('?inline')) {
78+
relativeStructureUrl = relativeStructureUrl.split('?inline')[0];
79+
isInline = true;
80+
}
81+
82+
const relativeStructurePath = `${relativeStructureUrl}.md`;
83+
84+
// No file content promise available, so add one and then wait
85+
// on it being resolved when the structure doc is processed
86+
if (!fileContent.has(relativeStructurePath)) {
87+
let resolve: (value: Parent) => void;
88+
let reject: (err: Error) => void;
89+
90+
// Set a timeout as a backstop so we don't deadlock forever if something
91+
// causes content to never be resolved - in theory an upstream change in
92+
// Docusaurus could cause that if they limited how many files are being
93+
// processed in parallel such that too many docs are awaiting others
94+
const timeoutId = setTimeout(() => {
95+
// links in translated locale [xy] have their paths prefixed with /xy/
96+
const isTranslatedDoc = !relativeStructurePath.startsWith('/docs/');
97+
98+
if (isTranslatedDoc) {
99+
// If we're running locally we might not have translations downloaded
100+
// so if we don't find it locally just supply the default locale
101+
const [_fullPath, locale, docPath] = relativeStructurePath.match(
102+
/\/([a-z][a-z])\/docs\/(.*)/
103+
);
104+
const defaultLocalePath = `/docs/${docPath}`;
105+
const localeDir = path.join(__dirname, '..', '..', 'i18n', locale);
106+
107+
if (!fs.existsSync(localeDir)) {
108+
if (fileContent.has(defaultLocalePath)) {
109+
const { promise } = fileContent.get(defaultLocalePath);
110+
promise.then((content) => resolve(content));
111+
return;
112+
}
113+
}
114+
}
115+
116+
reject(
117+
new Error(
118+
`Timed out waiting for API structure content from ${relativeStructurePath}`
119+
)
120+
);
121+
}, 60000000);
122+
123+
const promise = new Promise<Parent>((resolve_, reject_) => {
124+
resolve = (value: Parent) => {
125+
clearTimeout(timeoutId);
126+
resolve_(value);
127+
};
128+
reject = reject_;
129+
});
130+
131+
fileContent.set(relativeStructurePath, { promise, resolve });
132+
}
133+
134+
const { promise: targetStructure } = fileContent.get(relativeStructurePath);
135+
136+
if (
137+
(Array.isArray(node.children) &&
138+
node.children.length > 0 &&
139+
isText(node.children[0])) ||
140+
isInlineCode(node.children[0])
141+
) {
142+
mutations.add(
143+
targetStructure
144+
.then((structureContent) => {
145+
if (isInline) {
146+
const siblings = parents[parents.length - 1].children;
147+
const filtered = filter(
148+
structureContent,
149+
(node) => node.type !== 'mdxjsEsm' && node.type !== 'heading'
150+
);
151+
siblings.push(filtered);
152+
} else {
153+
// replace the Link node with an MDX element in-place
154+
const title = (node.children[0] as Text | InlineCode).value;
155+
const previewNode = node as unknown as MdxJsxFlowElement;
156+
previewNode.type = 'mdxJsxFlowElement';
157+
previewNode.name = 'APIStructurePreview';
158+
previewNode.children = [];
159+
previewNode.data = {
160+
_mdxExplicitJsx: true,
161+
};
162+
previewNode.attributes = [
163+
{
164+
type: 'mdxJsxAttribute',
165+
name: 'url',
166+
value: `${relativeStructureUrl}`,
167+
},
168+
{
169+
type: 'mdxJsxAttribute',
170+
name: 'title',
171+
value: title,
172+
},
173+
{
174+
type: 'mdxJsxAttribute',
175+
name: 'content',
176+
value: JSON.stringify(structureContent),
177+
},
178+
];
179+
}
180+
})
181+
.catch((err) => {
182+
logger.error(err);
183+
// NOTE - if build starts failing, comment the throw out
184+
throw err;
185+
})
186+
);
187+
}
188+
}
189+
visitParents(tree, checkLinksandDefinitions, replaceLinkWithPreview);
190+
visitParents(tree, isStructureLinkReference, replaceLinkWithPreview);
191+
await Promise.all(Array.from(mutations));
192+
39193
if (file.path.includes('/api/structures/')) {
40194
let relativePath = `/${path.relative(file.cwd, file.path)}`;
41195

@@ -57,171 +211,8 @@ async function transformer(tree: Parent, file: VFile) {
57211
fileContent.set(relativePath, { promise: Promise.resolve(tree) });
58212
}
59213
}
60-
structureDefinitions.clear();
61-
modifiers.clear();
62-
visitParents(tree, checkLinksandDefinitions, replaceLinkWithPreview);
63-
visitParents(tree, isStructureLinkReference, replaceLinkWithPreview);
64214
const importNode = getJSXImport('APIStructurePreview');
65-
if (modifiers.size) {
215+
if (mutations.size) {
66216
tree.children.unshift(importNode);
67217
}
68-
await Promise.all(Array.from(modifiers));
69-
}
70-
71-
/**
72-
* This function is the test function for the first pass of the tree visitor.
73-
* Any values returning 'true' will run replaceLinkWithPreview().
74-
*
75-
* As a side effect, this function also puts all reference-style links (definitions)
76-
* for API structures into a Map, which will be used on the second pass.
77-
*/
78-
const checkLinksandDefinitions = (node: Node): node is Link => {
79-
if (isDefinition(node) && node.url.includes('/api/structures/')) {
80-
structureDefinitions.set(node.identifier, node.url);
81-
}
82-
if (isLink(node) && node.url.includes('/api/structures/')) {
83-
return true;
84-
}
85-
86-
return false;
87-
};
88-
89-
/**
90-
* This function is the test function from the second pass of the tree visitor.
91-
* Any values returning 'true' will run replaceLinkWithPreview().
92-
*/
93-
function isStructureLinkReference(node: Node): node is LinkReference {
94-
return isLinkReference(node) && structureDefinitions.has(node.identifier);
95-
}
96-
97-
function replaceLinkWithPreview(node: Link | LinkReference, parents: Parent[]) {
98-
// depending on if the node is a direct link or a reference-style link,
99-
// we get its URL differently.
100-
let relativeStructureUrl: string;
101-
let isInline = false;
102-
if (isLink(node)) {
103-
relativeStructureUrl = node.url;
104-
} else if (isLinkReference(node)) {
105-
relativeStructureUrl = structureDefinitions.get(node.identifier);
106-
} else {
107-
return;
108-
}
109-
110-
if (relativeStructureUrl.endsWith('?inline')) {
111-
relativeStructureUrl = relativeStructureUrl.split('?inline')[0];
112-
isInline = true;
113-
}
114-
115-
const relativeStructurePath = `${relativeStructureUrl}.md`;
116-
117-
// No file content promise available, so add one and then wait
118-
// on it being resolved when the structure doc is processed
119-
if (!fileContent.has(relativeStructurePath)) {
120-
let resolve: (value: Parent) => void;
121-
let reject: (err: Error) => void;
122-
123-
// Set a timeout as a backstop so we don't deadlock forever if something
124-
// causes content to never be resolved - in theory an upstream change in
125-
// Docusaurus could cause that if they limited how many files are being
126-
// processed in parallel such that too many docs are awaiting others
127-
const timeoutId = setTimeout(() => {
128-
// links in translated locale [xy] have their paths prefixed with /xy/
129-
const isTranslatedDoc = !relativeStructurePath.startsWith('/docs/');
130-
131-
if (isTranslatedDoc) {
132-
// If we're running locally we might not have translations downloaded
133-
// so if we don't find it locally just supply the default locale
134-
const [_fullPath, locale, docPath] = relativeStructurePath.match(
135-
/\/([a-z][a-z])\/docs\/(.*)/
136-
);
137-
const defaultLocalePath = `/docs/${docPath}`;
138-
const localeDir = path.join(__dirname, '..', '..', 'i18n', locale);
139-
140-
if (!fs.existsSync(localeDir)) {
141-
if (fileContent.has(defaultLocalePath)) {
142-
const { promise } = fileContent.get(defaultLocalePath);
143-
promise.then((content) => resolve(content));
144-
return;
145-
}
146-
}
147-
}
148-
149-
reject(
150-
new Error(
151-
`Timed out waiting for API structure content from ${relativeStructurePath}`
152-
)
153-
);
154-
}, 60000);
155-
156-
const promise = new Promise<Parent>((resolve_, reject_) => {
157-
resolve = (value: Parent) => {
158-
clearTimeout(timeoutId);
159-
resolve_(value);
160-
};
161-
reject = reject_;
162-
});
163-
164-
fileContent.set(relativeStructurePath, { promise, resolve });
165-
}
166-
167-
const { promise } = fileContent.get(relativeStructurePath);
168-
169-
if (
170-
(Array.isArray(node.children) &&
171-
node.children.length > 0 &&
172-
isText(node.children[0])) ||
173-
isInlineCode(node.children[0])
174-
) {
175-
modifiers.add(
176-
promise
177-
.then((content) => {
178-
const title = (node.children[0] as Text | InlineCode).value;
179-
180-
if (isInline) {
181-
const siblings = parents[parents.length - 1].children;
182-
const filtered = filter(
183-
content,
184-
(node) => node.type !== 'mdxjsEsm' && node.type !== 'heading'
185-
);
186-
visitParents(
187-
filtered,
188-
checkLinksandDefinitions,
189-
replaceLinkWithPreview
190-
);
191-
siblings.push(filtered);
192-
} else {
193-
// replace the Link node with an MDX element in-place
194-
const previewNode = node as unknown as MdxJsxFlowElement;
195-
previewNode.type = 'mdxJsxFlowElement';
196-
previewNode.name = 'APIStructurePreview';
197-
previewNode.children = [];
198-
previewNode.data = {
199-
_mdxExplicitJsx: true,
200-
};
201-
previewNode.attributes = [
202-
{
203-
type: 'mdxJsxAttribute',
204-
name: 'url',
205-
value: `${relativeStructureUrl}`,
206-
},
207-
{
208-
type: 'mdxJsxAttribute',
209-
name: 'title',
210-
value: title,
211-
},
212-
{
213-
type: 'mdxJsxAttribute',
214-
name: 'content',
215-
value: JSON.stringify(content),
216-
},
217-
];
218-
}
219-
})
220-
.catch((err) => {
221-
logger.error(err);
222-
// NOTE - if build starts failing, comment the throw out
223-
throw err;
224-
})
225-
);
226-
}
227218
}

0 commit comments

Comments
 (0)