Merge pull request #10764 from zchsh/zs.remote-plugin-zip-approach
website: Implement RFC MKTG-033
This commit is contained in:
commit
ef6093c4c3
|
@ -0,0 +1,77 @@
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const fetchPluginDocs = require("../../website/components/remote-plugin-docs/utils/fetch-plugin-docs");
|
||||||
|
|
||||||
|
const COLOR_RESET = "\x1b[0m";
|
||||||
|
const COLOR_GREEN = "\x1b[32m";
|
||||||
|
const COLOR_BLUE = "\x1b[34m";
|
||||||
|
const COLOR_RED = "\x1b[31m";
|
||||||
|
|
||||||
|
async function checkPluginDocs() {
|
||||||
|
const failureMessages = [];
|
||||||
|
const pluginsPath = "website/data/docs-remote-plugins.json";
|
||||||
|
const pluginsFile = fs.readFileSync(path.join(process.cwd(), pluginsPath));
|
||||||
|
const pluginEntries = JSON.parse(pluginsFile);
|
||||||
|
const entriesCount = pluginEntries.length;
|
||||||
|
console.log(`\nResolving plugin docs from ${entriesCount} repositories …`);
|
||||||
|
for (var i = 0; i < entriesCount; i++) {
|
||||||
|
const pluginEntry = pluginEntries[i];
|
||||||
|
const { title, repo, version } = pluginEntry;
|
||||||
|
console.log(`\n${COLOR_BLUE}${repo}${COLOR_RESET} | ${title}`);
|
||||||
|
console.log(`Fetching docs from release "${version}" …`);
|
||||||
|
try {
|
||||||
|
const undefinedProps = ["title", "repo", "version", "path"].filter(
|
||||||
|
(key) => typeof pluginEntry[key] == "undefined"
|
||||||
|
);
|
||||||
|
if (undefinedProps.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to validate plugin docs config. Undefined configuration properties ${JSON.stringify(
|
||||||
|
undefinedProps
|
||||||
|
)} found for "${
|
||||||
|
title || pluginEntry.path || repo
|
||||||
|
}". In "website/data/docs-remote-plugins.json", please ensure the missing properties ${JSON.stringify(
|
||||||
|
undefinedProps
|
||||||
|
)} are defined. Additional information on this configuration can be found in "website/README.md".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const docsMdxFiles = await fetchPluginDocs({ repo, tag: version });
|
||||||
|
const mdxFilesByComponent = docsMdxFiles.reduce((acc, mdxFile) => {
|
||||||
|
const componentType = mdxFile.filePath.split("/")[1];
|
||||||
|
if (!acc[componentType]) acc[componentType] = [];
|
||||||
|
acc[componentType].push(mdxFile);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
console.log(`${COLOR_GREEN}Found valid docs:${COLOR_RESET}`);
|
||||||
|
Object.keys(mdxFilesByComponent).forEach((component) => {
|
||||||
|
const componentFiles = mdxFilesByComponent[component];
|
||||||
|
console.log(` ${component}`);
|
||||||
|
componentFiles.forEach(({ filePath }) => {
|
||||||
|
const pathFromComponent = filePath.split("/").slice(2).join("/");
|
||||||
|
console.log(` ├── ${pathFromComponent}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`${COLOR_RED}${err}${COLOR_RESET}`);
|
||||||
|
failureMessages.push(`\n${COLOR_RED}× ${repo}: ${COLOR_RESET}${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureMessages.length === 0) {
|
||||||
|
console.log(
|
||||||
|
`\n---\n\n${COLOR_GREEN}Summary: Successfully resolved all plugin docs.`
|
||||||
|
);
|
||||||
|
pluginEntries.forEach((e) =>
|
||||||
|
console.log(`${COLOR_GREEN}✓ ${e.repo}${COLOR_RESET}`)
|
||||||
|
);
|
||||||
|
console.log("");
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`\n---\n\n${COLOR_RED}Summary: Failed to fetch docs for ${failureMessages.length} plugin(s):`
|
||||||
|
);
|
||||||
|
failureMessages.forEach((err) => console.log(err));
|
||||||
|
console.log("");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPluginDocs();
|
|
@ -0,0 +1,29 @@
|
||||||
|
#
|
||||||
|
# This GitHub action checks plugin repositories for valid docs.
|
||||||
|
#
|
||||||
|
# This provides a quick assessment on PRs of whether
|
||||||
|
# there might be issues with docs in plugin repositories.
|
||||||
|
#
|
||||||
|
# This is intended to help debug Vercel build issues, which
|
||||||
|
# may or may not be related to docs in plugin repositories.
|
||||||
|
|
||||||
|
name: "website: Check plugin docs"
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "website/**"
|
||||||
|
schedule:
|
||||||
|
- cron: "45 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-plugin-docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm i isomorphic-unfetch adm-zip gray-matter
|
||||||
|
- name: Fetch and validate plugin docs
|
||||||
|
run: node .github/workflows/check-plugin-docs.js
|
|
@ -7,7 +7,7 @@ name: Check markdown links on modified website files
|
||||||
jobs:
|
jobs:
|
||||||
vercel-deployment-poll:
|
vercel-deployment-poll:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 3 #cancel job if no deployment is found within x minutes
|
timeout-minutes: 5 #cancel job if no deployment is found within x minutes
|
||||||
outputs:
|
outputs:
|
||||||
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
|
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -238,14 +238,22 @@ $ terraform apply
|
||||||
|
|
||||||
<!-- END: editing-markdown -->
|
<!-- END: editing-markdown -->
|
||||||
|
|
||||||
<!-- BEGIN: editing-docs-sidebars -->
|
<!--
|
||||||
<!-- Generated text, do not edit directly -->
|
|
||||||
|
NOTE: The "Editing Navigation Sidebars" section is forked from editing-docs-sidebars.
|
||||||
|
|
||||||
|
We plan on rolling these changes back into our "readme partials" source once all docs sites
|
||||||
|
have been transitioned to the JSON navigation format. See MKTG_032 for details:
|
||||||
|
|
||||||
|
https://docs.google.com/document/d/1kYvbyd6njHFSscoE1dtDNHQ3U8IzaMdcjOS0jg87rHg/
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
## Editing Navigation Sidebars
|
## Editing Navigation Sidebars
|
||||||
|
|
||||||
The structure of the sidebars are controlled by files in the [`/data` directory](data). For example, [this file](data/docs-navigation.js) controls the **docs** sidebar. Within the `data` folder, any file with `-navigation` after it controls the navigation for the given section.
|
The structure of the sidebars are controlled by files in the [`/data` directory](data). For example, [data/docs-nav-data.json](data/docs-nav-data.json) controls the **docs** sidebar. Within the `data` folder, any file with `-nav-data` after it controls the navigation for the given section.
|
||||||
|
|
||||||
The sidebar uses a simple recursive data structure to represent _files_ and _directories_. A file is represented by a string, and a directory is represented by an object. The sidebar is meant to reflect the structure of the docs within the filesystem while also allowing custom ordering. Let's look at an example. First, here's our example folder structure:
|
The sidebar uses a simple recursive data structure to represent _files_ and _directories_. The sidebar is meant to reflect the structure of the docs within the filesystem while also allowing custom ordering. Let's look at an example. First, here's our example folder structure:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.
|
.
|
||||||
|
@ -259,36 +267,55 @@ The sidebar uses a simple recursive data structure to represent _files_ and _dir
|
||||||
│ └── nested-file.mdx
|
│ └── nested-file.mdx
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's how this folder structure could be represented as a sidebar navigation, in this example it would be the file `website/data/docs-navigation.js`:
|
Here's how this folder structure could be represented as a sidebar navigation, in this example it would be the file `website/data/docs-nav-data.json`:
|
||||||
|
|
||||||
```js
|
```json
|
||||||
export default {
|
[
|
||||||
category: 'directory',
|
|
||||||
content: [
|
|
||||||
'file',
|
|
||||||
'another-file',
|
|
||||||
{
|
{
|
||||||
category: 'nested-directory',
|
"title": "Directory",
|
||||||
content: ['nested-file'],
|
"routes": [
|
||||||
|
{
|
||||||
|
"title": "Overview",
|
||||||
|
"path": "directory"
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
}
|
"title": "File",
|
||||||
|
"path": "directory/file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Another File",
|
||||||
|
"path": "directory/another-file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Nested Directory",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"title": "Overview",
|
||||||
|
"path": "directory/nested-directory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Nested File",
|
||||||
|
"path": "directory/nested-directory/nested-file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `category` values will be **directory names** within the `pages/<section>` directory
|
|
||||||
- `content` values will be **file names** within their appropriately nested directory
|
|
||||||
|
|
||||||
A couple more important notes:
|
A couple more important notes:
|
||||||
|
|
||||||
- Within this data structure, ordering does not matter, but hierarchy does. So while you could put `file` and `another-file` in any order, or even leave one or both of them out, you could not decide to un-nest the `nested-directory` object without also un-nesting it in the filesystem.
|
- Within this data structure, ordering is flexible, but hierarchy is not. The structure of the sidebar must correspond to the structure of the content directory. So while you could put `file` and `another-file` in any order in the sidebar, or even leave one or both of them out, you could not decide to un-nest the `nested-directory` object without also un-nesting it in the filesystem.
|
||||||
- The `sidebar_title` frontmatter property on each `mdx` page is responsible for displaying the human-readable page name in the navigation.
|
- The `title` property on each node in the `nav-data` tree is the human-readable name in the navigation.
|
||||||
- _By default_, every directory/category must have an `index.mdx` file. This file will be automatically added to the navigation as "Overview", and its `sidebar_title` property will set the human-readable name of the entire category.
|
- The `path` property on each leaf node in the `nav-data` tree is the URL path where the `.mdx` document will be rendered, and the
|
||||||
|
- Note that "index" files must be explicitly added. These will be automatically resolved, so the `path` value should be, as above, `directory` rather than `directory/index`. A common convention is to set the `title` of an "index" node to be `"Overview"`.
|
||||||
|
|
||||||
Below we will discuss a couple of more unusual but still helpful patterns.
|
Below we will discuss a couple of more unusual but still helpful patterns.
|
||||||
|
|
||||||
### Index-less Categories
|
### Index-less Categories
|
||||||
|
|
||||||
Sometimes you may want to include a category but not have a need for an index page for the category. This can be accomplished, but a human-readable category name needs to be set manually, since the category name is normally pulled from the `sidebar_title` property of the index page. Here's an example of how an index-less category might look:
|
Sometimes you may want to include a category but not have a need for an index page for the category. This can be accomplished, but as with other branch and leaf nodes, a human-readable `title` needs to be set manually. Here's an example of how an index-less category might look:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
.
|
.
|
||||||
|
@ -297,36 +324,95 @@ Sometimes you may want to include a category but not have a need for an index pa
|
||||||
│ └── file.mdx
|
│ └── file.mdx
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```json
|
||||||
// website/data/docs-navigation.js
|
// website/data/docs-nav-data.json
|
||||||
export default {
|
[
|
||||||
category: 'indexless-category',
|
{
|
||||||
name: 'Indexless Category',
|
"title": "Indexless Category",
|
||||||
content: ['file'],
|
"routes": [
|
||||||
}
|
{
|
||||||
|
"title": "File",
|
||||||
|
"path": "indexless-category/file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
The addition of the `name` property to a category object is all it takes to be able to skip the index file.
|
|
||||||
|
|
||||||
### Custom or External Links
|
### Custom or External Links
|
||||||
|
|
||||||
Sometimes you may have a need to include a link that is not directly to a file within the docs hierarchy. This can also be supported using a different pattern. For example:
|
Sometimes you may have a need to include a link that is not directly to a file within the docs hierarchy. This can also be supported using a different pattern. For example:
|
||||||
|
|
||||||
```js
|
```json
|
||||||
export default {
|
[
|
||||||
category: 'directory',
|
{
|
||||||
content: [
|
"name": "Directory",
|
||||||
'file',
|
"routes": [
|
||||||
'another-file',
|
{
|
||||||
{ title: 'Tao of HashiCorp', href: 'https://www.hashicorp.com/tao-of-hashicorp' }
|
"title": "File",
|
||||||
|
"path": "directory/file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Another File",
|
||||||
|
"path": "directory/another-file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Tao of HashiCorp",
|
||||||
|
"href": "https://www.hashicorp.com/tao-of-hashicorp"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
If the link provided in the `href` property is external, it will display a small icon indicating this. If it's internal, it will appear the same way as any other direct file link.
|
If the link provided in the `href` property is external, it will display a small icon indicating this. If it's internal, it will appear the same way as any other direct file link.
|
||||||
|
|
||||||
<!-- END: editing-docs-sidebars -->
|
### Plugin Docs
|
||||||
|
|
||||||
|
Plugin documentation may be located within the `packer` repository, or split out into separate `packer-plugin-` repositories. For plugin docs within the `packer` repository, the process for authoring files and managing sidebar data is identical to the process for other documentation.
|
||||||
|
|
||||||
|
For plugins in separate repositories, additional configuration is required.
|
||||||
|
|
||||||
|
#### Setting up remote plugin docs
|
||||||
|
|
||||||
|
Some setup is required to include docs from remote plugin repositories on the [packer.io/docs](https://www.packer.io/docs) site.
|
||||||
|
|
||||||
|
1. The plugin repository needs to include a `docs.zip` asset in its release
|
||||||
|
2. The `packer` repository must have a corresponding entry in `website/data/docs-remote-plugins.json` which points to the plugin repository.
|
||||||
|
|
||||||
|
The `docs.zip` release asset is expected to be generated as part of the standard release process for `packer-plugin-*` repositories. Additional details on this process can be found in [the `packer-plugin-scaffolding` `README`](https://github.com/hashicorp/packer-plugin-scaffolding#registering-documentation-on-packerio).
|
||||||
|
|
||||||
|
The `docs-remote-plugins.json` file contains an array of entries. Each entry points to a plugin repository. The `{ title, path, repo, version }` properties are required for each entry.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
[
|
||||||
|
{
|
||||||
|
// ALL FIELDS ARE REQUIRED.
|
||||||
|
// "title" sets the human-readable title shown in navigation
|
||||||
|
title: 'Scaffolding',
|
||||||
|
// "path" sets the URL subpath under the component URL (eg `docs/builders`)
|
||||||
|
path: 'scaffolding',
|
||||||
|
// "repo" points to the plugin repo, in the format "organization/repo-name"
|
||||||
|
// if the organization == hashicorp, the plugin docs will be labelled "official".
|
||||||
|
// for all other organizations or users, plugin docs will be labelled "community".
|
||||||
|
repo: 'hashicorp/packer-plugin-scaffolding',
|
||||||
|
// "version" is used to fetch "docs.zip" from the matching tagged release.
|
||||||
|
// version: "latest" is permitted, but please be aware that it
|
||||||
|
// may fetch incompatible or unintended versions of plugin docs.
|
||||||
|
// if version is NOT "latest", and if "docs.zip" is unavailable, then
|
||||||
|
// we fall back to fetching docs from the source "{version}.zip"
|
||||||
|
version: 'v0.0.5',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Updating remote plugin docs
|
||||||
|
|
||||||
|
Documentation from plugin repositories is fetched and rendered every time the Packer website builds. So, to update plugin documentation on the live site:
|
||||||
|
|
||||||
|
1. In the plugin repository, publish a new release that includes a `docs.zip` release asset
|
||||||
|
2. In the `packer` repository, update `website/data/docs-remote-plugins.json` to ensure the corresponding entry points to the correct release `version` (which should correspond to the release's tag name). This may not be necessary if the `version` is set to `"latest"`.
|
||||||
|
3. Rebuild the website. This will happen automatically on commits to `stable-website`. In exceptional cases, the site can also be [manually re-deployed through Vercel](https://vercel.com/hashicorp/packer).
|
||||||
|
|
||||||
<!-- BEGIN: releases -->
|
<!-- BEGIN: releases -->
|
||||||
<!-- Generated text, do not edit directly -->
|
<!-- Generated text, do not edit directly -->
|
||||||
|
@ -374,8 +460,18 @@ You may customize the parameters in any way you'd like. To remove a prerelease f
|
||||||
|
|
||||||
<!-- END: releases -->
|
<!-- END: releases -->
|
||||||
|
|
||||||
<!-- BEGIN: redirects -->
|
<!--
|
||||||
<!-- Generated text, do not edit directly -->
|
|
||||||
|
NOTE: The "Redirects" section is forked from redirects.
|
||||||
|
|
||||||
|
There are minor changes related to sidebar navigation format changes.
|
||||||
|
|
||||||
|
We plan on rolling these changes back into our "readme partials" source once all docs sites
|
||||||
|
have been transitioned to the JSON navigation format. See MKTG_032 for details:
|
||||||
|
|
||||||
|
https://docs.google.com/document/d/1kYvbyd6njHFSscoE1dtDNHQ3U8IzaMdcjOS0jg87rHg/
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
## Link Validation
|
## Link Validation
|
||||||
|
|
||||||
|
@ -406,7 +502,7 @@ There are a couple important caveats with redirects. First, redirects are applie
|
||||||
|
|
||||||
Second, redirects do not apply to client-side navigation. By default, all links in the navigation and docs sidebar will navigate purely on the client side, which makes navigation through the docs significantly faster, especially for those with low-end devices and/or weak internet connections. In the future, we plan to convert all internal links within docs pages to behave this way as well. This means that if there is a link on this website to a given piece of content that has changed locations in some way, we need to also _directly change existing links to the content_. This way, if a user clicks a link that navigates on the client side, or if they hit the url directly and the page renders from the server side, either one will work perfectly.
|
Second, redirects do not apply to client-side navigation. By default, all links in the navigation and docs sidebar will navigate purely on the client side, which makes navigation through the docs significantly faster, especially for those with low-end devices and/or weak internet connections. In the future, we plan to convert all internal links within docs pages to behave this way as well. This means that if there is a link on this website to a given piece of content that has changed locations in some way, we need to also _directly change existing links to the content_. This way, if a user clicks a link that navigates on the client side, or if they hit the url directly and the page renders from the server side, either one will work perfectly.
|
||||||
|
|
||||||
Let's look at an example. Say you have a page called `/docs/foo` which needs to be moved to `/docs/nested/foo`. Additionally, this is a page that has been around for a while and we know there are links into `/docs/foo.html` left over from our previous website structure. First, we move the page, then adjust the docs sidenav, in `data/docs-navigation.js`. Find the category the page is in, and move it into the appropriate subcategory. Next, we add to `_redirects` as such:
|
Let's look at an example. Say you have a page called `/docs/foo` which needs to be moved to `/docs/nested/foo`. Additionally, this is a page that has been around for a while and we know there are links into `/docs/foo.html` left over from our previous website structure. First, we move the page, then adjust the docs sidenav, in `data/docs-nav-data.json`. Find the category the page is in, and move it into the appropriate subcategory. Next, we add to `_redirects` as such:
|
||||||
|
|
||||||
```
|
```
|
||||||
/foo /nested/foo 301!
|
/foo /nested/foo 301!
|
||||||
|
@ -415,26 +511,36 @@ Let's look at an example. Say you have a page called `/docs/foo` which needs to
|
||||||
|
|
||||||
Finally, we run a global search for internal links to `/foo`, and make sure to adjust them to be `/nested/foo` - this is to ensure that client-side navigation still works correctly. _Adding a redirect alone is not enough_.
|
Finally, we run a global search for internal links to `/foo`, and make sure to adjust them to be `/nested/foo` - this is to ensure that client-side navigation still works correctly. _Adding a redirect alone is not enough_.
|
||||||
|
|
||||||
One more example - let's say that content is being moved to an external website. A common example is guides moving to `learn.hashicorp.com`. In this case, we take all the same steps, except that we need to make a different type of change to the `docs-navigation` file. If previously the structure looked like:
|
One more example - let's say that content is being moved to an external website. A common example is guides moving to `learn.hashicorp.com`. In this case, we take all the same steps, except that we need to make a different type of change to the `docs-nav-data` file. If previously the structure looked like:
|
||||||
|
|
||||||
```js
|
```json
|
||||||
{
|
[
|
||||||
category: 'docs',
|
{
|
||||||
content: [
|
"name": "Docs",
|
||||||
'foo'
|
"routes": [
|
||||||
|
{
|
||||||
|
"title": "Foo",
|
||||||
|
"path": "docs/foo"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
If we no longer want the link to be in the side nav, we can simply remove it. If we do still want the link in the side nav, but pointing to an external destnation, we need to slightly change the structure as such:
|
If we no longer want the link to be in the side nav, we can simply remove it. If we do still want the link in the side nav, but pointing to an external destination, we need to slightly change the structure as such:
|
||||||
|
|
||||||
```js
|
```json
|
||||||
{
|
[
|
||||||
category: 'docs',
|
{
|
||||||
content: [
|
"name": "Docs",
|
||||||
{ title: 'Foo Title', href: 'https://learn.hashicorp.com/<product>/foo' }
|
"routes": [
|
||||||
|
{
|
||||||
|
"title": "Foo",
|
||||||
|
"href": "https://learn.hashicorp.com/<product>/foo"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
As the majority of items in the side nav are internal links, the structure makes it as easy as possible to represent these links. This alternate syntax is the most concise manner than an external link can be represented. External links can be used anywhere within the docs sidenav.
|
As the majority of items in the side nav are internal links, the structure makes it as easy as possible to represent these links. This alternate syntax is the most concise manner than an external link can be represented. External links can be used anywhere within the docs sidenav.
|
||||||
|
|
|
@ -12,7 +12,9 @@ to match Terraform Registry tier labels.
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1px 8px 2px 8px;
|
padding: 1px 8px 2px 8px;
|
||||||
vertical-align: baseline;
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
vertical-align: bottom;
|
||||||
|
|
||||||
/* variations */
|
/* variations */
|
||||||
&[data-tier='official'] {
|
&[data-tier='official'] {
|
||||||
|
@ -52,4 +54,3 @@ search bar present on docs pages */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,9 @@ import path from 'path'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getPathsFromNavData,
|
getPathsFromNavData,
|
||||||
validateNavData,
|
|
||||||
} from '@hashicorp/react-docs-page/server'
|
} from '@hashicorp/react-docs-page/server'
|
||||||
import renderPageMdx from '@hashicorp/react-docs-page/render-page-mdx'
|
import renderPageMdx from '@hashicorp/react-docs-page/render-page-mdx'
|
||||||
import fetchGithubFile from './utils/fetch-github-file'
|
import resolveNavData from './utils/resolve-nav-data'
|
||||||
import mergeRemotePlugins from './utils/merge-remote-plugins'
|
|
||||||
|
|
||||||
const IS_DEV = process.env.VERCEL_ENV !== 'production'
|
|
||||||
|
|
||||||
async function generateStaticPaths(navDataFile, contentDir, options = {}) {
|
async function generateStaticPaths(navDataFile, contentDir, options = {}) {
|
||||||
const navData = await resolveNavData(navDataFile, contentDir, options)
|
const navData = await resolveNavData(navDataFile, contentDir, options)
|
||||||
|
@ -21,7 +17,8 @@ async function generateStaticProps(
|
||||||
navDataFile,
|
navDataFile,
|
||||||
localContentDir,
|
localContentDir,
|
||||||
params,
|
params,
|
||||||
{ productName, remotePluginsFile, additionalComponents } = {}
|
product,
|
||||||
|
{ remotePluginsFile, additionalComponents, mainBranch = 'main' } = {}
|
||||||
) {
|
) {
|
||||||
const navData = await resolveNavData(navDataFile, localContentDir, {
|
const navData = await resolveNavData(navDataFile, localContentDir, {
|
||||||
remotePluginsFile,
|
remotePluginsFile,
|
||||||
|
@ -30,12 +27,15 @@ async function generateStaticProps(
|
||||||
const navNode = getNodeFromPath(pathToMatch, navData, localContentDir)
|
const navNode = getNodeFromPath(pathToMatch, navData, localContentDir)
|
||||||
const { filePath, remoteFile, pluginTier } = navNode
|
const { filePath, remoteFile, pluginTier } = navNode
|
||||||
// Fetch the MDX file content
|
// Fetch the MDX file content
|
||||||
const [err, mdxString] = filePath
|
const mdxString = remoteFile
|
||||||
? // Read local content from the filesystem
|
? remoteFile.fileString
|
||||||
[null, fs.readFileSync(path.join(process.cwd(), filePath), 'utf8')]
|
: fs.readFileSync(path.join(process.cwd(), filePath), 'utf8')
|
||||||
: // Fetch remote content using GitHub's API
|
// Construct the githubFileUrl, used for "Edit this page" link
|
||||||
await fetchGithubFile(remoteFile)
|
// Note: we expect remote files, such as those used to render plugin docs,
|
||||||
if (err) throw new Error(err)
|
// to have a sourceUrl defined, that points to the file we built from
|
||||||
|
const githubFileUrl = remoteFile
|
||||||
|
? remoteFile.sourceUrl
|
||||||
|
: `https://github.com/hashicorp/${product.slug}/blob/${mainBranch}/website/${filePath}`
|
||||||
// For plugin pages, prefix the MDX content with a
|
// For plugin pages, prefix the MDX content with a
|
||||||
// label that reflects the plugin tier
|
// label that reflects the plugin tier
|
||||||
// (current options are "Official" or "Community")
|
// (current options are "Official" or "Community")
|
||||||
|
@ -48,41 +48,22 @@ async function generateStaticProps(
|
||||||
}
|
}
|
||||||
const { mdxSource, frontMatter } = await renderPageMdx(mdxString, {
|
const { mdxSource, frontMatter } = await renderPageMdx(mdxString, {
|
||||||
additionalComponents,
|
additionalComponents,
|
||||||
productName,
|
productName: product.name,
|
||||||
mdxContentHook,
|
mdxContentHook,
|
||||||
})
|
})
|
||||||
// Build the currentPath from page parameters
|
// Build the currentPath from page parameters
|
||||||
const currentPath = !params.page ? '' : params.page.join('/')
|
const currentPath = !params.page ? '' : params.page.join('/')
|
||||||
// In development, set a flag if there is no GITHUB_API_TOKEN,
|
|
||||||
// as this means dev is seeing only local content, and we want to flag that
|
|
||||||
const isDevMissingRemotePlugins = IS_DEV && !process.env.GITHUB_API_TOKEN
|
|
||||||
return {
|
return {
|
||||||
currentPath,
|
currentPath,
|
||||||
frontMatter,
|
frontMatter,
|
||||||
isDevMissingRemotePlugins,
|
|
||||||
mdxSource,
|
mdxSource,
|
||||||
mdxString,
|
mdxString,
|
||||||
|
githubFileUrl,
|
||||||
navData,
|
navData,
|
||||||
navNode,
|
navNode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveNavData(navDataFile, localContentDir, options = {}) {
|
|
||||||
const { remotePluginsFile } = options
|
|
||||||
// Read in files
|
|
||||||
const navDataPath = path.join(process.cwd(), navDataFile)
|
|
||||||
const navData = JSON.parse(fs.readFileSync(navDataPath, 'utf8'))
|
|
||||||
const remotePluginsPath = path.join(process.cwd(), remotePluginsFile)
|
|
||||||
const remotePlugins = JSON.parse(fs.readFileSync(remotePluginsPath, 'utf-8'))
|
|
||||||
// Resolve plugins, this yields branches with NavLeafRemote nodes
|
|
||||||
const withPlugins = await mergeRemotePlugins(remotePlugins, navData, IS_DEV)
|
|
||||||
// Resolve local filePaths for NavLeaf nodes
|
|
||||||
const withFilePaths = await validateNavData(withPlugins, localContentDir)
|
|
||||||
// Return the nav data with:
|
|
||||||
// 1. Plugins merged, transformed into navData structures with NavLeafRemote nodes
|
|
||||||
// 2. filePaths added to all local NavLeaf nodes
|
|
||||||
return withFilePaths
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { generateStaticPaths, generateStaticProps }
|
export default { generateStaticPaths, generateStaticProps }
|
||||||
export { generateStaticPaths, generateStaticProps }
|
export { generateStaticPaths, generateStaticProps }
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
const fetch = require('isomorphic-unfetch')
|
|
||||||
|
|
||||||
const GITHUB_API_TOKEN = process.env.GITHUB_API_TOKEN
|
|
||||||
|
|
||||||
async function githubQuery(body, token) {
|
|
||||||
const result = await fetch('https://api.github.com/graphql', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `bearer ${token}`,
|
|
||||||
ContentType: 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
})
|
|
||||||
return await result.json()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch a file from GitHub using the GraphQL API
|
|
||||||
async function getGithubFile({ repo, branch, filePath }) {
|
|
||||||
const [repo_owner, repo_name] = repo.split('/')
|
|
||||||
// Set up the GraphQL query
|
|
||||||
// (usually we can keep this in a separate file, and rely on a
|
|
||||||
// plaintext loader we've set up in our NextJS config, but we need
|
|
||||||
// to fetch remote content when indexing it, which happens outside
|
|
||||||
// NextJS, so unfortunately it seems this has to be inlined)
|
|
||||||
const query = `
|
|
||||||
query($repo_name: String!, $repo_owner: String!, $object_expression: String!) {
|
|
||||||
repository(name: $repo_name, owner: $repo_owner) {
|
|
||||||
object(expression: $object_expression) {
|
|
||||||
... on Blob {
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
// Set variables
|
|
||||||
const variables = {
|
|
||||||
repo_name,
|
|
||||||
repo_owner,
|
|
||||||
object_expression: `${branch}:${filePath}`,
|
|
||||||
}
|
|
||||||
// Query the GitHub API, and parse the navigation data
|
|
||||||
const result = await githubQuery({ query, variables }, GITHUB_API_TOKEN)
|
|
||||||
try {
|
|
||||||
const fileText = result.data.repository.object.text
|
|
||||||
return [null, fileText]
|
|
||||||
} catch (e) {
|
|
||||||
const errorMsg = `Could not fetch remote file text from "${
|
|
||||||
variables.object_expression
|
|
||||||
}" in "${repo_owner}/${repo_name}". Received instead:\n\n${JSON.stringify(
|
|
||||||
result,
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
return [errorMsg, null]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function memoize(method) {
|
|
||||||
let cache = {}
|
|
||||||
|
|
||||||
return async function () {
|
|
||||||
let args = JSON.stringify(arguments[0])
|
|
||||||
if (!cache[args]) {
|
|
||||||
cache[args] = method.apply(this, arguments)
|
|
||||||
}
|
|
||||||
return cache[args]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = memoize(getGithubFile)
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
const fetch = require('isomorphic-unfetch')
|
||||||
|
const parseSourceZip = require('./parse-source-zip')
|
||||||
|
const parseDocsZip = require('./parse-docs-zip')
|
||||||
|
|
||||||
|
// Given a repo and tag,
|
||||||
|
//
|
||||||
|
// return [null, docsMdxFiles] if docs files
|
||||||
|
// are successfully fetched and valid,
|
||||||
|
// where docsMdxFiles is an array of { filePath, fileString } items.
|
||||||
|
//
|
||||||
|
// otherwise, return [err, null]
|
||||||
|
// where err is an error message describing whether the
|
||||||
|
// docs files were missing or invalid, with a path to resolution
|
||||||
|
async function fetchDocsFiles({ repo, tag }) {
|
||||||
|
// If there's a docs.zip asset, we'll prefer that
|
||||||
|
const docsZipUrl = `https://github.com/${repo}/releases/download/${tag}/docs.zip`
|
||||||
|
const docsZipResponse = await fetch(docsZipUrl, { method: 'GET' })
|
||||||
|
const hasDocsZip = docsZipResponse.status === 200
|
||||||
|
// Note: early return!
|
||||||
|
if (hasDocsZip) return await parseDocsZip(docsZipResponse)
|
||||||
|
// Else if docs.zip is not present, and we only have the "latest" tag,
|
||||||
|
// then throw an error - we can't resolve the fallback source ZIP
|
||||||
|
// unless we resort to calling the GitHub API, which we do not want to do
|
||||||
|
if (tag === 'latest') {
|
||||||
|
const err = `Failed to fetch. Could not find "docs.zip" at ${docsZipUrl}. To fall back to parsing docs from "source", please provide a specific version tag instead of "${tag}".`
|
||||||
|
return [err, null]
|
||||||
|
}
|
||||||
|
// Else if docs.zip is not present, and we have a specific tag, then
|
||||||
|
// fall back to parsing docs files from the source zip
|
||||||
|
const sourceZipUrl = `https://github.com/${repo}/archive/${tag}.zip`
|
||||||
|
const sourceZipResponse = await fetch(sourceZipUrl, { method: 'GET' })
|
||||||
|
const missingSourceZip = sourceZipResponse.status !== 200
|
||||||
|
if (missingSourceZip) {
|
||||||
|
const err = `Failed to fetch. Could not find "docs.zip" at ${docsZipUrl}, and could not find fallback source ZIP at ${sourceZipUrl}. Please ensure one of these assets is available.`
|
||||||
|
return [err, null]
|
||||||
|
}
|
||||||
|
// Handle parsing from plugin source zip
|
||||||
|
return await parseSourceZip(sourceZipResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPluginDocs({ repo, tag }) {
|
||||||
|
const [err, docsMdxFiles] = await fetchDocsFiles({ repo, tag })
|
||||||
|
if (err) {
|
||||||
|
const errMsg = `Invalid plugin docs ${repo}, on release ${tag}. ${err}`
|
||||||
|
throw new Error(errMsg)
|
||||||
|
}
|
||||||
|
return docsMdxFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
function memoize(method) {
|
||||||
|
let cache = {}
|
||||||
|
return async function () {
|
||||||
|
let args = JSON.stringify(arguments)
|
||||||
|
if (!cache[args]) {
|
||||||
|
cache[args] = method.apply(this, arguments)
|
||||||
|
}
|
||||||
|
return cache[args]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = memoize(fetchPluginDocs)
|
|
@ -1,166 +0,0 @@
|
||||||
const path = require('path')
|
|
||||||
const fetchGithubFile = require('./fetch-github-file')
|
|
||||||
|
|
||||||
const COMPONENT_TYPES = [
|
|
||||||
'builders',
|
|
||||||
'datasources',
|
|
||||||
'post-processors',
|
|
||||||
'provisioners',
|
|
||||||
]
|
|
||||||
|
|
||||||
async function gatherRemotePlugins(pluginsData, navData, isDev = true) {
|
|
||||||
const allPluginData = await Promise.all(
|
|
||||||
pluginsData.map(async (pluginEntry) => {
|
|
||||||
const componentEntries = await Promise.all(
|
|
||||||
COMPONENT_TYPES.map(async (type) => {
|
|
||||||
const routes = await gatherPluginBranch(pluginEntry, type)
|
|
||||||
if (!routes) return false
|
|
||||||
const isSingleLeaf =
|
|
||||||
routes.length === 1 && typeof routes[0].path !== 'undefined'
|
|
||||||
const navData = isSingleLeaf
|
|
||||||
? { ...routes[0], path: path.join(type, pluginEntry.path) }
|
|
||||||
: { title: pluginEntry.title, routes }
|
|
||||||
return { type, navData }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const validComponents = componentEntries.filter(Boolean)
|
|
||||||
if (validComponents.length === 0) {
|
|
||||||
const errMsg = `Could not fetch any component documentation for remote plugin from ${pluginEntry.repo}. This may be a GitHub credential issue at build time, or it may be an issue with missing docs in the source repository. Please ensure you have a valid GITHUB_API_TOKEN set in .env.local at the root of the project.`
|
|
||||||
if (isDev) {
|
|
||||||
console.warn(errMsg)
|
|
||||||
} else {
|
|
||||||
throw new Error(errMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return validComponents
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const allPluginsByType = allPluginData.reduce((acc, pluginData) => {
|
|
||||||
pluginData.forEach((p) => {
|
|
||||||
const { type, navData } = p
|
|
||||||
if (!acc[type]) acc[type] = []
|
|
||||||
acc[type].push(navData)
|
|
||||||
})
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
const navDataWithPlugins = navData.slice().map((n) => {
|
|
||||||
// we only care about top-level NavBranch nodes
|
|
||||||
if (!n.routes) return n
|
|
||||||
// for each component type, check if this NavBranch
|
|
||||||
// is the parent route for that type
|
|
||||||
for (var i = 0; i < COMPONENT_TYPES.length; i++) {
|
|
||||||
const type = COMPONENT_TYPES[i]
|
|
||||||
const isTypeRoute = n.routes.filter((nn) => nn.path === type).length > 0
|
|
||||||
if (isTypeRoute) {
|
|
||||||
const pluginsOfType = allPluginsByType[type]
|
|
||||||
if (!pluginsOfType || pluginsOfType.length == 0) return n
|
|
||||||
// if this NavBranch is the parent route for the type,
|
|
||||||
// then append all remote plugins of this type to the
|
|
||||||
// NavBranch's child routes
|
|
||||||
const routesWithPlugins = n.routes.slice().concat(pluginsOfType)
|
|
||||||
// console.log(JSON.stringify(routesWithPlugins, null, 2))
|
|
||||||
// Also, sort the child routes so the order is alphabetical
|
|
||||||
routesWithPlugins.sort((a, b) => {
|
|
||||||
// (exception: "Overview" comes first)
|
|
||||||
if (a.title == 'Overview') return -1
|
|
||||||
if (b.title === 'Overview') return 1
|
|
||||||
// (exception: "Community-Supported" comes last)
|
|
||||||
if (a.title == 'Community-Supported') return 1
|
|
||||||
if (b.title === 'Community-Supported') return -1
|
|
||||||
// (exception: "Custom" comes second-last)
|
|
||||||
if (a.title == 'Custom') return 1
|
|
||||||
if (b.title === 'Custom') return -1
|
|
||||||
return a.title < b.title ? -1 : a.title > b.title ? 1 : 0
|
|
||||||
})
|
|
||||||
// return n
|
|
||||||
return { ...n, routes: routesWithPlugins }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
})
|
|
||||||
|
|
||||||
return navDataWithPlugins
|
|
||||||
}
|
|
||||||
|
|
||||||
async function gatherPluginBranch(pluginEntry, component) {
|
|
||||||
const artifactDir = pluginEntry.artifactDir || '.docs-artifacts'
|
|
||||||
const branch = pluginEntry.branch || 'main'
|
|
||||||
const navDataFilePath = `${artifactDir}/${component}/nav-data.json`
|
|
||||||
const [err, fileResult] = await fetchGithubFile({
|
|
||||||
repo: pluginEntry.repo,
|
|
||||||
branch,
|
|
||||||
filePath: navDataFilePath,
|
|
||||||
})
|
|
||||||
// If one component errors, that's expected - we try all components.
|
|
||||||
// We'll check one level up to see if ALL components fail.
|
|
||||||
if (err) return false
|
|
||||||
const navData = JSON.parse(fileResult)
|
|
||||||
const withPrefixedPath = await prefixNavDataPath(
|
|
||||||
navData,
|
|
||||||
{
|
|
||||||
repo: pluginEntry.repo,
|
|
||||||
branch,
|
|
||||||
componentArtifactsDir: path.join('.docs-artifacts', component),
|
|
||||||
},
|
|
||||||
path.join(component, pluginEntry.path)
|
|
||||||
)
|
|
||||||
// Add plugin tier
|
|
||||||
// Parse the plugin tier
|
|
||||||
const pluginOwner = pluginEntry.repo.split('/')[0]
|
|
||||||
const pluginTier = pluginOwner === 'hashicorp' ? 'official' : 'community'
|
|
||||||
const withPluginTier = addPluginTier(withPrefixedPath, pluginTier)
|
|
||||||
// Return the augmented navData
|
|
||||||
return withPluginTier
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPluginTier(navData, pluginTier) {
|
|
||||||
return navData.slice().map((navNode) => {
|
|
||||||
if (typeof navNode.path !== 'undefined') {
|
|
||||||
return { ...navNode, pluginTier }
|
|
||||||
}
|
|
||||||
if (navNode.routes) {
|
|
||||||
return { ...navNode, routes: addPluginTier(navNode.routes, pluginTier) }
|
|
||||||
}
|
|
||||||
return navNode
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function prefixNavDataPath(
|
|
||||||
navData,
|
|
||||||
{ repo, branch, componentArtifactsDir },
|
|
||||||
parentPath
|
|
||||||
) {
|
|
||||||
return await Promise.all(
|
|
||||||
navData.slice().map(async (navNode) => {
|
|
||||||
if (typeof navNode.path !== 'undefined') {
|
|
||||||
const prefixedPath = path.join(parentPath, navNode.path)
|
|
||||||
const remoteFile = {
|
|
||||||
repo,
|
|
||||||
branch,
|
|
||||||
filePath: path.join(componentArtifactsDir, navNode.filePath),
|
|
||||||
}
|
|
||||||
const withPrefixedRoute = {
|
|
||||||
...navNode,
|
|
||||||
path: prefixedPath,
|
|
||||||
remoteFile: remoteFile,
|
|
||||||
}
|
|
||||||
delete withPrefixedRoute.filePath
|
|
||||||
return withPrefixedRoute
|
|
||||||
}
|
|
||||||
if (navNode.routes) {
|
|
||||||
const prefixedRoutes = await prefixNavDataPath(
|
|
||||||
navNode.routes,
|
|
||||||
{ repo, branch, componentArtifactsDir },
|
|
||||||
parentPath
|
|
||||||
)
|
|
||||||
const withPrefixedRoutes = { ...navNode, routes: prefixedRoutes }
|
|
||||||
return withPrefixedRoutes
|
|
||||||
}
|
|
||||||
return navNode
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = gatherRemotePlugins
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
const path = require('path')
|
||||||
|
const AdmZip = require('adm-zip')
|
||||||
|
const validatePluginDocsFiles = require('./validate-plugin-docs-files')
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
NOTE: used for default `docs.zip` release assets
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Given a response from fetching a docs.zip file,
|
||||||
|
// which is a compressed "docs" folder,
|
||||||
|
//
|
||||||
|
// return [null, docsMdxFiles] if docs files
|
||||||
|
// are successfully fetched and valid,
|
||||||
|
// where docsMdxFiles is an array of { filePath, fileString } items.
|
||||||
|
//
|
||||||
|
// otherwise, return [err, null]
|
||||||
|
// where err is an error message describing whether the
|
||||||
|
// docs files were missing or invalid, with a path to resolution
|
||||||
|
async function parseDocsZip(response) {
|
||||||
|
// the file path from the repo root is the same as the zip entryName,
|
||||||
|
// which includes the docs directory as the first part of the path
|
||||||
|
const responseBuffer = Buffer.from(await response.arrayBuffer())
|
||||||
|
const responseZip = new AdmZip(responseBuffer)
|
||||||
|
const docsEntries = responseZip.getEntries()
|
||||||
|
// Validate the file paths within the "docs" folder
|
||||||
|
const docsFilePaths = docsEntries.map((e) => e.entryName)
|
||||||
|
const validationError = validatePluginDocsFiles(docsFilePaths)
|
||||||
|
if (validationError) return [validationError, null]
|
||||||
|
// If valid, filter for MDX files only, and return
|
||||||
|
// a { filePath, fileString } object for each mdx file
|
||||||
|
const docsMdxFiles = docsEntries
|
||||||
|
.filter((e) => {
|
||||||
|
return path.extname(e.entryName) === '.mdx'
|
||||||
|
})
|
||||||
|
.map((e) => {
|
||||||
|
const filePath = e.entryName
|
||||||
|
const fileString = e.getData().toString()
|
||||||
|
return { filePath, fileString }
|
||||||
|
})
|
||||||
|
return [null, docsMdxFiles]
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
const dirs = path.dirname(e.entryName).split('/')
|
||||||
|
const pathFromDocsDir = dirs.slice(1).join('/')
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = parseDocsZip
|
|
@ -0,0 +1,56 @@
|
||||||
|
const path = require('path')
|
||||||
|
const AdmZip = require('adm-zip')
|
||||||
|
const validatePluginDocsFiles = require('./validate-plugin-docs-files')
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
NOTE: used for fallback approach, where we parse from
|
||||||
|
the full release archive
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Given a response from fetching a source .zip file,
|
||||||
|
// which contains a "docs" folder,
|
||||||
|
//
|
||||||
|
// return [null, docsMdxFiles] if docs files
|
||||||
|
// are successfully fetched and valid,
|
||||||
|
// where docsMdxFiles is an array of { filePath, fileString } items.
|
||||||
|
//
|
||||||
|
// otherwise, return [err, null]
|
||||||
|
// where err is an error message describing whether the
|
||||||
|
// docs files were missing or invalid, with a path to resolution
|
||||||
|
async function parseSourceZip(response) {
|
||||||
|
const responseBuffer = Buffer.from(await response.arrayBuffer())
|
||||||
|
const responseZip = new AdmZip(responseBuffer)
|
||||||
|
const sourceEntries = responseZip.getEntries()
|
||||||
|
const docsEntries = sourceEntries.filter((entry) => {
|
||||||
|
// filter for zip entries in the docs subfolder only
|
||||||
|
const dirs = path.dirname(entry.entryName).split('/')
|
||||||
|
return dirs.length > 1 && dirs[1] === 'docs'
|
||||||
|
})
|
||||||
|
// Validate the file paths within the "docs" folder
|
||||||
|
const docsFilePaths = docsEntries.map((e) => {
|
||||||
|
// We need to remove the leading directory,
|
||||||
|
// which will be something like packer-plugin-docs-0.0.5
|
||||||
|
const filePath = e.entryName.split('/').slice(1).join('/')
|
||||||
|
return filePath
|
||||||
|
})
|
||||||
|
const validationError = validatePluginDocsFiles(docsFilePaths)
|
||||||
|
if (validationError) return [validationError, null]
|
||||||
|
// If valid, filter for MDX files only, and return
|
||||||
|
// a { filePath, fileString } object for each mdx file
|
||||||
|
const docsMdxFiles = docsEntries
|
||||||
|
.filter((e) => {
|
||||||
|
return path.extname(e.entryName) === '.mdx'
|
||||||
|
})
|
||||||
|
.map((e) => {
|
||||||
|
// We need to remove the leading directory,
|
||||||
|
// which will be something like packer-plugin-docs-0.0.5
|
||||||
|
const filePath = e.entryName.split('/').slice(1).join('/')
|
||||||
|
const fileString = e.getData().toString()
|
||||||
|
return { filePath, fileString }
|
||||||
|
})
|
||||||
|
return [null, docsMdxFiles]
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = parseSourceZip
|
|
@ -0,0 +1,218 @@
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const grayMatter = require('gray-matter')
|
||||||
|
const fetchPluginDocs = require('./fetch-plugin-docs')
|
||||||
|
const validateFilePaths = require('@hashicorp/react-docs-sidenav/utils/validate-file-paths')
|
||||||
|
const validateRouteStructure = require('@hashicorp/react-docs-sidenav/utils/validate-route-structure')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves nav-data from file, including optional
|
||||||
|
* resolution of remote plugin docs entries
|
||||||
|
*
|
||||||
|
* @param {string} navDataFile path to the nav-data.json file, relative to the cwd. Example: "data/docs-nav-data.json".
|
||||||
|
* @param {string} localContentDir path to the content root, relative to the cwd. Example: "content/docs".
|
||||||
|
* @param {object} options optional configuration object
|
||||||
|
* @param {string} options.remotePluginsFile path to a remote-plugins.json file, relative to the cwd. Example: "data/docs-remote-plugins.json".
|
||||||
|
* @returns {array} the resolved navData. This includes NavBranch nodes pulled from remote plugin repositories, as well as filePath properties on all local NavLeaf nodes, and remoteFile properties on all NavLeafRemote nodes.
|
||||||
|
*/
|
||||||
|
async function resolveNavData(navDataFile, localContentDir, options = {}) {
|
||||||
|
const { remotePluginsFile } = options
|
||||||
|
// Read in files
|
||||||
|
const navDataPath = path.join(process.cwd(), navDataFile)
|
||||||
|
const navData = JSON.parse(fs.readFileSync(navDataPath, 'utf8'))
|
||||||
|
// Fetch remote plugin docs, if applicable
|
||||||
|
let withPlugins = navData
|
||||||
|
if (remotePluginsFile) {
|
||||||
|
// Resolve plugins, this yields branches with NavLeafRemote nodes
|
||||||
|
withPlugins = await mergeRemotePlugins(remotePluginsFile, navData)
|
||||||
|
}
|
||||||
|
// Resolve local filePaths for NavLeaf nodes
|
||||||
|
const withFilePaths = await validateFilePaths(withPlugins, localContentDir)
|
||||||
|
validateRouteStructure(withFilePaths)
|
||||||
|
// Return the nav data with:
|
||||||
|
// 1. Plugins merged, transformed into navData structures with NavLeafRemote nodes
|
||||||
|
// 2. filePaths added to all local NavLeaf nodes
|
||||||
|
return withFilePaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a remote plugins config file, and the full tree of docs navData which
|
||||||
|
// contains top-level branch routes that match plugin component types,
|
||||||
|
// fetch and parse all remote plugin docs, merge them into the
|
||||||
|
// broader tree of docs navData, and return the docs navData
|
||||||
|
// with the merged plugin docs
|
||||||
|
async function mergeRemotePlugins(remotePluginsFile, navData) {
|
||||||
|
// Read in and parse the plugin configuration JSON
|
||||||
|
const remotePluginsPath = path.join(process.cwd(), remotePluginsFile)
|
||||||
|
const pluginEntries = JSON.parse(fs.readFileSync(remotePluginsPath, 'utf-8'))
|
||||||
|
// Add navData for each plugin's component.
|
||||||
|
// Note that leaf nodes include a remoteFile property object with the full MDX fileString
|
||||||
|
const pluginEntriesWithDocs = await Promise.all(
|
||||||
|
pluginEntries.map(resolvePluginEntryDocs)
|
||||||
|
)
|
||||||
|
// group navData by component type, to prepare to merge plugin docs
|
||||||
|
// into the broader tree of navData.
|
||||||
|
const pluginDocsByComponent = pluginEntriesWithDocs.reduce(
|
||||||
|
(acc, pluginEntry) => {
|
||||||
|
const { components } = pluginEntry
|
||||||
|
Object.keys(components).forEach((type) => {
|
||||||
|
const navData = components[type]
|
||||||
|
if (!navData) return
|
||||||
|
if (!acc[type]) acc[type] = []
|
||||||
|
acc[type].push(navData[0])
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
// merge plugin docs, by plugin component type,
|
||||||
|
// into the corresponding top-level component NavBranch
|
||||||
|
const navDataWithPlugins = navData.slice().map((n) => {
|
||||||
|
// we only care about top-level NavBranch nodes
|
||||||
|
if (!n.routes) return n
|
||||||
|
// for each component type, check if this NavBranch
|
||||||
|
// is the parent route for that type
|
||||||
|
const componentTypes = Object.keys(pluginDocsByComponent)
|
||||||
|
let typeMatch = false
|
||||||
|
for (var i = 0; i < componentTypes.length; i++) {
|
||||||
|
const componentType = componentTypes[i]
|
||||||
|
const routeMatches = n.routes.filter((r) => r.path === componentType)
|
||||||
|
if (routeMatches.length > 0) {
|
||||||
|
typeMatch = componentType
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if this NavBranch does not match a component type slug,
|
||||||
|
// then return it unmodified
|
||||||
|
if (!typeMatch) return n
|
||||||
|
// if there are no matching remote plugin components,
|
||||||
|
// then return the navBranch unmodified
|
||||||
|
const pluginsOfType = pluginDocsByComponent[typeMatch]
|
||||||
|
if (!pluginsOfType || pluginsOfType.length == 0) return n
|
||||||
|
// if this NavBranch is the parent route for the type,
|
||||||
|
// then append all remote plugins of this type to the
|
||||||
|
// NavBranch's child routes
|
||||||
|
const routesWithPlugins = n.routes.slice().concat(pluginsOfType)
|
||||||
|
// console.log(JSON.stringify(routesWithPlugins, null, 2))
|
||||||
|
// Also, sort the child routes so the order is alphabetical
|
||||||
|
routesWithPlugins.sort((a, b) => {
|
||||||
|
// (exception: "Overview" comes first)
|
||||||
|
if (a.title == 'Overview') return -1
|
||||||
|
if (b.title === 'Overview') return 1
|
||||||
|
// (exception: "Community-Supported" comes last)
|
||||||
|
if (a.title == 'Community-Supported') return 1
|
||||||
|
if (b.title === 'Community-Supported') return -1
|
||||||
|
// (exception: "Custom" comes second-last)
|
||||||
|
if (a.title == 'Custom') return 1
|
||||||
|
if (b.title === 'Custom') return -1
|
||||||
|
return a.title < b.title ? -1 : a.title > b.title ? 1 : 0
|
||||||
|
})
|
||||||
|
// return n
|
||||||
|
return { ...n, routes: routesWithPlugins }
|
||||||
|
})
|
||||||
|
// return the merged navData, which now includes special NavLeaf nodes
|
||||||
|
// for plugin docs with { filePath, fileString } remoteFile properties
|
||||||
|
return navDataWithPlugins
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch remote plugin docs .mdx files, and
|
||||||
|
// transform each plugin's array of .mdx files into navData.
|
||||||
|
// Organize this navData by component, add it to the plugin config entry,
|
||||||
|
// and return the modified entry.
|
||||||
|
//
|
||||||
|
// Note that navData leaf nodes have a special remoteFile property,
|
||||||
|
// which contains { filePath, fileString } data for the remote
|
||||||
|
// plugin doc .mdx file
|
||||||
|
async function resolvePluginEntryDocs(pluginConfigEntry) {
|
||||||
|
const { title, path: slug, repo, version } = pluginConfigEntry
|
||||||
|
const docsMdxFiles = await fetchPluginDocs({ repo, tag: version })
|
||||||
|
// We construct a special kind of "NavLeaf" node, with a remoteFile property,
|
||||||
|
// consisting of a { filePath, fileString, sourceUrl }, where:
|
||||||
|
// - filePath is the path to the source file in the source repo
|
||||||
|
// - fileString is a string representing the file source
|
||||||
|
// - sourceUrl is a link to the original file in the source repo
|
||||||
|
// We also add a pluginTier attribute
|
||||||
|
const navNodes = docsMdxFiles.map((mdxFile) => {
|
||||||
|
const { filePath, fileString } = mdxFile
|
||||||
|
// Process into a NavLeaf, with a remoteFile attribute
|
||||||
|
const dirs = path.dirname(filePath).split('/')
|
||||||
|
const dirUrl = dirs.slice(2).join('/')
|
||||||
|
const basename = path.basename(filePath)
|
||||||
|
// build urlPath
|
||||||
|
// note that this will be prefixed to get to our final path
|
||||||
|
const isIndexFile = basename === 'index'
|
||||||
|
const urlPath = isIndexFile ? dirUrl : path.join(dirUrl, basename)
|
||||||
|
// parse title, either from frontmatter or file name
|
||||||
|
const { data: frontmatter } = grayMatter(fileString)
|
||||||
|
const { nav_title, sidebar_title } = frontmatter
|
||||||
|
const title = nav_title || sidebar_title || basename
|
||||||
|
// construct sourceUrl (used for "Edit this page" link)
|
||||||
|
const sourceUrl = `https://github.com/${repo}/blob/${version}/${filePath}`
|
||||||
|
// determine pluginTier
|
||||||
|
const pluginOwner = repo.split('/')[0]
|
||||||
|
const pluginTier = pluginOwner === 'hashicorp' ? 'official' : 'community'
|
||||||
|
// Construct and return a NavLeafRemote node
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
path: urlPath,
|
||||||
|
remoteFile: { filePath, fileString, sourceUrl },
|
||||||
|
pluginTier,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
//
|
||||||
|
const navNodesByComponent = navNodes.reduce((acc, navLeaf) => {
|
||||||
|
const componentType = navLeaf.remoteFile.filePath.split('/')[1]
|
||||||
|
if (!acc[componentType]) acc[componentType] = []
|
||||||
|
acc[componentType].push(navLeaf)
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
//
|
||||||
|
const components = Object.keys(navNodesByComponent).map((type) => {
|
||||||
|
// Plugins many not contain every component type,
|
||||||
|
// we return null if this is the case
|
||||||
|
const rawNavNodes = navNodesByComponent[type]
|
||||||
|
if (!rawNavNodes) return null
|
||||||
|
// Avoid unnecessary nesting if there's only a single doc file
|
||||||
|
const navData = normalizeNavNodes(title, rawNavNodes)
|
||||||
|
// Prefix paths to fit into broader docs nav-data
|
||||||
|
const pathPrefix = path.join(type, slug)
|
||||||
|
const withPrefixedPaths = visitNavLeaves(navData, (n) => {
|
||||||
|
const prefixedPath = path.join(pathPrefix, n.path)
|
||||||
|
return { ...n, path: prefixedPath }
|
||||||
|
})
|
||||||
|
//
|
||||||
|
return { type, navData: withPrefixedPaths }
|
||||||
|
})
|
||||||
|
const componentsObj = components.reduce((acc, component) => {
|
||||||
|
if (!component) return acc
|
||||||
|
acc[component.type] = component.navData
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
return { ...pluginConfigEntry, components: componentsObj }
|
||||||
|
}
|
||||||
|
|
||||||
|
// For components with a single doc file, transform so that
|
||||||
|
// a single leaf node renders, rather than a nav branch
|
||||||
|
function normalizeNavNodes(pluginName, routes) {
|
||||||
|
const isSingleLeaf =
|
||||||
|
routes.length === 1 && typeof routes[0].path !== 'undefined'
|
||||||
|
const navData = isSingleLeaf
|
||||||
|
? [{ ...routes[0], path: '' }]
|
||||||
|
: [{ title: pluginName, routes }]
|
||||||
|
return navData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse a clone of the given navData,
|
||||||
|
// modifying any NavLeaf nodes with the provided visitFn
|
||||||
|
function visitNavLeaves(navData, visitFn) {
|
||||||
|
return navData.slice().map((navNode) => {
|
||||||
|
if (typeof navNode.path !== 'undefined') {
|
||||||
|
return visitFn(navNode)
|
||||||
|
}
|
||||||
|
if (navNode.routes) {
|
||||||
|
return { ...navNode, routes: visitNavLeaves(navNode.routes, visitFn) }
|
||||||
|
}
|
||||||
|
return navNode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = resolveNavData
|
|
@ -0,0 +1,46 @@
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const COMPONENT_TYPES = [
|
||||||
|
'builders',
|
||||||
|
'datasources',
|
||||||
|
'post-processors',
|
||||||
|
'provisioners',
|
||||||
|
]
|
||||||
|
|
||||||
|
// Given an array of file paths within the "docs" folder,
|
||||||
|
// validate that no unexpected files are being included,
|
||||||
|
// and that there is at least one component subfolder
|
||||||
|
// with at least one .mdx file within it.
|
||||||
|
function validatePluginDocsFiles(filePaths) {
|
||||||
|
function isValidPath(filePath) {
|
||||||
|
const isDocsRoot = filePath === 'docs/'
|
||||||
|
const isComponentRoot = COMPONENT_TYPES.reduce((acc, type) => {
|
||||||
|
return acc || filePath === `docs/${type}/`
|
||||||
|
}, false)
|
||||||
|
const isComponentMdx = COMPONENT_TYPES.reduce((acc, type) => {
|
||||||
|
const mdxPathRegex = new RegExp(`^docs/${type}/(.*).mdx$`)
|
||||||
|
return acc || mdxPathRegex.test(filePath)
|
||||||
|
}, false)
|
||||||
|
const isValidPath = isDocsRoot || isComponentRoot || isComponentMdx
|
||||||
|
return isValidPath
|
||||||
|
}
|
||||||
|
const invalidPaths = filePaths.filter((f) => !isValidPath(f))
|
||||||
|
if (invalidPaths.length > 0) {
|
||||||
|
return `Found invalid files or folders in the docs directory: ${JSON.stringify(
|
||||||
|
invalidPaths
|
||||||
|
)}. Please ensure the docs folder contains only component subfolders and .mdx files within those subfolders. Valid component types are: ${JSON.stringify(
|
||||||
|
COMPONENT_TYPES
|
||||||
|
)}.`
|
||||||
|
}
|
||||||
|
const validPaths = filePaths.filter(isValidPath)
|
||||||
|
const mdxFiles = validPaths.filter((fp) => path.extname(fp) === '.mdx')
|
||||||
|
const isMissingDocs = mdxFiles.length == 0
|
||||||
|
if (isMissingDocs) {
|
||||||
|
return `Could not find valid .mdx files. Please ensure there is at least one component subfolder in the docs directory, which contains at least one .mdx file. Valid component types are: ${JSON.stringify(
|
||||||
|
COMPONENT_TYPES
|
||||||
|
)}.`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = validatePluginDocsFiles
|
|
@ -2,6 +2,7 @@
|
||||||
{
|
{
|
||||||
"title": "Docker",
|
"title": "Docker",
|
||||||
"path": "docker",
|
"path": "docker",
|
||||||
"repo": "hashicorp/packer-plugin-docker"
|
"repo": "hashicorp/packer-plugin-docker",
|
||||||
|
"version": "v0.0.4"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,118 +5,118 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/cache-browser-local-storage": {
|
"@algolia/cache-browser-local-storage": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.8.6.tgz",
|
||||||
"integrity": "sha512-Cwc03hikHSUI+xvgUdN+H+f6jFyoDsC9fegzXzJ2nPn1YSN9EXzDMBnbrgl0sbl9iLGXe0EIGMYqR2giCv1wMQ==",
|
"integrity": "sha512-Bam7otzjIEgrRXWmk0Amm1+B3ROI5dQnUfJEBjIy0YPM0kMahEoJXCw6160tGKxJLl1g6icoC953nGshQKO7cA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/cache-common": "4.8.3"
|
"@algolia/cache-common": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/cache-common": {
|
"@algolia/cache-common": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.8.6.tgz",
|
||||||
"integrity": "sha512-Cf7zZ2i6H+tLSBTkFePHhYvlgc9fnMPKsF9qTmiU38kFIGORy/TN2Fx5n1GBuRLIzaSXvcf+oHv1HvU0u1gE1g=="
|
"integrity": "sha512-eGQlsXU5G7n4RvV/K6qe6lRAeL6EKAYPT3yZDBjCW4pAh7JWta+77a7BwUQkTqXN1MEQWZXjex3E4z/vFpzNrg=="
|
||||||
},
|
},
|
||||||
"@algolia/cache-in-memory": {
|
"@algolia/cache-in-memory": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.8.6.tgz",
|
||||||
"integrity": "sha512-+N7tkvmijXiDy2E7u1mM73AGEgGPWFmEmPeJS96oT46I98KXAwVPNYbcAqBE79YlixdXpkYJk41cFcORzNh+Iw==",
|
"integrity": "sha512-kbJrvCFANxL/l5Pq1NFyHLRphKDwmqcD/OJga0IbNKEulRGDPkt1+pC7/q8d2ikP12adBjLLg2CVias9RJpIaw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/cache-common": "4.8.3"
|
"@algolia/cache-common": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/client-account": {
|
"@algolia/client-account": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.8.6.tgz",
|
||||||
"integrity": "sha512-Uku8LqnXBwfDCtsTCDYTUOz2/2oqcAQCKgaO0uGdIR8DTQENBXFQvzziambHdn9KuFuY+6Et9k1+cjpTPBDTBg==",
|
"integrity": "sha512-FQVJE/BgCb78jtG7V0r30sMl9P5JKsrsOacGtGF2YebqI0YF25y8Z1nO39lbdjahxUS3QkDw2d0P2EVMj65g2Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/client-common": "4.8.3",
|
"@algolia/client-common": "4.8.6",
|
||||||
"@algolia/client-search": "4.8.3",
|
"@algolia/client-search": "4.8.6",
|
||||||
"@algolia/transporter": "4.8.3"
|
"@algolia/transporter": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/client-analytics": {
|
"@algolia/client-analytics": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.8.6.tgz",
|
||||||
"integrity": "sha512-9ensIWmjYJprZ+YjAVSZdWUG05xEnbytENXp508X59tf34IMIX8BR2xl0RjAQODtxBdAteGxuKt5THX6U9tQLA==",
|
"integrity": "sha512-ZBYFUlzNaWDFtt0rYHI7xbfVX0lPWU9lcEEXI/BlnkRgEkm247H503tNatPQFA1YGkob52EU18sV1eJ+OFRBLA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/client-common": "4.8.3",
|
"@algolia/client-common": "4.8.6",
|
||||||
"@algolia/client-search": "4.8.3",
|
"@algolia/client-search": "4.8.6",
|
||||||
"@algolia/requester-common": "4.8.3",
|
"@algolia/requester-common": "4.8.6",
|
||||||
"@algolia/transporter": "4.8.3"
|
"@algolia/transporter": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/client-common": {
|
"@algolia/client-common": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.8.6.tgz",
|
||||||
"integrity": "sha512-TU3623AEFAWUQlDTznkgAMSYo8lfS9pNs5QYDQzkvzWdqK0GBDWthwdRfo9iIsfxiR9qdCMHqwEu+AlZMVhNSA==",
|
"integrity": "sha512-8dI+K3Nvbes2YRZm2LY7bdCUD05e60BhacrMLxFuKxnBGuNehME1wbxq/QxcG1iNFJlxLIze5TxIcNN3+pn76g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/requester-common": "4.8.3",
|
"@algolia/requester-common": "4.8.6",
|
||||||
"@algolia/transporter": "4.8.3"
|
"@algolia/transporter": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/client-recommendation": {
|
"@algolia/client-recommendation": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.8.6.tgz",
|
||||||
"integrity": "sha512-qysGbmkcc6Agt29E38KWJq9JuxjGsyEYoKuX9K+P5HyQh08yR/BlRYrA8mB7vT/OIUHRGFToGO6Vq/rcg0NIOQ==",
|
"integrity": "sha512-Kg8DpjwvaWWujNx6sAUrSL+NTHxFe/UNaliCcSKaMhd3+FiPXN+CrSkO0KWR7I+oK2qGBTG/2Y0BhFOJ5/B/RA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/client-common": "4.8.3",
|
"@algolia/client-common": "4.8.6",
|
||||||
"@algolia/requester-common": "4.8.3",
|
"@algolia/requester-common": "4.8.6",
|
||||||
"@algolia/transporter": "4.8.3"
|
"@algolia/transporter": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/client-search": {
|
"@algolia/client-search": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.8.6.tgz",
|
||||||
"integrity": "sha512-rAnvoy3GAhbzOQVniFcKVn1eM2NX77LearzYNCbtFrFYavG+hJI187bNVmajToiuGZ10FfJvK99X2OB1AzzezQ==",
|
"integrity": "sha512-vXLS6umL/9G3bwqc6pkrS9K5/s8coq55mpfRARL+bs0NsToOf77WSTdwzlxv/KdbVF7dHjXgUpBvJ6RyR4ZdAw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/client-common": "4.8.3",
|
"@algolia/client-common": "4.8.6",
|
||||||
"@algolia/requester-common": "4.8.3",
|
"@algolia/requester-common": "4.8.6",
|
||||||
"@algolia/transporter": "4.8.3"
|
"@algolia/transporter": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/logger-common": {
|
"@algolia/logger-common": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.8.6.tgz",
|
||||||
"integrity": "sha512-03wksHRbhl2DouEKnqWuUb64s1lV6kDAAabMCQ2Du1fb8X/WhDmxHC4UXMzypeOGlH5BZBsgVwSB7vsZLP3MZg=="
|
"integrity": "sha512-FMRxZGdDxSzd0/Mv0R1021FvUt0CcbsQLYeyckvSWX8w+Uk4o0lcV6UtZdERVR5XZsGOqoXLMIYDbR2vkbGbVw=="
|
||||||
},
|
},
|
||||||
"@algolia/logger-console": {
|
"@algolia/logger-console": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.8.6.tgz",
|
||||||
"integrity": "sha512-Npt+hI4UF8t3TLMluL5utr9Gc11BjL5kDnGZOhDOAz5jYiSO2nrHMFmnpLT4Cy/u7a5t7EB5dlypuC4/AGStkA==",
|
"integrity": "sha512-TYw9lwUCjvApC6Z0zn36T6gkCl7hbfJmnU+Z/D8pFJ3Yp7lz06S3oWGjbdrULrYP1w1VOhjd0X7/yGNsMhzutQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/logger-common": "4.8.3"
|
"@algolia/logger-common": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/requester-browser-xhr": {
|
"@algolia/requester-browser-xhr": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.8.6.tgz",
|
||||||
"integrity": "sha512-/LTTIpgEmEwkyhn8yXxDdBWqXqzlgw5w2PtTpIwkSlP2/jDwdR/9w1TkFzhNbJ81ki6LAEQM5mSwoTTnbIIecg==",
|
"integrity": "sha512-omh6uJ3CJXOmcrU9M3/KfGg8XkUuGJGIMkqEbkFvIebpBJxfs6TVs0ziNeMFAcAfhi8/CGgpLbDSgJtWdGQa6w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/requester-common": "4.8.3"
|
"@algolia/requester-common": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/requester-common": {
|
"@algolia/requester-common": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.8.6.tgz",
|
||||||
"integrity": "sha512-+Yo9vBkofoKR1SCqqtMnmnfq9yt/BiaDewY/6bYSMNxSYCnu2Fw1JKSIaf/4zos09PMSsxGpLohZwGas3+0GDQ=="
|
"integrity": "sha512-r5xJqq/D9KACkI5DgRbrysVL5DUUagikpciH0k0zjBbm+cXiYfpmdflo/h6JnY6kmvWgjr/4DoeTjKYb/0deAQ=="
|
||||||
},
|
},
|
||||||
"@algolia/requester-node-http": {
|
"@algolia/requester-node-http": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.8.6.tgz",
|
||||||
"integrity": "sha512-k2fiKIeMIFqgC01FnzII6kqC2GQBAfbNaUX4k7QCPa6P8t4sp2xE6fImOUiztLnnL3C9X9ZX6Fw3L+cudi7jvQ==",
|
"integrity": "sha512-TB36OqTVOKyHCOtdxhn/IJyI/NXi/BWy8IEbsiWwwZWlL79NWHbetj49jXWFolEYEuu8PgDjjZGpRhypSuO9XQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/requester-common": "4.8.3"
|
"@algolia/requester-common": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@algolia/transporter": {
|
"@algolia/transporter": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.8.6.tgz",
|
||||||
"integrity": "sha512-nU7fy2iU8snxATlsks0MjMyv97QJWQmOVwTjDc+KZ4+nue8CLcgm4LA4dsTBqvxeCQIoEtt3n72GwXcaqiJSjQ==",
|
"integrity": "sha512-NRb31J0TP7EPoVMpXZ4yAtr61d26R8KGaf6qdULknvq5sOVHuuH4PwmF08386ERfIsgnM/OBhl+uzwACdCIjSg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/cache-common": "4.8.3",
|
"@algolia/cache-common": "4.8.6",
|
||||||
"@algolia/logger-common": "4.8.3",
|
"@algolia/logger-common": "4.8.6",
|
||||||
"@algolia/requester-common": "4.8.3"
|
"@algolia/requester-common": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ampproject/toolbox-core": {
|
"@ampproject/toolbox-core": {
|
||||||
|
@ -2854,12 +2854,12 @@
|
||||||
"integrity": "sha512-AYIe6tcOxlKPe5Sq89o/Vk0rGE6Z1dCzf+N3ynECTh5L2A1zusf9xeM659QEh/edE/Ll9EBBLmq49sQXLNDxTw=="
|
"integrity": "sha512-AYIe6tcOxlKPe5Sq89o/Vk0rGE6Z1dCzf+N3ynECTh5L2A1zusf9xeM659QEh/edE/Ll9EBBLmq49sQXLNDxTw=="
|
||||||
},
|
},
|
||||||
"@hashicorp/react-docs-page": {
|
"@hashicorp/react-docs-page": {
|
||||||
"version": "10.9.4-alpha.18",
|
"version": "11.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-docs-page/-/react-docs-page-10.9.4-alpha.18.tgz",
|
"resolved": "https://registry.npmjs.org/@hashicorp/react-docs-page/-/react-docs-page-11.0.1.tgz",
|
||||||
"integrity": "sha512-+eRKJ2PX9s4Is0ZT2O8ZBcBWuDt7OGxwBrqKF1ulo/DcZunj7pODCQQulb+jAtQyq7YzikWdFmQ/pcvwaVHK6Q==",
|
"integrity": "sha512-BZ746Qm97OQTyMPI7calYDb+LAQQZGTn/vZ8FaMXbVCF+X9Bvs1xYyWRDV6gV0mtgmlkCwYqIrmqneOZdd6PcA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@hashicorp/react-content": "^6.3.0",
|
"@hashicorp/react-content": "^6.3.0",
|
||||||
"@hashicorp/react-docs-sidenav": "6.1.1-alpha.16",
|
"@hashicorp/react-docs-sidenav": "^7.0.0",
|
||||||
"@hashicorp/react-head": "^1.2.0",
|
"@hashicorp/react-head": "^1.2.0",
|
||||||
"@hashicorp/react-search": "^4.1.0",
|
"@hashicorp/react-search": "^4.1.0",
|
||||||
"fs-exists-sync": "0.1.0",
|
"fs-exists-sync": "0.1.0",
|
||||||
|
@ -2870,121 +2870,6 @@
|
||||||
"readdirp": "3.5.0"
|
"readdirp": "3.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/cache-browser-local-storage": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-9rs/Yi82ilgifweJamOy4DlJ4xPGsCN/zg+RKy4vjytNhOrkEHLRQC8vPZ3OhD8KVlw9lRQIZTlgjgFl8iMeeA==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/cache-common": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/cache-common": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-4SvRWnagKtwBFAy8Rsfmv0/Uk53fZL+6dy2idwdx6SjMGKSs0y1Qv+thb4h/k/H5MONisAoT9C2rgZ/mqwh5yw=="
|
|
||||||
},
|
|
||||||
"@algolia/cache-in-memory": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-XBBfqs28FbjwLboY3sxvuzBgYsuXdFsj2mUvkgxfb0GVEzwW4I0NM7KzSPwT+iht55WS1PgIOnynjmhPsrubCw==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/cache-common": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/client-account": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-DjXMpeCdY4J4IDBfowiG6Xl9ec/FhG1NpPQM0Uv4xXsc/TeeZ1JgbgNDhWe9jW0jBEALy+a/RmPrZ0vsxcadsg==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/client-common": "4.8.5",
|
|
||||||
"@algolia/client-search": "4.8.5",
|
|
||||||
"@algolia/transporter": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/client-analytics": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-PQEY+chbHmZnRJdaWsvUYzDpEPr60az0EPUexdouvXGZId15/SnDaXjnf89F7tYmCzkHdUtG4bSvPzAupQ4AFA==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/client-common": "4.8.5",
|
|
||||||
"@algolia/client-search": "4.8.5",
|
|
||||||
"@algolia/requester-common": "4.8.5",
|
|
||||||
"@algolia/transporter": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/client-common": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-Dn8vog2VrGsJeOcBMcSAEIjBtPyogzUBGlh1DtVd0m8GN6q+cImCESl6DY846M2PTYWsLBKBksq37eUfSe9FxQ==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/requester-common": "4.8.5",
|
|
||||||
"@algolia/transporter": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/client-recommendation": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-recommendation/-/client-recommendation-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-ffawCC1C25rCa8/JU2niRZgwr8aV9b2qsLVMo73GXFzi2lceXPAe9K68mt/BGHU+w7PFUwVHsV2VmB+G/HQRVw==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/client-common": "4.8.5",
|
|
||||||
"@algolia/requester-common": "4.8.5",
|
|
||||||
"@algolia/transporter": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/client-search": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-Ru2MljGZWrSQ0CVsDla11oGEPL/RinmVkLJfBtQ+/pk1868VfpAQFGKtOS/b8/xLrMA0Vm4EfC3Mgclk/p3KJA==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/client-common": "4.8.5",
|
|
||||||
"@algolia/requester-common": "4.8.5",
|
|
||||||
"@algolia/transporter": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/logger-common": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-PS6NS6bpED0rAxgCPGhjZJg9why0PnoVEE7ZoCbPq6lsAOc6FPlQLri4OiLyU7wx8RWDoVtOadyzulqAAsfPSQ=="
|
|
||||||
},
|
|
||||||
"@algolia/logger-console": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-3+4gLSbwzuGmrb5go3IZNcFIYVMSbB4c8UMtWEJ/gDBtgGZIvT6f/KlvVSOHIhthSxaM3Y13V6Qile/SpGqc6A==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/logger-common": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/requester-browser-xhr": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-M/Gf2vv/fU4+CqDW+wok7HPpEcLym3NtDzU9zaPzGYI/9X7o36581oyfnzt2pNfsXSQVj5a2pZVUWC3Z4SO27w==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/requester-common": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/requester-common": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-OIhsdwIrJVAlVlP7cwlt+RoR5AmxAoTGrFokOY9imVmgqXUUljdKO/DjhRL8vwYGFEidZ9efIjAIQ2B3XOhT9A=="
|
|
||||||
},
|
|
||||||
"@algolia/requester-node-http": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-viHAjfo53A3VSE7Bb/nzgpSMZ3prPp2qti7Wg8w7qxhntppKp3Fln6t4Vp+BoPOqruLsj139xXhheAKeRcYa0w==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/requester-common": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@algolia/transporter": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-Rb3cMlh/GoJK0+g+49GNA3IvR/EXsDEBwpyM+FOotSwxgiGt1wGBHM0K2v0GHwIEcuww02pl6KMDVlilA+qh0g==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/cache-common": "4.8.5",
|
|
||||||
"@algolia/logger-common": "4.8.5",
|
|
||||||
"@algolia/requester-common": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@hashicorp/react-content": {
|
"@hashicorp/react-content": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-content/-/react-content-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hashicorp/react-content/-/react-content-6.3.0.tgz",
|
||||||
|
@ -3016,73 +2901,23 @@
|
||||||
"search-insights": "^1.6.0",
|
"search-insights": "^1.6.0",
|
||||||
"unist-util-visit": "^2.0.3"
|
"unist-util-visit": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"algoliasearch": {
|
|
||||||
"version": "4.8.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.8.5.tgz",
|
|
||||||
"integrity": "sha512-GjKjpeevpePEJYinGokASNtIkl1t5EseNMlqDNAc+sXE8+iyyeqTyiJsN7bwlRG2BIremuslE/NlwdEfUuBLJw==",
|
|
||||||
"requires": {
|
|
||||||
"@algolia/cache-browser-local-storage": "4.8.5",
|
|
||||||
"@algolia/cache-common": "4.8.5",
|
|
||||||
"@algolia/cache-in-memory": "4.8.5",
|
|
||||||
"@algolia/client-account": "4.8.5",
|
|
||||||
"@algolia/client-analytics": "4.8.5",
|
|
||||||
"@algolia/client-common": "4.8.5",
|
|
||||||
"@algolia/client-recommendation": "4.8.5",
|
|
||||||
"@algolia/client-search": "4.8.5",
|
|
||||||
"@algolia/logger-common": "4.8.5",
|
|
||||||
"@algolia/logger-console": "4.8.5",
|
|
||||||
"@algolia/requester-browser-xhr": "4.8.5",
|
|
||||||
"@algolia/requester-common": "4.8.5",
|
|
||||||
"@algolia/requester-node-http": "4.8.5",
|
|
||||||
"@algolia/transporter": "4.8.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"algoliasearch-helper": {
|
|
||||||
"version": "3.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.4.4.tgz",
|
|
||||||
"integrity": "sha512-OjyVLjykaYKCMxxRMZNiwLp8CS310E0qAeIY2NaublcmLAh8/SL19+zYHp7XCLtMem2ZXwl3ywMiA32O9jszuw==",
|
|
||||||
"requires": {
|
|
||||||
"events": "^1.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"events": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
|
||||||
},
|
|
||||||
"react-instantsearch-core": {
|
|
||||||
"version": "6.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.10.0.tgz",
|
|
||||||
"integrity": "sha512-bn8rh/od4nw43caOiAsArA2Pw/JXX/7jL+nYe0n/Se66P7VR7UIA1i1ycthOrJzXCn9iNVFJFNMfyAN0HYVaWg==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.1.2",
|
|
||||||
"algoliasearch-helper": "^3.4.3",
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"react-fast-compare": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-instantsearch-dom": {
|
|
||||||
"version": "6.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-instantsearch-dom/-/react-instantsearch-dom-6.10.0.tgz",
|
|
||||||
"integrity": "sha512-t1IGn1i4btp9a8wNNV/OCYwfJwCx5CuCP6WNwBxYY1QeL27RKGaWPxvz6FjfRFCfrOvD2556STyvVriyGhDoeg==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.1.2",
|
|
||||||
"algoliasearch-helper": "^3.4.3",
|
|
||||||
"classnames": "^2.2.5",
|
|
||||||
"prop-types": "^15.6.2",
|
|
||||||
"react-fast-compare": "^3.0.0",
|
|
||||||
"react-instantsearch-core": "^6.10.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@hashicorp/react-docs-sidenav": {
|
"@hashicorp/react-docs-sidenav": {
|
||||||
"version": "6.1.1-alpha.16",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-docs-sidenav/-/react-docs-sidenav-6.1.1-alpha.16.tgz",
|
"resolved": "https://registry.npmjs.org/@hashicorp/react-docs-sidenav/-/react-docs-sidenav-7.0.0.tgz",
|
||||||
"integrity": "sha512-RpPjNwMNe5L2LA1vvgp496CauVJ8wLnKge1lPBZKL5931jR1SFEMwuWLB8R6Pe2HmkIC55nPB/c43GrmPN4FFw==",
|
"integrity": "sha512-gzOEG4fwfdfdHvxMuRC73bZUIpUzSPrG826NIM4N0lqUPzsAkDsfEl2+Vg1ZVfgzy2+41E+lIpHW4ZmWc5OZ7A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@hashicorp/react-link-wrap": "^2.0.2",
|
||||||
"fuzzysearch": "1.0.3"
|
"fuzzysearch": "1.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hashicorp/react-link-wrap": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hashicorp/react-link-wrap/-/react-link-wrap-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-q8s2TTd9Uy3BSYyUe2TTr2Kbc0ViRc7XQga2fZI0bzlFqBTiMXtf6gh2cg3QvimHY42y4YtaO5C109V9ahMUpQ=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@hashicorp/react-enterprise-alert": {
|
"@hashicorp/react-enterprise-alert": {
|
||||||
|
@ -3183,17 +3018,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@hashicorp/react-search": {
|
"@hashicorp/react-search": {
|
||||||
"version": "3.0.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hashicorp/react-search/-/react-search-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hashicorp/react-search/-/react-search-4.2.0.tgz",
|
||||||
"integrity": "sha512-62ttyCxjVFSHz1aNbdjeOcqCezpk3dLhMWTXeQb9Zsi0JYaJdBzK1M9khW1bfozTzjTXXGd/B79orlHMj/Zo9A==",
|
"integrity": "sha512-ITj3UC06w+bZKrHv77kYdtWlEH9gbtk+pAzZ5ZRxt2GMnw8qMzWnXZKVf1yHvyKAKkHkGXA5s+uFElxRJj3AVQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@hashicorp/react-inline-svg": "^1.0.2",
|
"@hashicorp/react-inline-svg": "^1.0.2",
|
||||||
"@hashicorp/remark-plugins": "^3.0.0",
|
"@hashicorp/remark-plugins": "^3.0.0",
|
||||||
"algoliasearch": "^4.4.0",
|
"algoliasearch": "^4.8.4",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"gray-matter": "^4.0.2",
|
"gray-matter": "^4.0.2",
|
||||||
"react-instantsearch-dom": "^6.7.0",
|
"react-instantsearch-dom": "^6.9.0",
|
||||||
"remark": "^12.0.1",
|
"remark": "^12.0.1",
|
||||||
"search-insights": "^1.6.0",
|
"search-insights": "^1.6.0",
|
||||||
"unist-util-visit": "^2.0.3"
|
"unist-util-visit": "^2.0.3"
|
||||||
|
@ -3927,6 +3762,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
|
||||||
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
|
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
|
||||||
},
|
},
|
||||||
|
"adm-zip": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-GMQg1a1cAegh+/EgWbz+XHZrwB467iB/IgtToldvxs7Xa5Br8mPmvCeRfY/Un2fLzrlIPt6Yu7Cej+8Ut9TGPg=="
|
||||||
|
},
|
||||||
"agent-base": {
|
"agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
@ -3973,30 +3813,30 @@
|
||||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
|
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
|
||||||
},
|
},
|
||||||
"algoliasearch": {
|
"algoliasearch": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.6",
|
||||||
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.8.6.tgz",
|
||||||
"integrity": "sha512-pljX9jEE2TQ3i1JayhG8afNdE8UuJg3O9c7unW6QO67yRWCKr6b0t5aKC3hSVtjt7pA2TQXLKoAISb4SHx9ozQ==",
|
"integrity": "sha512-G8IA3lcgaQB4r9HuQ4G+uSFjjz0Wv2OgEPiQ8emA+G2UUlroOfMl064j1bq/G+QTW0LmTQp9JwrFDRWxFM9J7w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@algolia/cache-browser-local-storage": "4.8.3",
|
"@algolia/cache-browser-local-storage": "4.8.6",
|
||||||
"@algolia/cache-common": "4.8.3",
|
"@algolia/cache-common": "4.8.6",
|
||||||
"@algolia/cache-in-memory": "4.8.3",
|
"@algolia/cache-in-memory": "4.8.6",
|
||||||
"@algolia/client-account": "4.8.3",
|
"@algolia/client-account": "4.8.6",
|
||||||
"@algolia/client-analytics": "4.8.3",
|
"@algolia/client-analytics": "4.8.6",
|
||||||
"@algolia/client-common": "4.8.3",
|
"@algolia/client-common": "4.8.6",
|
||||||
"@algolia/client-recommendation": "4.8.3",
|
"@algolia/client-recommendation": "4.8.6",
|
||||||
"@algolia/client-search": "4.8.3",
|
"@algolia/client-search": "4.8.6",
|
||||||
"@algolia/logger-common": "4.8.3",
|
"@algolia/logger-common": "4.8.6",
|
||||||
"@algolia/logger-console": "4.8.3",
|
"@algolia/logger-console": "4.8.6",
|
||||||
"@algolia/requester-browser-xhr": "4.8.3",
|
"@algolia/requester-browser-xhr": "4.8.6",
|
||||||
"@algolia/requester-common": "4.8.3",
|
"@algolia/requester-common": "4.8.6",
|
||||||
"@algolia/requester-node-http": "4.8.3",
|
"@algolia/requester-node-http": "4.8.6",
|
||||||
"@algolia/transporter": "4.8.3"
|
"@algolia/transporter": "4.8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"algoliasearch-helper": {
|
"algoliasearch-helper": {
|
||||||
"version": "3.3.4",
|
"version": "3.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.4.4.tgz",
|
||||||
"integrity": "sha512-1Ts2XcgGdjGlDrp3v6zbY8VW+X9+jJ5rBmtPBmXOQLd4b5t/LpJlaBdxoAnlMfVFjywP7KSAdmyFUNNYVHDyRQ==",
|
"integrity": "sha512-OjyVLjykaYKCMxxRMZNiwLp8CS310E0qAeIY2NaublcmLAh8/SL19+zYHp7XCLtMem2ZXwl3ywMiA32O9jszuw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"events": "^1.1.1"
|
"events": "^1.1.1"
|
||||||
},
|
},
|
||||||
|
@ -11653,27 +11493,27 @@
|
||||||
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
|
||||||
},
|
},
|
||||||
"react-instantsearch-core": {
|
"react-instantsearch-core": {
|
||||||
"version": "6.8.2",
|
"version": "6.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-instantsearch-core/-/react-instantsearch-core-6.10.3.tgz",
|
||||||
"integrity": "sha512-UdAjcNIXb2mSECEDS/2XuB4W6rcbnph1NjJBUpY5TLLzSCdKXNTzS2PxF5hkdeuY0L/m/hvDQX6YqxV28PqKLA==",
|
"integrity": "sha512-7twp3OJrPGTFpyXwjJNeOTbQw7RTv+0cUyKkXR9njEyLdXKcPWfpeBirXfdQHjYIHEY2b0V2Vom1B9IHSDSUtQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"@babel/runtime": "^7.1.2",
|
||||||
"algoliasearch-helper": "^3.1.0",
|
"algoliasearch-helper": "^3.4.3",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.6.2",
|
||||||
"react-fast-compare": "^3.0.0"
|
"react-fast-compare": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-instantsearch-dom": {
|
"react-instantsearch-dom": {
|
||||||
"version": "6.8.2",
|
"version": "6.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-instantsearch-dom/-/react-instantsearch-dom-6.8.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-instantsearch-dom/-/react-instantsearch-dom-6.10.3.tgz",
|
||||||
"integrity": "sha512-d6YBsjW/aF3qzul7qqUV/KuzEFPVxlAZm3QhREPqMvOyrPTnG5itZZBLe7sFm9OKJ/8shR4TyNp3hb94as7COg==",
|
"integrity": "sha512-kxc6IEruxJrc7O9lsLV5o4YK/RkGt3l7D1Y51JfmYkgeLuQHApwgcy/TAIoSN7wfR/1DONFbX8Y5VhU9Wqh87Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.1.2",
|
"@babel/runtime": "^7.1.2",
|
||||||
"algoliasearch-helper": "^3.1.0",
|
"algoliasearch-helper": "^3.4.3",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.6.2",
|
||||||
"react-fast-compare": "^3.0.0",
|
"react-fast-compare": "^3.0.0",
|
||||||
"react-instantsearch-core": "^6.8.2"
|
"react-instantsearch-core": "^6.10.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
|
|
|
@ -7,16 +7,18 @@
|
||||||
"@hashicorp/mktg-global-styles": "2.1.0",
|
"@hashicorp/mktg-global-styles": "2.1.0",
|
||||||
"@hashicorp/nextjs-scripts": "16.2.0",
|
"@hashicorp/nextjs-scripts": "16.2.0",
|
||||||
"@hashicorp/react-button": "4.0.0",
|
"@hashicorp/react-button": "4.0.0",
|
||||||
"@hashicorp/react-docs-page": "10.9.4-alpha.18",
|
"@hashicorp/react-docs-page": "11.0.1",
|
||||||
"@hashicorp/react-hashi-stack-menu": "^1.1.0",
|
"@hashicorp/react-hashi-stack-menu": "^1.1.0",
|
||||||
"@hashicorp/react-head": "1.1.6",
|
"@hashicorp/react-head": "1.1.6",
|
||||||
"@hashicorp/react-inline-svg": "5.0.0",
|
"@hashicorp/react-inline-svg": "5.0.0",
|
||||||
"@hashicorp/react-markdown-page": "^0.1.0",
|
"@hashicorp/react-markdown-page": "^0.1.0",
|
||||||
"@hashicorp/react-product-downloader": "4.0.2",
|
"@hashicorp/react-product-downloader": "4.0.2",
|
||||||
"@hashicorp/react-search": "^3.0.0",
|
"@hashicorp/react-search": "^4.2.0",
|
||||||
"@hashicorp/react-section-header": "4.0.0",
|
"@hashicorp/react-section-header": "4.0.0",
|
||||||
"@hashicorp/react-subnav": "7.1.0",
|
"@hashicorp/react-subnav": "7.1.0",
|
||||||
"@hashicorp/react-vertical-text-block-list": "4.0.1",
|
"@hashicorp/react-vertical-text-block-list": "4.0.1",
|
||||||
|
"adm-zip": "^0.5.4",
|
||||||
|
"gray-matter": "^4.0.2",
|
||||||
"next": "10.0.6",
|
"next": "10.0.6",
|
||||||
"next-mdx-remote": "1.0.1",
|
"next-mdx-remote": "1.0.1",
|
||||||
"next-remote-watch": "0.3.0",
|
"next-remote-watch": "0.3.0",
|
||||||
|
|
|
@ -12,12 +12,12 @@ import {
|
||||||
const BASE_ROUTE = 'docs'
|
const BASE_ROUTE = 'docs'
|
||||||
const NAV_DATA = 'data/docs-nav-data.json'
|
const NAV_DATA = 'data/docs-nav-data.json'
|
||||||
const CONTENT_DIR = 'content/docs'
|
const CONTENT_DIR = 'content/docs'
|
||||||
// override default "main" value for branch for "edit on this page"
|
const PRODUCT = { name: productName, slug: productSlug }
|
||||||
const MAIN_BRANCH = 'master'
|
|
||||||
// add remote plugin docs loading
|
// add remote plugin docs loading
|
||||||
const OPTIONS = {
|
const OPTIONS = {
|
||||||
remotePluginsFile: 'data/docs-remote-plugins.json',
|
remotePluginsFile: 'data/docs-remote-plugins.json',
|
||||||
additionalComponents: { PluginTierLabel },
|
additionalComponents: { PluginTierLabel },
|
||||||
|
mainBranch: 'master',
|
||||||
}
|
}
|
||||||
|
|
||||||
function DocsLayout({ isDevMissingRemotePlugins, ...props }) {
|
function DocsLayout({ isDevMissingRemotePlugins, ...props }) {
|
||||||
|
@ -49,8 +49,7 @@ function DocsLayout({ isDevMissingRemotePlugins, ...props }) {
|
||||||
<DocsPage
|
<DocsPage
|
||||||
additionalComponents={OPTIONS.additionalComponents}
|
additionalComponents={OPTIONS.additionalComponents}
|
||||||
baseRoute={BASE_ROUTE}
|
baseRoute={BASE_ROUTE}
|
||||||
mainBranch="master" // used for "edit on this page", default "main"
|
product={PRODUCT}
|
||||||
product={{ name: productName, slug: productSlug }}
|
|
||||||
staticProps={props}
|
staticProps={props}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -67,6 +66,7 @@ export async function getStaticProps({ params }) {
|
||||||
NAV_DATA,
|
NAV_DATA,
|
||||||
CONTENT_DIR,
|
CONTENT_DIR,
|
||||||
params,
|
params,
|
||||||
|
PRODUCT,
|
||||||
OPTIONS
|
OPTIONS
|
||||||
)
|
)
|
||||||
return { props }
|
return { props }
|
||||||
|
|
|
@ -10,15 +10,12 @@ import {
|
||||||
const BASE_ROUTE = 'guides'
|
const BASE_ROUTE = 'guides'
|
||||||
const NAV_DATA = 'data/guides-nav-data.json'
|
const NAV_DATA = 'data/guides-nav-data.json'
|
||||||
const CONTENT_DIR = 'content/guides'
|
const CONTENT_DIR = 'content/guides'
|
||||||
|
const MAIN_BRANCH = 'master'
|
||||||
|
const PRODUCT = { name: productName, slug: productSlug }
|
||||||
|
|
||||||
export default function GuidesLayout(props) {
|
export default function GuidesLayout(props) {
|
||||||
return (
|
return (
|
||||||
<DocsPage
|
<DocsPage baseRoute={BASE_ROUTE} product={PRODUCT} staticProps={props} />
|
||||||
baseRoute={BASE_ROUTE}
|
|
||||||
mainBranch="master" // used for "edit on this page", default "main"
|
|
||||||
product={{ name: productName, slug: productSlug }}
|
|
||||||
staticProps={props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +25,12 @@ export async function getStaticPaths() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ params }) {
|
export async function getStaticProps({ params }) {
|
||||||
const props = await generateStaticProps(NAV_DATA, CONTENT_DIR, params)
|
const props = await generateStaticProps(
|
||||||
|
NAV_DATA,
|
||||||
|
CONTENT_DIR,
|
||||||
|
params,
|
||||||
|
PRODUCT,
|
||||||
|
{ mainBranch: MAIN_BRANCH }
|
||||||
|
)
|
||||||
return { props }
|
return { props }
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,15 +10,12 @@ import {
|
||||||
const BASE_ROUTE = 'intro'
|
const BASE_ROUTE = 'intro'
|
||||||
const NAV_DATA = 'data/intro-nav-data.json'
|
const NAV_DATA = 'data/intro-nav-data.json'
|
||||||
const CONTENT_DIR = 'content/intro'
|
const CONTENT_DIR = 'content/intro'
|
||||||
|
const MAIN_BRANCH = 'master'
|
||||||
|
const PRODUCT = { name: productName, slug: productSlug }
|
||||||
|
|
||||||
export default function IntroLayout(props) {
|
export default function IntroLayout(props) {
|
||||||
return (
|
return (
|
||||||
<DocsPage
|
<DocsPage baseRoute={BASE_ROUTE} product={PRODUCT} staticProps={props} />
|
||||||
baseRoute={BASE_ROUTE}
|
|
||||||
mainBranch="master" // used for "edit on this page", default "main"
|
|
||||||
product={{ name: productName, slug: productSlug }}
|
|
||||||
staticProps={props}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +25,12 @@ export async function getStaticPaths() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStaticProps({ params }) {
|
export async function getStaticProps({ params }) {
|
||||||
const props = await generateStaticProps(NAV_DATA, CONTENT_DIR, params)
|
const props = await generateStaticProps(
|
||||||
|
NAV_DATA,
|
||||||
|
CONTENT_DIR,
|
||||||
|
params,
|
||||||
|
PRODUCT,
|
||||||
|
{ mainBranch: MAIN_BRANCH }
|
||||||
|
)
|
||||||
return { props }
|
return { props }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,95 @@
|
||||||
const { indexDocsContent } = require('@hashicorp/react-search/tools')
|
require('dotenv').config()
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
const {
|
||||||
|
indexContent,
|
||||||
|
getDocsSearchObject,
|
||||||
|
} = require('@hashicorp/react-search/tools')
|
||||||
|
const resolveNavData = require('../components/remote-plugin-docs/utils/resolve-nav-data')
|
||||||
|
|
||||||
indexDocsContent()
|
// Run indexing
|
||||||
|
indexContent({ getSearchObjects })
|
||||||
|
|
||||||
|
async function getSearchObjects() {
|
||||||
|
// Resolve /docs, /guides, and /intro nav data, which
|
||||||
|
// corresponds to all the content we will actually render
|
||||||
|
// This avoids indexing non-rendered content, and partials.
|
||||||
|
// Fetch objects for `docs` content
|
||||||
|
async function fetchDocsObjects() {
|
||||||
|
const navFile = 'data/docs-nav-data.json'
|
||||||
|
const contentDir = 'content/docs'
|
||||||
|
const opts = { remotePluginsFile: 'data/docs-remote-plugins.json' }
|
||||||
|
const navData = await resolveNavData(navFile, contentDir, opts)
|
||||||
|
return await searchObjectsFromNavData(navData, 'docs')
|
||||||
|
}
|
||||||
|
// Fetch objects for `guides` content
|
||||||
|
async function fetchGuidesObjects() {
|
||||||
|
const navFile = 'data/guides-nav-data.json'
|
||||||
|
const contentDir = 'content/guides'
|
||||||
|
const navData = await resolveNavData(navFile, contentDir)
|
||||||
|
return await searchObjectsFromNavData(navData, 'guides')
|
||||||
|
}
|
||||||
|
// Fetch objects for `intro` content
|
||||||
|
async function fetchIntroObjects() {
|
||||||
|
const navFile = 'data/intro-nav-data.json'
|
||||||
|
const contentDir = 'content/intro'
|
||||||
|
const navData = await resolveNavData(navFile, contentDir)
|
||||||
|
return await searchObjectsFromNavData(navData, 'intro')
|
||||||
|
}
|
||||||
|
// Collect, flatten and return the collected search objects
|
||||||
|
const searchObjects = (
|
||||||
|
await Promise.all([
|
||||||
|
fetchDocsObjects(),
|
||||||
|
fetchGuidesObjects(),
|
||||||
|
fetchIntroObjects(),
|
||||||
|
])
|
||||||
|
).reduce((acc, array) => acc.concat(array), [])
|
||||||
|
return searchObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given navData, return a flat array of search objects
|
||||||
|
* for each content file referenced in the navData nodes
|
||||||
|
* @param {Object[]} navData - an array of nav-data nodes, as detailed in [mktg-032](https://docs.google.com/document/d/1kYvbyd6njHFSscoE1dtDNHQ3U8IzaMdcjOS0jg87rHg)
|
||||||
|
* @param {string} baseRoute - the base route where the navData will be rendered. For example, "docs".
|
||||||
|
* @returns {Object[]} - an array of searchObjects to pass to Algolia. Must include an objectID property. See https://www.algolia.com/doc/api-reference/api-methods/add-objects/?client=javascript#examples.
|
||||||
|
*/
|
||||||
|
async function searchObjectsFromNavData(navData, baseRoute = '') {
|
||||||
|
const searchObjectsFromNodes = await Promise.all(
|
||||||
|
navData.map((n) => searchObjectsFromNavNode(n, baseRoute))
|
||||||
|
)
|
||||||
|
const flattenedSearchObjects = searchObjectsFromNodes.reduce(
|
||||||
|
(acc, searchObjects) => acc.concat(searchObjects),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
return flattenedSearchObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a single navData node, return a flat array of search objects.
|
||||||
|
* For "leaf" nodes, this will yield an array with a single object.
|
||||||
|
* For "branch" nodes, this may yield an array with zero or more search objects.
|
||||||
|
* For all other nodes, this will yield an empty array.
|
||||||
|
* @param {object} node - a nav-data nodes, as detailed in [mktg-032](https://docs.google.com/document/d/1kYvbyd6njHFSscoE1dtDNHQ3U8IzaMdcjOS0jg87rHg)
|
||||||
|
* @param {string} baseRoute - the base route where the navData will be rendered. For example, "docs".
|
||||||
|
* @returns {Object[]} - an array of searchObjects to pass to Algolia. Must include an objectID property. See https://www.algolia.com/doc/api-reference/api-methods/add-objects/?client=javascript#examples.
|
||||||
|
*/
|
||||||
|
async function searchObjectsFromNavNode(node, baseRoute) {
|
||||||
|
// If this is a node, build a search object
|
||||||
|
if (node.path) {
|
||||||
|
// Fetch the MDX file content
|
||||||
|
const fileString = node.filePath
|
||||||
|
? fs.readFileSync(path.join(process.cwd(), node.filePath), 'utf8')
|
||||||
|
: node.remoteFile.fileString
|
||||||
|
const searchObject = await getDocsSearchObject(
|
||||||
|
path.join(baseRoute, node.path),
|
||||||
|
fileString
|
||||||
|
)
|
||||||
|
return searchObject
|
||||||
|
}
|
||||||
|
// If this is a branch, recurse
|
||||||
|
if (node.routes) return await searchObjectsFromNavData(node.routes, baseRoute)
|
||||||
|
// Otherwise, return an empty array
|
||||||
|
// (for direct link nodes, divider nodes)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue