refactor: reunification of techdocs-cli and backstage repos
Signed-off-by: Andrew Thauer <athauer@wealthsimple.com>
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@techdocs/cli': patch
|
||||
---
|
||||
|
||||
Reunifies the [techdocs-cli](https://github.com/backstage/techdocs-cli) monorepo
|
||||
code back into the main [backstage](https://github.com/backstage/backstage) repo
|
||||
(see [7288](https://github.com/backstage/backstage/issues/7288)). The changes
|
||||
include some internal refactoring that do not affect functionality beyond the
|
||||
local development setup.
|
||||
@@ -18,7 +18,9 @@
|
||||
/plugins/techdocs-backend @backstage/techdocs-core
|
||||
/plugins/ilert @backstage/reviewers @yacut
|
||||
/plugins/home @backstage/techdocs-core
|
||||
/packages/embedded-techdocs-app @backstage/techdocs-core
|
||||
/packages/search-common @backstage/techdocs-core
|
||||
/packages/techdocs-cli @backstage/techdocs-core
|
||||
/packages/techdocs-common @backstage/techdocs-core
|
||||
/.changeset/cost-insights-* @backstage/reviewers @backstage/silver-lining
|
||||
/.changeset/search-* @backstage/techdocs-core
|
||||
|
||||
@@ -62,6 +62,7 @@ Debounce
|
||||
declaratively
|
||||
deduplicated
|
||||
deps
|
||||
dependabot
|
||||
destructured
|
||||
dev
|
||||
devops
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Techdocs E2E Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.changeset/**'
|
||||
- 'contrib/**'
|
||||
- 'docs/**'
|
||||
- 'microsite/**'
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x]
|
||||
|
||||
env:
|
||||
CI: true
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
|
||||
- name: install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: generate types
|
||||
run: yarn tsc
|
||||
|
||||
- name: build techdocs-cli
|
||||
working-directory: packages/techdocs-cli
|
||||
run: yarn build
|
||||
|
||||
- name: Install mkdocs & techdocs-core
|
||||
run: python -m pip install mkdocs-techdocs-core
|
||||
|
||||
- name: techdocs-cli e2e test
|
||||
working-directory: packages/techdocs-cli
|
||||
run: yarn test:e2e
|
||||
|
After Width: | Height: | Size: 278 KiB |
@@ -0,0 +1,241 @@
|
||||
# TechDocs CLI
|
||||
|
||||
Utility command line interface for managing TechDocs sites in
|
||||
[Backstage](https://github.com/backstage/backstage).
|
||||
|
||||
https://backstage.io/docs/features/techdocs/techdocs-overview
|
||||
|
||||
## Features
|
||||
|
||||
- Supports local development/preview of a TechDocs site in a Backstage app.
|
||||
- Supports generation and publishing of a documentation site in a CI/CD
|
||||
workflow.
|
||||
|
||||
```bash
|
||||
techdocs-cli --help
|
||||
Usage: techdocs-cli [options] [command]
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
generate|build [options] Generate TechDocs documentation site using mkdocs.
|
||||
publish [options] Publish generated TechDocs site to an external storage AWS S3,
|
||||
Google GCS, etc.
|
||||
serve:mkdocs [options] Serve a documentation project locally using mkdocs serve.
|
||||
serve [options] Serve a documentation project locally in a Backstage app-like
|
||||
environment
|
||||
help [command] display help for command
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
You can always use [`npx`](https://github.com/npm/npx) to run the latest version
|
||||
of `techdocs-cli` -
|
||||
|
||||
```bash
|
||||
npx @techdocs/cli [command]
|
||||
```
|
||||
|
||||
Or you can install it using [npm](https://www.npmjs.com/package/@techdocs/cli) -
|
||||
|
||||
```bash
|
||||
npm install -g @techdocs/cli
|
||||
techdocs-cli [command]
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Preview TechDocs site locally in a Backstage like environment
|
||||
|
||||
```bash
|
||||
techdocs-cli serve
|
||||
```
|
||||
|
||||

|
||||
|
||||
By default, Docker and
|
||||
[techdocs-container](https://github.com/backstage/techdocs-container) is used to
|
||||
make sure all the dependencies are installed. However, Docker can be disabled
|
||||
with `--no-docker` flag.
|
||||
|
||||
The command starts two local servers - an MkDocs preview server on port 8000 and
|
||||
a Backstage app server on port 3000. The Backstage app has a custom TechDocs API
|
||||
implementation, which uses the MkDocs preview server as a proxy to fetch the
|
||||
generated documentation files and assets.
|
||||
|
||||
NOTE: When using a custom `techdocs` docker image, make sure the entry point is
|
||||
also `ENTRYPOINT ["mkdocs"]`.
|
||||
|
||||
Command reference:
|
||||
|
||||
```bash
|
||||
Usage: techdocs-cli serve [options]
|
||||
|
||||
Serve a documentation project locally in a Backstage app-like environment
|
||||
|
||||
Options:
|
||||
-i, --docker-image <DOCKER_IMAGE> The mkdocs docker container to use (default: "spotify/techdocs")
|
||||
--no-docker Do not use Docker, use MkDocs executable in current user environment.
|
||||
--mkdocs-port <PORT> Port for MkDocs server to use (default: "8000")
|
||||
-v --verbose Enable verbose output. (default: false)
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
### Generate TechDocs site from a documentation project
|
||||
|
||||
```bash
|
||||
techdocs-cli generate
|
||||
```
|
||||
|
||||
Alias: `techdocs-cli build`
|
||||
|
||||
The generate command uses the
|
||||
[`@backstage/techdocs-common`](https://github.com/backstage/backstage/tree/master/packages/techdocs-common)
|
||||
package from Backstage for consistency. A Backstage app can also generate and
|
||||
publish TechDocs sites if `techdocs.builder` is set to `'local'` in
|
||||
`app-config.yaml`. See
|
||||
[configuration reference](https://backstage.io/docs/features/techdocs/configuration).
|
||||
|
||||
By default, this command uses Docker and
|
||||
[techdocs-container](https://github.com/backstage/techdocs-container) to make
|
||||
sure all the dependencies are installed. But it can be disabled using
|
||||
`--no-docker` flag.
|
||||
|
||||
Command reference:
|
||||
|
||||
```bash
|
||||
techdocs-cli generate --help
|
||||
Usage: techdocs-cli generate|build [options]
|
||||
|
||||
Generate TechDocs documentation site using MkDocs.
|
||||
|
||||
Options:
|
||||
--source-dir <PATH> Source directory containing mkdocs.yml and docs/ directory. (default: ".")
|
||||
--output-dir <PATH> Output directory containing generated TechDocs site. (default: "./site/")
|
||||
--docker-image <DOCKER_IMAGE> The mkdocs docker container to use (default: "spotify/techdocs:v0.3.4")
|
||||
--no-pull Do not pull the latest docker image
|
||||
--no-docker Do not use Docker, use MkDocs executable and plugins in current user environment.
|
||||
--techdocs-ref <HOST_TYPE:URL> The repository hosting documentation source files e.g.
|
||||
github:https://ghe.mycompany.net.com/org/repo.
|
||||
This value is same as the backstage.io/techdocs-ref annotation of the corresponding
|
||||
Backstage entity.
|
||||
It is completely fine to skip this as it is only being used to set repo_url in mkdocs.yml
|
||||
if not found.
|
||||
--etag <ETAG> A unique identifier for the prepared tree e.g. commit SHA. If provided it will be stored
|
||||
in techdocs_metadata.json.
|
||||
-v --verbose Enable verbose output. (default: false)
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
### Publish generated TechDocs sites
|
||||
|
||||
```bash
|
||||
techdocs-cli publish --publisher-type <awsS3|googleGcs|azureBlobStorage> --storage-name <bucket/container name> --entity <namespace/kind/name>
|
||||
```
|
||||
|
||||
After generating a TechDocs site using `techdocs-cli generate`, use the publish
|
||||
command to upload the static generated files on a cloud storage (AWS/GCS) bucket
|
||||
or (Azure) container which your Backstage app can read from.
|
||||
|
||||
The value for `--entity` must be the Backstage entity which the generated
|
||||
TechDocs site belongs to. You can find the values in your Entity's
|
||||
`catalog-info.yaml` file. If namespace is missing in the `catalog-info.yaml`,
|
||||
use `default`. The directory structure used in the storage bucket is
|
||||
`namespace/kind/name/<files>`.
|
||||
|
||||
Note that the values are case-sensitive. An example for `--entity` is
|
||||
`default/Component/<entityName>`.
|
||||
|
||||
Command reference:
|
||||
|
||||
```bash
|
||||
Usage: techdocs-cli publish [options]
|
||||
|
||||
Publish generated TechDocs site to an external storage AWS S3, Google GCS, etc.
|
||||
|
||||
Options:
|
||||
--publisher-type <TYPE> (Required always) awsS3 | googleGcs | azureBlobStorage
|
||||
- same as techdocs.publisher.type in Backstage
|
||||
app-config.yaml
|
||||
--storage-name <BUCKET/CONTAINER NAME> (Required always) In case of AWS/GCS, use the bucket
|
||||
name. In case of Azure, use container name. Same as
|
||||
techdocs.publisher.[TYPE].bucketName
|
||||
--entity <NAMESPACE/KIND/NAME> (Required always) Entity uid separated by / in
|
||||
namespace/kind/name order (case-sensitive). Example:
|
||||
default/Component/myEntity
|
||||
--legacyUseCaseSensitiveTripletPaths Publishes objects with cased entity triplet prefix when set (e.g. namespace/Kind/name).
|
||||
Only use if your TechDocs backend is configured the same way
|
||||
--azureAccountName <AZURE ACCOUNT NAME> (Required for Azure) specify when --publisher-type
|
||||
azureBlobStorage
|
||||
--azureAccountKey <AZURE ACCOUNT KEY> Azure Storage Account key to use for authentication.
|
||||
If not specified, you must set AZURE_TENANT_ID,
|
||||
AZURE_CLIENT_ID & AZURE_CLIENT_SECRET as environment
|
||||
variables.
|
||||
--awsRoleArn <AWS ROLE ARN> Optional AWS ARN of role to be assumed.
|
||||
--awsEndpoint <AWS ENDPOINT> Optional AWS endpoint to send requests to.
|
||||
--awsS3ForcePathStyle Optional AWS S3 option to force path style.
|
||||
--directory <PATH> Path of the directory containing generated files to
|
||||
publish (default: "./site/")
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
### Migrate content for case-insensitive access
|
||||
|
||||
Prior to the beta version of TechDocs (`v[0.11.0]`), TechDocs were stored in
|
||||
object storage using a case-sensitive entity triplet (e.g.
|
||||
`default/API/name/index.html`). This resulted in a limitation where that exact
|
||||
case was required in the Backstage URL in order to read/render TechDocs content.
|
||||
As of `v[0.11.0]` of the TechDocs plugin, any case is allowed in the URL (e.g.
|
||||
`default/api/name`), matching the behavior of the Catalog plugin.
|
||||
|
||||
Backstage instances created with TechDocs `v[0.11.0]` or later do not need this
|
||||
command. However, when upgrading to this version from an older version of
|
||||
TechDocs, the `migrate` command can be used prior to deployment to ensure docs
|
||||
remain accessible without having to rebuild all docs.
|
||||
|
||||
Prior to upgrading to `v[0.11.0]` or greater, run this command to copy all
|
||||
assets to their lower-case triplet equivalents like this:
|
||||
|
||||
```bash
|
||||
techdocs-cli migrate --publisher-type <awsS3|googleGcs|azureBlobStorage> --storage-name <bucket/container name> --verbose
|
||||
```
|
||||
|
||||
Once migrated and the upgraded version of the Backstage plugin has been
|
||||
deployed, you can clean up the legacy, case-sensitive triplet files by
|
||||
re-running the command with the `--removeOriginal` flag passed, which _moves_
|
||||
(rather than copies) the files. Note: this deletes files and is therefore a
|
||||
destructive operation that should performed with caution.
|
||||
|
||||
```bash
|
||||
techdocs-cli migrate --publisher-type <awsS3|googleGcs|azureBlobStorage> --storage-name <bucket/container name> --removeOriginal --verbose
|
||||
```
|
||||
|
||||
Afterward, update your TechDocs CLI to `v[0.7.0]` to ensure further publishing
|
||||
happens using a lower-case entity triplet.
|
||||
|
||||
Note: arguments for this command largely match those of the `publish` command,
|
||||
depending on your chosen storage provider. Run `techdocs-cli migrate --help` for
|
||||
details.
|
||||
|
||||
#### Authentication
|
||||
|
||||
You need to make sure that your environment is able to authenticate with the
|
||||
target cloud provider. `techdocs-cli` uses the official Node.js clients provided
|
||||
by AWS (v2), Google Cloud and Azure. You can authenticate using environment
|
||||
variables and/or by other means (`~/.aws/credentials`, `~/.config/gcloud` etc.)
|
||||
|
||||
Refer to the Authentication section of the following documentation depending
|
||||
upon your cloud storage provider -
|
||||
|
||||
- [Google Cloud Storage](https://backstage.io/docs/features/techdocs/using-cloud-storage#configuring-google-gcs-bucket-with-techdocs)
|
||||
- [AWS S3](https://backstage.io/docs/features/techdocs/using-cloud-storage#configuring-aws-s3-bucket-with-techdocs)
|
||||
- [Azure Blob Storage](https://backstage.io/docs/features/techdocs/using-cloud-storage#configuring-azure-blob-storage-container-with-techdocs)
|
||||
|
||||
## Development
|
||||
|
||||
You are welcome to contribute to TechDocs CLI to improve it and support new
|
||||
features! See the project
|
||||
[README](https://github.com/backstage/backstage/blob/main/src/packages/techdocs-cli/README.md)
|
||||
for more information.
|
||||
@@ -105,6 +105,7 @@
|
||||
"features/techdocs/configuration",
|
||||
"features/techdocs/using-cloud-storage",
|
||||
"features/techdocs/configuring-ci-cd",
|
||||
"features/techdocs/cli",
|
||||
"features/techdocs/how-to-guides",
|
||||
"features/techdocs/troubleshooting",
|
||||
"features/techdocs/faqs"
|
||||
|
||||
@@ -61,7 +61,6 @@ nav:
|
||||
- Writing Custom Actions: 'features/software-templates/writing-custom-actions.md'
|
||||
- Writing Templates (Legacy): 'features/software-templates/legacy.md'
|
||||
- Migrating from v1alpha1 to v1beta2 templates: 'features/software-templates/migrating-from-v1alpha1-to-v1beta2.md'
|
||||
|
||||
- Backstage Search:
|
||||
- Overview: 'features/search/README.md'
|
||||
- Getting Started: 'features/search/getting-started.md'
|
||||
@@ -78,6 +77,7 @@ nav:
|
||||
- TechDocs Configuration Options: 'features/techdocs/configuration.md'
|
||||
- Using Cloud Storage: 'features/techdocs/using-cloud-storage.md'
|
||||
- Configuring CI/CD to generate and publish TechDocs sites: 'features/techdocs/configuring-ci-cd.md'
|
||||
- CLI: 'features/techdocs/cli.md'
|
||||
- HOW TO guides: 'features/techdocs/how-to-guides.md'
|
||||
- Troubleshooting: 'features/techdocs/troubleshooting.md'
|
||||
- FAQ: 'features/techdocs/FAQ.md'
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"lerna": "lerna",
|
||||
"storybook": "yarn workspace storybook start",
|
||||
"build-storybook": "yarn workspace storybook build-storybook",
|
||||
"techdocs-cli:dev": "TECHDOCS_CLI_DEV_MODE=true packages/techdocs-cli/bin/techdocs-cli",
|
||||
"prepare": "husky install",
|
||||
"lock:check": "yarn-lock-check"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
# NOTE: This file is used for testing techdocs-cli locally
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
|
||||
techdocs:
|
||||
requestUrl: http://localhost:7000/api
|
||||
@@ -0,0 +1,10 @@
|
||||
app:
|
||||
title: Techdocs Preview App
|
||||
baseUrl: http://localhost:3000
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:3000
|
||||
|
||||
techdocs:
|
||||
builder: 'external'
|
||||
requestUrl: http://localhost:3000/api
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3001",
|
||||
"fixturesFolder": false,
|
||||
"pluginsFile": false
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"plugins": ["cypress"],
|
||||
"extends": ["plugin:cypress/recommended"],
|
||||
"rules": {
|
||||
"jest/expect-expect": [
|
||||
"error",
|
||||
{
|
||||
"assertFunctionNames": ["expect", "cy.contains"]
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true,
|
||||
"optionalDependencies": true,
|
||||
"peerDependencies": true,
|
||||
"bundledDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
describe('App', () => {
|
||||
it('should render the catalog', () => {
|
||||
cy.visit('/');
|
||||
cy.contains('My Company Service Catalog');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "embedded-techdocs-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.9.5",
|
||||
"@backstage/cli": "^0.8.0",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/core-app-api": "^0.1.18",
|
||||
"@backstage/core-components": "^0.7.1",
|
||||
"@backstage/core-plugin-api": "^0.1.11",
|
||||
"@backstage/integration-react": "^0.1.12",
|
||||
"@backstage/plugin-catalog": "^0.7.2",
|
||||
"@backstage/plugin-techdocs": "^0.12.3",
|
||||
"@backstage/test-utils": "^0.1.19",
|
||||
"@backstage/theme": "^0.2.11",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"history": "^5.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^089.0",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react-dom": "*",
|
||||
"cross-env": "^7.0.0",
|
||||
"cypress": "^7.3.0",
|
||||
"eslint-plugin-cypress": "^2.10.3",
|
||||
"start-server-and-test": "^1.10.11"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli app:serve --config ./app-config.yaml --config ./app-config.dev.yaml",
|
||||
"build": "backstage-cli app:build --config ./app-config.yaml",
|
||||
"clean": "backstage-cli clean",
|
||||
"test": "backstage-cli test",
|
||||
"lint": "backstage-cli lint",
|
||||
"test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev",
|
||||
"test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run",
|
||||
"cy:dev": "cypress open",
|
||||
"cy:run": "cypress run"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 883 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Backstage is an open platform for building developer portals"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="<%= publicPath %>/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link
|
||||
rel="manifest"
|
||||
href="<%= publicPath %>/manifest.json"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<link rel="icon" href="<%= publicPath %>/favicon.ico" />
|
||||
<link rel="shortcut icon" href="<%= publicPath %>/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="<%= publicPath %>/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="<%= publicPath %>/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="<%= publicPath %>/favicon-16x16.png"
|
||||
/>
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="<%= publicPath %>/safari-pinned-tab.svg"
|
||||
color="#5bbad5"
|
||||
/>
|
||||
<style>
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<title><%= app.title %></title>
|
||||
</head>
|
||||
<body style="margin: 0">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Backstage",
|
||||
"name": "Backstage",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="682.667" height="682.667" preserveAspectRatio="xMidYMid meet" version="1.0" viewBox="0 0 512 512"><metadata>Created by potrace 1.11, written by Peter Selinger 2001-2013</metadata><g fill="#000" stroke="none"><path d="M492 4610 c-4 -3 -8 -882 -7 -1953 l0 -1948 850 2 c898 1 945 3 1118 49 505 134 823 531 829 1037 2 136 -9 212 -44 323 -40 125 -89 218 -163 310 -35 43 -126 128 -169 157 -22 15 -43 30 -46 33 -12 13 -131 70 -188 91 l-64 22 60 28 c171 77 317 224 403 404 64 136 92 266 91 425 -5 424 -245 770 -642 923 -79 30 -105 39 -155 50 -11 3 -38 10 -60 15 -22 6 -60 13 -85 17 -25 3 -58 9 -75 12 -36 8 -1643 11 -1653 3z m1497 -743 c236 -68 352 -254 305 -486 -26 -124 -110 -224 -232 -277 -92 -40 -151 -46 -439 -49 l-283 -3 -1 27 c-1 36 -1 760 0 790 l1 23 298 -5 c226 -4 310 -9 351 -20z m-82 -1538 c98 -3 174 -19 247 -52 169 -78 257 -212 258 -395 0 -116 -36 -221 -100 -293 -64 -72 -192 -135 -314 -155 -23 -3 -181 -7 -350 -8 l-308 -2 -1 26 c-6 210 1 874 9 879 9 5 366 6 559 0z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/><path d="M4160 1789 c-275 -24 -499 -263 -503 -536 -1 -115 21 -212 66 -292 210 -369 697 -402 950 -65 77 103 110 199 111 329 0 50 -6 113 -13 140 -16 58 -62 155 -91 193 -33 43 -122 132 -132 132 -5 0 -26 11 -46 25 -85 56 -219 85 -342 74z" transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import App from './App';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', async () => {
|
||||
process.env = {
|
||||
NODE_ENV: 'test',
|
||||
APP_CONFIG: [
|
||||
{
|
||||
data: {
|
||||
app: { title: 'Test' },
|
||||
backend: { baseUrl: 'http://localhost:7000' },
|
||||
techdocs: {
|
||||
storageUrl: 'http://localhost:7000/api/techdocs/static/docs',
|
||||
},
|
||||
},
|
||||
context: 'test',
|
||||
},
|
||||
] as any,
|
||||
};
|
||||
|
||||
const rendered = await renderWithEffects(<App />);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Navigate, Route } from 'react-router';
|
||||
import { createApp, FlatRoutes } from '@backstage/core-app-api';
|
||||
import { CatalogEntityPage } from '@backstage/plugin-catalog';
|
||||
|
||||
import {
|
||||
DefaultTechDocsHome,
|
||||
TechDocsIndexPage,
|
||||
TechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
import { apis } from './apis';
|
||||
import { Root } from './components/Root';
|
||||
import { techDocsPage } from './components/TechDocsPage';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
plugins: Object.values(plugins),
|
||||
});
|
||||
|
||||
const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Navigate key="/" to="/docs/default/component/local/" />
|
||||
{/* we need this route as TechDocs header links relies on it */}
|
||||
<Route
|
||||
path="/catalog/:namespace/:kind/:name"
|
||||
element={<CatalogEntityPage />}
|
||||
/>
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
>
|
||||
{techDocsPage}
|
||||
</Route>
|
||||
</FlatRoutes>
|
||||
);
|
||||
|
||||
const App = () => (
|
||||
<AppProvider>
|
||||
<AppRouter>
|
||||
<Root>{routes}</Root>
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
scmIntegrationsApiRef,
|
||||
ScmIntegrationsApi,
|
||||
} from '@backstage/integration-react';
|
||||
import {
|
||||
AnyApiFactory,
|
||||
configApiRef,
|
||||
createApiFactory,
|
||||
DiscoveryApi,
|
||||
discoveryApiRef,
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
SyncResult,
|
||||
TechDocsApi,
|
||||
techdocsApiRef,
|
||||
TechDocsStorageApi,
|
||||
techdocsStorageApiRef,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
|
||||
// TODO: Export type from plugin-techdocs and import this here
|
||||
// import { ParsedEntityId } from '@backstage/plugin-techdocs'
|
||||
|
||||
/**
|
||||
* Note: Override TechDocs API to use local mkdocs server instead of techdocs-backend.
|
||||
*/
|
||||
|
||||
class TechDocsDevStorageApi implements TechDocsStorageApi {
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
public identityApi: IdentityApi;
|
||||
|
||||
constructor({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
this.identityApi = identityApi;
|
||||
}
|
||||
|
||||
async getApiOrigin() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.requestUrl') ??
|
||||
(await this.discoveryApi.getBaseUrl('techdocs'))
|
||||
);
|
||||
}
|
||||
|
||||
async getStorageUrl() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.storageUrl') ??
|
||||
`${await this.discoveryApi.getBaseUrl('techdocs')}/static/docs`
|
||||
);
|
||||
}
|
||||
|
||||
async getBuilder() {
|
||||
return this.configApi.getString('techdocs.builder');
|
||||
}
|
||||
|
||||
async getEntityDocs(_entityId: EntityName, path: string) {
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
// Irrespective of the entity, use mkdocs server to find the file for the path.
|
||||
const url = `${apiOrigin}/${path}`;
|
||||
|
||||
const request = await fetch(
|
||||
`${url.endsWith('/') ? url : `${url}/`}index.html`,
|
||||
);
|
||||
|
||||
if (request.status === 404) {
|
||||
throw new Error('Page not found');
|
||||
}
|
||||
|
||||
return request.text();
|
||||
}
|
||||
|
||||
async syncEntityDocs(_: EntityName): Promise<SyncResult> {
|
||||
// this is just stub of this function as we don't need to check if docs are up to date,
|
||||
// we always want to retrigger a new build
|
||||
return 'cached';
|
||||
}
|
||||
|
||||
// Used by transformer to modify the request to assets (CSS, Image) from inside the HTML.
|
||||
async getBaseUrl(
|
||||
oldBaseUrl: string,
|
||||
_entityId: EntityName,
|
||||
path: string,
|
||||
): Promise<string> {
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
return new URL(oldBaseUrl, `${apiOrigin}/${path}`).toString();
|
||||
}
|
||||
}
|
||||
|
||||
class TechDocsDevApi implements TechDocsApi {
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
public identityApi: IdentityApi;
|
||||
|
||||
constructor({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
this.identityApi = identityApi;
|
||||
}
|
||||
|
||||
async getApiOrigin() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.requestUrl') ??
|
||||
(await this.discoveryApi.getBaseUrl('techdocs'))
|
||||
);
|
||||
}
|
||||
|
||||
async getEntityMetadata(_entityId: any) {
|
||||
return {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'local',
|
||||
},
|
||||
spec: {
|
||||
owner: 'test',
|
||||
lifecycle: 'experimental',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getTechDocsMetadata(_entityId: EntityName) {
|
||||
return {
|
||||
site_name: 'Live preview environment',
|
||||
site_description: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const apis: AnyApiFactory[] = [
|
||||
createApiFactory({
|
||||
api: techdocsStorageApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, identityApi }) =>
|
||||
new TechDocsDevStorageApi({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: techdocsApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, identityApi }) =>
|
||||
new TechDocsDevApi({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: scmIntegrationsApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
svg: {
|
||||
width: 'auto',
|
||||
height: 28,
|
||||
},
|
||||
path: {
|
||||
fill: '#7df3e1',
|
||||
},
|
||||
});
|
||||
|
||||
const LogoIcon = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={classes.svg}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 337.46 428.5"
|
||||
>
|
||||
<path
|
||||
className={classes.path}
|
||||
d="M303,166.05a80.69,80.69,0,0,0,13.45-10.37c.79-.77,1.55-1.53,2.3-2.3a83.12,83.12,0,0,0,7.93-9.38A63.69,63.69,0,0,0,333,133.23a48.58,48.58,0,0,0,4.35-16.4c1.49-19.39-10-38.67-35.62-54.22L198.56,0,78.3,115.23,0,190.25l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.69,19.16-18.36,25.52-42.12,13.7-61.87a49.22,49.22,0,0,0-6.8-8.87A89.17,89.17,0,0,0,259,178.29h.15a85.08,85.08,0,0,0,31-5.79A80.88,80.88,0,0,0,303,166.05ZM202.45,225.86c-19.32,18.51-50.4,21.23-75.7,5.9L51.61,186.15l67.45-64.64,76.41,46.38C223,184.58,221.49,207.61,202.45,225.86Zm8.93-82.22-70.65-42.89L205.14,39,274.51,81.1c25.94,15.72,29.31,37,10.55,55A60.69,60.69,0,0,1,211.38,143.64Zm29.86,190c-19.57,18.75-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,282.52v24.67L108.6,373.1a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A87.27,87.27,0,0,1,241.24,333.68Zm0-39c-19.57,18.75-46.17,29.08-74.88,29.08a123.81,123.81,0,0,1-64.1-18.19L0,243.53v24.68l108.6,65.91a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.81,66.42-25.69,12.88-12.34,20-27.13,19.68-41.5v-1.78A87.27,87.27,0,0,1,241.24,294.7Zm0-39c-19.57,18.76-46.17,29.09-74.88,29.09a123.81,123.81,0,0,1-64.1-18.19L0,204.55v24.68l108.6,65.91a111.59,111.59,0,0,0,57.76,16.41c24.92,0,48.8-8.8,66.42-25.68,12.88-12.35,20-27.13,19.68-41.5v-1.82A86.09,86.09,0,0,1,241.24,255.71Zm83.7,25.74a94.15,94.15,0,0,1-60.2,25.86h0V334a81.6,81.6,0,0,0,51.74-22.37c14-13.38,21.14-28.11,21-42.64v-2.19A94.92,94.92,0,0,1,324.94,281.45Zm-83.7,91.21c-19.57,18.76-46.17,29.09-74.88,29.09a123.73,123.73,0,0,1-64.1-18.2L0,321.5v24.68l108.6,65.9a111.6,111.6,0,0,0,57.76,16.42c24.92,0,48.8-8.8,66.42-25.69,12.88-12.34,20-27.13,19.68-41.49v-1.79A86.29,86.29,0,0,1,241.24,372.66ZM327,162.45c-.68.69-1.35,1.38-2.05,2.06a94.37,94.37,0,0,1-10.64,8.65,91.35,91.35,0,0,1-11.6,7,94.53,94.53,0,0,1-26.24,8.71,97.69,97.69,0,0,1-14.16,1.57c.5,1.61.9,3.25,1.25,4.9a53.27,53.27,0,0,1,1.14,12V217h.05a84.41,84.41,0,0,0,25.35-5.55,81,81,0,0,0,26.39-16.82c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,172.17a48.55,48.55,0,0,0,4.32-16.45c.09-1.23.2-2.47.19-3.7V150q-1.08,1.54-2.25,3.09A96.73,96.73,0,0,1,327,162.45Zm0,77.92c-.69.7-1.31,1.41-2,2.1a94.2,94.2,0,0,1-60.2,25.86h0l0,26.67h0a81.6,81.6,0,0,0,51.74-22.37A73.51,73.51,0,0,0,333,250.13a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.47.19-3.71v-2.19c-.74,1.07-1.46,2.15-2.27,3.21A95.68,95.68,0,0,1,327,240.37Zm0-39c-.69.7-1.31,1.41-2,2.1a93.18,93.18,0,0,1-10.63,8.65,91.63,91.63,0,0,1-11.63,7,95.47,95.47,0,0,1-37.94,10.18h0V256h0a81.65,81.65,0,0,0,51.74-22.37c.8-.77,1.5-1.56,2.26-2.34a82.08,82.08,0,0,0,7.93-9.38A63.76,63.76,0,0,0,333,211.15a48.56,48.56,0,0,0,4.32-16.44c.09-1.24.2-2.48.19-3.71v-2.2c-.74,1.08-1.46,2.16-2.27,3.22A95.68,95.68,0,0,1,327,201.39Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoIcon;
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useContext, PropsWithChildren } from 'react';
|
||||
import { Link, makeStyles } from '@material-ui/core';
|
||||
import LibraryBooks from '@material-ui/icons/LibraryBooks';
|
||||
import LogoFull from './LogoFull';
|
||||
import LogoIcon from './LogoIcon';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarPage,
|
||||
sidebarConfig,
|
||||
SidebarContext,
|
||||
SidebarItem,
|
||||
SidebarDivider,
|
||||
} from '@backstage/core-components';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const useSidebarLogoStyles = makeStyles({
|
||||
root: {
|
||||
width: sidebarConfig.drawerWidthClosed,
|
||||
height: 3 * sidebarConfig.logoHeight,
|
||||
display: 'flex',
|
||||
flexFlow: 'row nowrap',
|
||||
alignItems: 'center',
|
||||
marginBottom: -14,
|
||||
},
|
||||
link: {
|
||||
width: sidebarConfig.drawerWidthClosed,
|
||||
marginLeft: 24,
|
||||
},
|
||||
});
|
||||
|
||||
const SidebarLogo = () => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
const { isOpen } = useContext(SidebarContext);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Link
|
||||
component={NavLink}
|
||||
to="/docs/default/component/local/"
|
||||
underline="none"
|
||||
className={classes.link}
|
||||
>
|
||||
{isOpen ? <LogoFull /> : <LogoIcon />}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
||||
<SidebarPage>
|
||||
<Sidebar>
|
||||
<SidebarLogo />
|
||||
<SidebarDivider />
|
||||
{/* Global nav, not org-specific */}
|
||||
<SidebarItem
|
||||
icon={LibraryBooks}
|
||||
to="docs/default/component/local/"
|
||||
text="Docs Preview"
|
||||
/>
|
||||
{/* End global nav */}
|
||||
</Sidebar>
|
||||
{children}
|
||||
</SidebarPage>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Root } from './Root';
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Content } from '@backstage/core-components';
|
||||
|
||||
import {
|
||||
Reader,
|
||||
TechDocsPage,
|
||||
TechDocsPageHeader,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
|
||||
const DefaultTechDocsPage = () => {
|
||||
const techDocsMetadata = {
|
||||
site_name: 'Live preview environment',
|
||||
site_description: '',
|
||||
};
|
||||
|
||||
return (
|
||||
<TechDocsPage>
|
||||
{({ entityRef, onReady }) => (
|
||||
<>
|
||||
<TechDocsPageHeader
|
||||
techDocsMetadata={techDocsMetadata}
|
||||
entityRef={entityRef}
|
||||
/>
|
||||
<Content data-testid="techdocs-content">
|
||||
<Reader
|
||||
onReady={onReady}
|
||||
entityRef={entityRef}
|
||||
withSearch={false}
|
||||
/>
|
||||
</Content>
|
||||
</>
|
||||
)}
|
||||
</TechDocsPage>
|
||||
);
|
||||
};
|
||||
|
||||
export const techDocsPage = <DefaultTechDocsPage />;
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './TechDocsPage';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import '@backstage/cli/asset-types';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { plugin as TechDocsPlugin } from '@backstage/plugin-techdocs';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.ts?(x)'],
|
||||
rules: {
|
||||
'no-restricted-imports': 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
# @techdocs/cli
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8fc7384: Allow to execute techdocs-cli serve using docker techdocs-container on Windows
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0187424: Separate build and publish release steps
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c6f437a: OpenStack Swift configuration changed due to OSS SDK Client change in @backstage/techdocs-common, it was a breaking change.
|
||||
PR Reference: https://github.com/backstage/backstage/pull/6839
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 05f0409: Merge Jobs for Release Pull Requests and Package Publishes
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 9d1f8d8: The `techdocs-cli publish` command will now publish TechDocs content to remote
|
||||
storage using the lowercase'd entity triplet as the storage path. This is in
|
||||
line with the beta release of the TechDocs plugin (`v0.11.0`).
|
||||
|
||||
If you have been running `techdocs-cli` prior to this version, you will need to
|
||||
follow this [migration guide](https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-alpha-to-beta).
|
||||
|
||||
## 0.6.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f1bcf1a: Changelog (from v0.6.1 to v0.6.2)
|
||||
|
||||
#### :bug: Bug Fix
|
||||
|
||||
- `techdocs-cli`
|
||||
- [#105](https://github.com/backstage/techdocs-cli/pull/105) Add azureAccountKey parameter back to the publish command ([@emmaindal](https://github.com/emmaindal))
|
||||
|
||||
#### :house: Internal
|
||||
|
||||
- `embedded-techdocs-app`
|
||||
- [#122](https://github.com/backstage/techdocs-cli/pull/122) chore(deps-dev): bump @types/node from 12.20.20 to 16.7.1 in /packages/embedded-techdocs-app ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#120](https://github.com/backstage/techdocs-cli/pull/120) chore(deps-dev): bump @types/react-dom from 16.9.14 to 17.0.9 in /packages/embedded-techdocs-app ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#119](https://github.com/backstage/techdocs-cli/pull/119) chore(deps-dev): bump @testing-library/user-event from 12.8.3 to 13.2.1 in /packages/embedded-techdocs-app ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#118](https://github.com/backstage/techdocs-cli/pull/118) chore(deps-dev): bump @testing-library/react from 10.4.9 to 12.0.0 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Other
|
||||
- [#117](https://github.com/backstage/techdocs-cli/pull/117) chore(deps): bump @backstage/plugin-catalog from 0.6.11 to 0.6.12 ([@dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- [#124](https://github.com/backstage/techdocs-cli/pull/124) Update release process docs ([@emmaindal](https://github.com/emmaindal))
|
||||
- [#116](https://github.com/backstage/techdocs-cli/pull/116) ignore dependabot branches for project board workflow ([@emmaindal](https://github.com/emmaindal))
|
||||
- [#106](https://github.com/backstage/techdocs-cli/pull/106) Configure dependabot for all packages ([@emmaindal](https://github.com/emmaindal))
|
||||
- [#102](https://github.com/backstage/techdocs-cli/pull/102) readme: add information about running techdocs-common locally ([@vcapretz](https://github.com/vcapretz))
|
||||
- [#103](https://github.com/backstage/techdocs-cli/pull/103) Introduce changesets and improve the publish workflow ([@minkimcello](https://github.com/minkimcello))
|
||||
- [#101](https://github.com/backstage/techdocs-cli/pull/101) update yarn lockfile to get rid of old version of node-forge ([@emmaindal](https://github.com/emmaindal))
|
||||
|
||||
#### Committers: 3
|
||||
|
||||
Thank you for contributing ❤️
|
||||
|
||||
- `Emma Indal` ([@emmaindal](https://github.com/emmaindal))
|
||||
- `Min Kim` ([@minkimcello](https://github.com/minkimcello))
|
||||
- `Vitor Capretz` ([@vcapretz](https://github.com/vcapretz))
|
||||
@@ -0,0 +1,68 @@
|
||||
# techdocs-cli
|
||||
|
||||
[](https://www.npmjs.com/package/@techdocs/cli)
|
||||
|
||||
## Usage
|
||||
|
||||
See [techdocs-cli usage docs](https://backstage.io/docs/features/techdocs/cli).
|
||||
|
||||
## Development
|
||||
|
||||
NOTE: When we build `techdocs-cli` it copies the output `embedded-techdocs-app`
|
||||
bundle into the `packages/techdocs-cli/dist` which is then published with the
|
||||
`@techdocs/cli` npm package.
|
||||
|
||||
### Running
|
||||
|
||||
```sh
|
||||
# From the root of this repository run
|
||||
# NOTE: This will build the embedded-techdocs-app and copy the output into the cli dist directory
|
||||
yarn build --scope @techdocs/cli
|
||||
|
||||
# Now execute the binary
|
||||
packages/techdocs-cli/bin/techdocs-cli
|
||||
|
||||
# ... or as a shell alias in ~/.zshrc or ~/.zprofile or ~/.bashrc or similar
|
||||
export PATH=/path/to/backstage/packages/techdocs-cli/bin:$PATH
|
||||
```
|
||||
|
||||
If you want to test live test changes to the `packages/embedded-techdocs-app`
|
||||
you can serve the app and run the CLI using the following commands:
|
||||
|
||||
```sh
|
||||
# Open a shell to the embedded-techdocs-app directory
|
||||
cd packages/embedded-techdocs-app
|
||||
|
||||
# Run the embedded-techdocs-app using dev mode
|
||||
yarn start
|
||||
|
||||
# In another shell use the techdocs-cli from the root of this repo
|
||||
yarn techdocs-cli:dev [...options]
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Running unit tests requires mkdocs to be installed locally:
|
||||
|
||||
```sh
|
||||
pip install mkdocs
|
||||
pip install mkdocs-techdocs-core
|
||||
```
|
||||
|
||||
Then run `yarn test`.
|
||||
|
||||
### Use an example docs project
|
||||
|
||||
We have created an [example documentation project](https://github.com/backstage/techdocs-container/tree/main/mock-docs) and it's shipped with [techdocs-container](https://github.com/backstage/techdocs-container) repository, for the purpose of local development. But you are free to create your own local test site. All it takes is a `docs/index.md` and `mkdocs.yml` in a directory.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/backstage/techdocs-container.git
|
||||
|
||||
cd techdocs-container/mock-docs
|
||||
|
||||
# To get a view of your docs in Backstage, use:
|
||||
techdocs-cli serve
|
||||
|
||||
# To view the raw mkdocs site (without Backstage), use:
|
||||
techdocs-cli serve:mkdocs
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// Figure out whether we're running inside the backstage repo or as an installed dependency
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src'));
|
||||
|
||||
if (!isLocal) {
|
||||
require('..');
|
||||
} else {
|
||||
require('ts-node').register({
|
||||
transpileOnly: true,
|
||||
project: path.resolve(__dirname, '../../../tsconfig.json'),
|
||||
compilerOptions: {
|
||||
module: 'CommonJS',
|
||||
},
|
||||
});
|
||||
|
||||
require('../src');
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "@techdocs/cli",
|
||||
"description": "Utility CLI for managing TechDocs sites in Backstage.",
|
||||
"version": "0.8.4",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/techdocs-cli"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage",
|
||||
"techdocs"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "",
|
||||
"scripts": {
|
||||
"start": "nodemon --",
|
||||
"build": "./scripts/build.sh",
|
||||
"clean": "backstage-cli clean",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test --testPathIgnorePatterns src/e2e.test.ts",
|
||||
"test:e2e": "backstage-cli test --testPathPattern src/e2e.test.ts --runInBand"
|
||||
},
|
||||
"bin": {
|
||||
"techdocs-cli": "bin/techdocs-cli"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.8.0",
|
||||
"@types/commander": "^2.12.2",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react-dev-utils": "^9.0.4",
|
||||
"@types/serve-handler": "^6.1.0",
|
||||
"@types/webpack-env": "^1.15.3",
|
||||
"embedded-techdocs-app": "0.0.0",
|
||||
"nodemon": "^2.0.2",
|
||||
"ts-node": "^10.0.0"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"nodemonConfig": {
|
||||
"watch": "./src",
|
||||
"exec": "bin/techdocs-cli",
|
||||
"ext": "ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.9.7",
|
||||
"@backstage/catalog-model": "^0.9.5",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/techdocs-common": "^0.10.4",
|
||||
"@types/dockerode": "^3.2.1",
|
||||
"commander": "^6.1.0",
|
||||
"dockerode": "^3.2.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"http-proxy": "^1.18.1",
|
||||
"react-dev-utils": "^11.0.4",
|
||||
"serve-handler": "^6.1.3",
|
||||
"winston": "^3.2.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2020 The Backstage Authors
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
|
||||
# Build the TechDocs CLI
|
||||
npx backstage-cli -- build --outputs cjs
|
||||
|
||||
# Make sure to do `yarn run build` in packages/embedded-techdocs before building here.
|
||||
|
||||
EMBEDDED_TECHDOCS_APP_PATH=../embedded-techdocs-app
|
||||
TECHDOCS_PREVIEW_SOURCE=$EMBEDDED_TECHDOCS_APP_PATH/dist
|
||||
TECHDOCS_PREVIEW_DEST=dist/techdocs-preview-bundle
|
||||
|
||||
# Build the embedded-techdocs-app
|
||||
pushd $EMBEDDED_TECHDOCS_APP_PATH >/dev/null
|
||||
yarn build
|
||||
popd >/dev/null
|
||||
|
||||
cp -r $TECHDOCS_PREVIEW_SOURCE $TECHDOCS_PREVIEW_DEST
|
||||
|
||||
# Write to console
|
||||
echo "[techdocs-cli]: Built the dist/ folder"
|
||||
echo "[techdocs-cli]: Imported @backstage/plugin-techdocs dist/ folder into techdocs-preview-bundle/"
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { Command } from 'commander';
|
||||
import fs from 'fs-extra';
|
||||
import Docker from 'dockerode';
|
||||
import {
|
||||
TechdocsGenerator,
|
||||
ParsedLocationAnnotation,
|
||||
} from '@backstage/techdocs-common';
|
||||
import { DockerContainerRunner } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
convertTechDocsRefToLocationAnnotation,
|
||||
createLogger,
|
||||
} from '../../lib/utility';
|
||||
import { stdout } from 'process';
|
||||
|
||||
export default async function generate(cmd: Command) {
|
||||
// Use techdocs-common package to generate docs. Keep consistency between Backstage and CI generating docs.
|
||||
// Docs can be prepared using actions/checkout or git clone, or similar paradigms on CI. The TechDocs CI workflow
|
||||
// will run on the CI pipeline containing the documentation files.
|
||||
|
||||
const logger = createLogger({ verbose: cmd.verbose });
|
||||
|
||||
const sourceDir = resolve(cmd.sourceDir);
|
||||
const outputDir = resolve(cmd.outputDir);
|
||||
const dockerImage = cmd.dockerImage;
|
||||
const pullImage = cmd.pull;
|
||||
|
||||
logger.info(`Using source dir ${sourceDir}`);
|
||||
logger.info(`Will output generated files in ${outputDir}`);
|
||||
|
||||
logger.verbose('Creating output directory if it does not exist.');
|
||||
|
||||
await fs.ensureDir(outputDir);
|
||||
|
||||
const config = new ConfigReader({
|
||||
techdocs: {
|
||||
generator: {
|
||||
runIn: cmd.docker ? 'docker' : 'local',
|
||||
dockerImage,
|
||||
pullImage,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Docker client (conditionally) used by the generators, based on techdocs.generators config.
|
||||
const dockerClient = new Docker();
|
||||
const containerRunner = new DockerContainerRunner({ dockerClient });
|
||||
|
||||
let parsedLocationAnnotation = {} as ParsedLocationAnnotation;
|
||||
if (cmd.techdocsRef) {
|
||||
try {
|
||||
parsedLocationAnnotation = convertTechDocsRefToLocationAnnotation(
|
||||
cmd.techdocsRef,
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate docs using @backstage/techdocs-common
|
||||
const techdocsGenerator = await TechdocsGenerator.fromConfig(config, {
|
||||
logger,
|
||||
containerRunner,
|
||||
});
|
||||
|
||||
logger.info('Generating documentation...');
|
||||
|
||||
await techdocsGenerator.run({
|
||||
inputDir: sourceDir,
|
||||
outputDir,
|
||||
...(cmd.techdocsRef
|
||||
? {
|
||||
parsedLocationAnnotation,
|
||||
}
|
||||
: {}),
|
||||
logger,
|
||||
etag: cmd.etag,
|
||||
...(process.env.LOG_LEVEL === 'debug' ? { logStream: stdout } : {}),
|
||||
});
|
||||
|
||||
logger.info('Done!');
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommanderStatic } from 'commander';
|
||||
import { TechdocsGenerator } from '@backstage/techdocs-common';
|
||||
|
||||
const defaultDockerImage = TechdocsGenerator.defaultDockerImage;
|
||||
|
||||
export function registerCommands(program: CommanderStatic) {
|
||||
program
|
||||
.command('generate')
|
||||
.description('Generate TechDocs documentation site using MkDocs.')
|
||||
.option(
|
||||
'--source-dir <PATH>',
|
||||
'Source directory containing mkdocs.yml and docs/ directory.',
|
||||
'.',
|
||||
)
|
||||
.option(
|
||||
'--output-dir <PATH>',
|
||||
'Output directory containing generated TechDocs site.',
|
||||
'./site/',
|
||||
)
|
||||
.option(
|
||||
'--docker-image <DOCKER_IMAGE>',
|
||||
'The mkdocs docker container to use',
|
||||
defaultDockerImage,
|
||||
)
|
||||
.option('--no-pull', 'Do not pull the latest docker image', false)
|
||||
.option(
|
||||
'--no-docker',
|
||||
'Do not use Docker, use MkDocs executable and plugins in current user environment.',
|
||||
)
|
||||
.option(
|
||||
'--techdocs-ref <HOST_TYPE:URL>',
|
||||
'The repository hosting documentation source files e.g. github:https://ghe.mycompany.net.com/org/repo.' +
|
||||
'\nThis value is same as the backstage.io/techdocs-ref annotation of the corresponding Backstage entity.' +
|
||||
'\nIt is completely fine to skip this as it is only being used to set repo_url in mkdocs.yml if not found.\n',
|
||||
)
|
||||
.option(
|
||||
'--etag <ETAG>',
|
||||
'A unique identifier for the prepared tree e.g. commit SHA. If provided it will be stored in techdocs_metadata.json.',
|
||||
)
|
||||
.option('-v --verbose', 'Enable verbose output.', false)
|
||||
.alias('build')
|
||||
.action(lazy(() => import('./generate/generate').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('migrate')
|
||||
.description(
|
||||
'Migrate objects with case-sensitive entity triplets to lower-case versions.',
|
||||
)
|
||||
.requiredOption(
|
||||
'--publisher-type <TYPE>',
|
||||
'(Required always) awsS3 | googleGcs | azureBlobStorage | openStackSwift - same as techdocs.publisher.type in Backstage app-config.yaml',
|
||||
)
|
||||
.requiredOption(
|
||||
'--storage-name <BUCKET/CONTAINER NAME>',
|
||||
'(Required always) In case of AWS/GCS, use the bucket name. In case of Azure, use container name. Same as techdocs.publisher.[TYPE].bucketName',
|
||||
)
|
||||
.option(
|
||||
'--azureAccountName <AZURE ACCOUNT NAME>',
|
||||
'(Required for Azure) specify when --publisher-type azureBlobStorage',
|
||||
)
|
||||
.option(
|
||||
'--azureAccountKey <AZURE ACCOUNT KEY>',
|
||||
'Azure Storage Account key to use for authentication. If not specified, you must set AZURE_TENANT_ID, AZURE_CLIENT_ID & AZURE_CLIENT_SECRET as environment variables.',
|
||||
)
|
||||
.option(
|
||||
'--awsRoleArn <AWS ROLE ARN>',
|
||||
'Optional AWS ARN of role to be assumed.',
|
||||
)
|
||||
.option(
|
||||
'--awsEndpoint <AWS ENDPOINT>',
|
||||
'Optional AWS endpoint to send requests to.',
|
||||
)
|
||||
.option(
|
||||
'--awsS3ForcePathStyle',
|
||||
'Optional AWS S3 option to force path style.',
|
||||
)
|
||||
.option(
|
||||
'--osCredentialId <OPENSTACK SWIFT APPLICATION CREDENTIAL ID>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--osSecret <OPENSTACK SWIFT APPLICATION CREDENTIAL SECRET>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--osAuthUrl <OPENSTACK SWIFT AUTHURL>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--osSwiftUrl <OPENSTACK SWIFT SWIFTURL>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--removeOriginal',
|
||||
'Optional Files are copied by default. If flag is set, files are renamed/moved instead.',
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
'--concurrency <MAX CONCURRENT REQS>',
|
||||
'Optional Controls the number of API requests allowed to be performed simultaneously.',
|
||||
'25',
|
||||
)
|
||||
.option('-v --verbose', 'Enable verbose output.', false)
|
||||
.action(lazy(() => import('./migrate/migrate').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('publish')
|
||||
.description(
|
||||
'Publish generated TechDocs site to an external storage AWS S3, Google GCS, etc.',
|
||||
)
|
||||
.requiredOption(
|
||||
'--publisher-type <TYPE>',
|
||||
'(Required always) awsS3 | googleGcs | azureBlobStorage | openStackSwift - same as techdocs.publisher.type in Backstage app-config.yaml',
|
||||
)
|
||||
.requiredOption(
|
||||
'--storage-name <BUCKET/CONTAINER NAME>',
|
||||
'(Required always) In case of AWS/GCS, use the bucket name. In case of Azure, use container name. Same as techdocs.publisher.[TYPE].bucketName',
|
||||
)
|
||||
.requiredOption(
|
||||
'--entity <NAMESPACE/KIND/NAME>',
|
||||
'(Required always) Entity uid separated by / in namespace/kind/name order (case-sensitive). Example: default/Component/myEntity ',
|
||||
)
|
||||
.option(
|
||||
'--legacyUseCaseSensitiveTripletPaths',
|
||||
'Publishes objects with cased entity triplet prefix when set (e.g. namespace/Kind/name). Only use if your TechDocs backend is configured the same way.',
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
'--azureAccountName <AZURE ACCOUNT NAME>',
|
||||
'(Required for Azure) specify when --publisher-type azureBlobStorage',
|
||||
)
|
||||
.option(
|
||||
'--azureAccountKey <AZURE ACCOUNT KEY>',
|
||||
'Azure Storage Account key to use for authentication. If not specified, you must set AZURE_TENANT_ID, AZURE_CLIENT_ID & AZURE_CLIENT_SECRET as environment variables.',
|
||||
)
|
||||
.option(
|
||||
'--awsRoleArn <AWS ROLE ARN>',
|
||||
'Optional AWS ARN of role to be assumed.',
|
||||
)
|
||||
.option(
|
||||
'--awsEndpoint <AWS ENDPOINT>',
|
||||
'Optional AWS endpoint to send requests to.',
|
||||
)
|
||||
.option(
|
||||
'--awsS3ForcePathStyle',
|
||||
'Optional AWS S3 option to force path style.',
|
||||
)
|
||||
.option(
|
||||
'--osCredentialId <OPENSTACK SWIFT APPLICATION CREDENTIAL ID>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--osSecret <OPENSTACK SWIFT APPLICATION CREDENTIAL SECRET>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--osAuthUrl <OPENSTACK SWIFT AUTHURL>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--osSwiftUrl <OPENSTACK SWIFT SWIFTURL>',
|
||||
'(Required for OpenStack) specify when --publisher-type openStackSwift',
|
||||
)
|
||||
.option(
|
||||
'--directory <PATH>',
|
||||
'Path of the directory containing generated files to publish',
|
||||
'./site/',
|
||||
)
|
||||
.action(lazy(() => import('./publish/publish').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('serve:mkdocs')
|
||||
.description('Serve a documentation project locally using MkDocs serve.')
|
||||
.option(
|
||||
'-i, --docker-image <DOCKER_IMAGE>',
|
||||
'The mkdocs docker container to use',
|
||||
defaultDockerImage,
|
||||
)
|
||||
.option(
|
||||
'--no-docker',
|
||||
'Do not use Docker, run `mkdocs serve` in current user environment.',
|
||||
)
|
||||
.option('-p, --port <PORT>', 'Port to serve documentation locally', '8000')
|
||||
.option('-v --verbose', 'Enable verbose output.', false)
|
||||
.action(lazy(() => import('./serve/mkdocs').then(m => m.default)));
|
||||
|
||||
program
|
||||
.command('serve')
|
||||
.description(
|
||||
'Serve a documentation project locally in a Backstage app-like environment',
|
||||
)
|
||||
.option(
|
||||
'-i, --docker-image <DOCKER_IMAGE>',
|
||||
'The mkdocs docker container to use',
|
||||
defaultDockerImage,
|
||||
)
|
||||
.option(
|
||||
'--no-docker',
|
||||
'Do not use Docker, use MkDocs executable in current user environment.',
|
||||
)
|
||||
.option('--mkdocs-port <PORT>', 'Port for MkDocs server to use', '8000')
|
||||
.option('-v --verbose', 'Enable verbose output.', false)
|
||||
.action(lazy(() => import('./serve/serve').then(m => m.default)));
|
||||
}
|
||||
|
||||
// Wraps an action function so that it always exits and handles errors
|
||||
// Humbly taken from backstage-cli's registerCommands
|
||||
function lazy(
|
||||
getActionFunc: () => Promise<(...args: any[]) => Promise<void>>,
|
||||
): (...args: any[]) => Promise<never> {
|
||||
return async (...args: any[]) => {
|
||||
try {
|
||||
const actionFunc = await getActionFunc();
|
||||
await actionFunc(...args);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SingleHostDiscovery } from '@backstage/backend-common';
|
||||
import { Publisher } from '@backstage/techdocs-common';
|
||||
import { Command } from 'commander';
|
||||
import { createLogger } from '../../lib/utility';
|
||||
import { PublisherConfig } from '../../lib/PublisherConfig';
|
||||
|
||||
export default async function migrate(cmd: Command) {
|
||||
const logger = createLogger({ verbose: cmd.verbose });
|
||||
|
||||
const config = PublisherConfig.getValidConfig(cmd);
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const publisher = await Publisher.fromConfig(config, { logger, discovery });
|
||||
|
||||
if (!publisher.migrateDocsCase) {
|
||||
throw new Error(`Migration not implemented for ${cmd.publisherType}`);
|
||||
}
|
||||
|
||||
// Check that the publisher's underlying storage is ready and available.
|
||||
const { isAvailable } = await publisher.getReadiness();
|
||||
if (!isAvailable) {
|
||||
// Error messages printed in getReadiness() call. This ensures exit code 1.
|
||||
throw new Error('');
|
||||
}
|
||||
|
||||
// Validate and parse migration arguments.
|
||||
const removeOriginal = cmd.removeOriginal;
|
||||
const numericConcurrency = parseInt(cmd.concurrency, 10);
|
||||
|
||||
if (!Number.isInteger(numericConcurrency) || numericConcurrency <= 0) {
|
||||
throw new Error(
|
||||
`Concurrency must be a number greater than 1. ${cmd.concurrency} provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
await publisher.migrateDocsCase({
|
||||
concurrency: numericConcurrency,
|
||||
removeOriginal,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { Command } from 'commander';
|
||||
import { createLogger } from '../../lib/utility';
|
||||
import { SingleHostDiscovery } from '@backstage/backend-common';
|
||||
import { Publisher } from '@backstage/techdocs-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PublisherConfig } from '../../lib/PublisherConfig';
|
||||
|
||||
export default async function publish(cmd: Command): Promise<any> {
|
||||
const logger = createLogger({ verbose: cmd.verbose });
|
||||
|
||||
const config = PublisherConfig.getValidConfig(cmd);
|
||||
const discovery = SingleHostDiscovery.fromConfig(config);
|
||||
const publisher = await Publisher.fromConfig(config, { logger, discovery });
|
||||
|
||||
// Check that the publisher's underlying storage is ready and available.
|
||||
const { isAvailable } = await publisher.getReadiness();
|
||||
if (!isAvailable) {
|
||||
// Error messages printed in getReadiness() call. This ensures exit code 1.
|
||||
return Promise.reject(new Error(''));
|
||||
}
|
||||
|
||||
const [namespace, kind, name] = cmd.entity.split('/');
|
||||
const entity = {
|
||||
kind,
|
||||
metadata: {
|
||||
namespace,
|
||||
name,
|
||||
},
|
||||
} as Entity;
|
||||
|
||||
const directory = resolve(cmd.directory);
|
||||
await publisher.publish({ entity, directory });
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import openBrowser from 'react-dev-utils/openBrowser';
|
||||
import { createLogger } from '../../lib/utility';
|
||||
import { runMkdocsServer } from '../../lib/mkdocsServer';
|
||||
import { LogFunc, waitForSignal } from '../../lib/run';
|
||||
|
||||
export default async function serveMkdocs(cmd: Command) {
|
||||
const logger = createLogger({ verbose: cmd.verbose });
|
||||
|
||||
const dockerAddr = `http://0.0.0.0:${cmd.port}`;
|
||||
const localAddr = `http://127.0.0.1:${cmd.port}`;
|
||||
const expectedDevAddr = cmd.docker ? dockerAddr : localAddr;
|
||||
// We want to open browser only once based on a log.
|
||||
let boolOpenBrowserTriggered = false;
|
||||
|
||||
const logFunc: LogFunc = data => {
|
||||
// Sometimes the lines contain an unnecessary extra new line in between
|
||||
const logLines = data.toString().split('\n');
|
||||
const logPrefix = cmd.docker ? '[docker/mkdocs]' : '[mkdocs]';
|
||||
logLines.forEach(line => {
|
||||
if (line === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Logs from container is verbose.
|
||||
logger.verbose(`${logPrefix} ${line}`);
|
||||
|
||||
// When the server has started, open a new browser tab for the user.
|
||||
if (
|
||||
!boolOpenBrowserTriggered &&
|
||||
line.includes(`Serving on ${expectedDevAddr}`)
|
||||
) {
|
||||
// Always open the local address, since 0.0.0.0 belongs to docker
|
||||
logger.info(`\nStarting mkdocs server on ${localAddr}\n`);
|
||||
openBrowser(localAddr);
|
||||
boolOpenBrowserTriggered = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
// mkdocs writes all of its logs to stderr by default, and not stdout.
|
||||
// https://github.com/mkdocs/mkdocs/issues/879#issuecomment-203536006
|
||||
// Had me questioning this whole implementation for half an hour.
|
||||
|
||||
// Commander stores --no-docker in cmd.docker variable
|
||||
const childProcess = await runMkdocsServer({
|
||||
port: cmd.port,
|
||||
dockerImage: cmd.dockerImage,
|
||||
useDocker: cmd.docker,
|
||||
stdoutLogFunc: logFunc,
|
||||
stderrLogFunc: logFunc,
|
||||
});
|
||||
|
||||
// Keep waiting for user to cancel the process
|
||||
await waitForSignal([childProcess]);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import path from 'path';
|
||||
import openBrowser from 'react-dev-utils/openBrowser';
|
||||
import HTTPServer from '../../lib/httpServer';
|
||||
import { runMkdocsServer } from '../../lib/mkdocsServer';
|
||||
import { LogFunc, waitForSignal } from '../../lib/run';
|
||||
import { createLogger } from '../../lib/utility';
|
||||
|
||||
export default async function serve(cmd: Command) {
|
||||
const logger = createLogger({ verbose: cmd.verbose });
|
||||
|
||||
// Determine if we want to run in local dev mode or not
|
||||
// This will run the backstage http server on a different port and only used
|
||||
// for proxying mkdocs to the backstage app running locally (e.g. with webpack-dev-server)
|
||||
const isDevMode = Object.keys(process.env).includes('TECHDOCS_CLI_DEV_MODE')
|
||||
? true
|
||||
: false;
|
||||
|
||||
// TODO: Backstage app port should also be configurable as a CLI option. However, since we bundle
|
||||
// a backstage app, we define app.baseUrl in the app-config.yaml.
|
||||
// Hence, it is complicated to make this configurable.
|
||||
const backstagePort = 3000;
|
||||
const backstageBackendPort = 7000;
|
||||
|
||||
const mkdocsDockerAddr = `http://0.0.0.0:${cmd.mkdocsPort}`;
|
||||
const mkdocsLocalAddr = `http://127.0.0.1:${cmd.mkdocsPort}`;
|
||||
const mkdocsExpectedDevAddr = cmd.docker ? mkdocsDockerAddr : mkdocsLocalAddr;
|
||||
|
||||
let mkdocsServerHasStarted = false;
|
||||
const mkdocsLogFunc: LogFunc = data => {
|
||||
// Sometimes the lines contain an unnecessary extra new line
|
||||
const logLines = data.toString().split('\n');
|
||||
const logPrefix = cmd.docker ? '[docker/mkdocs]' : '[mkdocs]';
|
||||
logLines.forEach(line => {
|
||||
if (line === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.verbose(`${logPrefix} ${line}`);
|
||||
|
||||
// When the server has started, open a new browser tab for the user.
|
||||
if (
|
||||
!mkdocsServerHasStarted &&
|
||||
line.includes(`Serving on ${mkdocsExpectedDevAddr}`)
|
||||
) {
|
||||
mkdocsServerHasStarted = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
// mkdocs writes all of its logs to stderr by default, and not stdout.
|
||||
// https://github.com/mkdocs/mkdocs/issues/879#issuecomment-203536006
|
||||
// Had me questioning this whole implementation for half an hour.
|
||||
logger.info('Starting mkdocs server.');
|
||||
const mkdocsChildProcess = await runMkdocsServer({
|
||||
port: cmd.mkdocsPort,
|
||||
dockerImage: cmd.dockerImage,
|
||||
useDocker: cmd.docker,
|
||||
stdoutLogFunc: mkdocsLogFunc,
|
||||
stderrLogFunc: mkdocsLogFunc,
|
||||
});
|
||||
|
||||
// Wait until mkdocs server has started so that Backstage starts with docs loaded
|
||||
// Takes 1-5 seconds
|
||||
for (let attempt = 0; attempt < 10; attempt++) {
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
if (mkdocsServerHasStarted) {
|
||||
break;
|
||||
}
|
||||
logger.info('Waiting for mkdocs server to start...');
|
||||
}
|
||||
|
||||
if (!mkdocsServerHasStarted) {
|
||||
logger.error(
|
||||
'mkdocs server did not start. Exiting. Try re-running command with -v option for more details.',
|
||||
);
|
||||
}
|
||||
|
||||
// Run the embedded-techdocs Backstage app
|
||||
const techdocsPreviewBundlePath = path.join(
|
||||
path.dirname(require.resolve('@techdocs/cli/package.json')),
|
||||
'dist',
|
||||
'techdocs-preview-bundle',
|
||||
);
|
||||
|
||||
const httpServer = new HTTPServer(
|
||||
techdocsPreviewBundlePath,
|
||||
isDevMode ? backstageBackendPort : backstagePort,
|
||||
cmd.mkdocsPort,
|
||||
cmd.verbose,
|
||||
);
|
||||
|
||||
httpServer
|
||||
.serve()
|
||||
.catch(err => {
|
||||
logger.error(err);
|
||||
mkdocsChildProcess.kill();
|
||||
process.exit(1);
|
||||
})
|
||||
.then(() => {
|
||||
// The last three things default/component/local/ don't matter. They can be anything.
|
||||
openBrowser(
|
||||
`http://localhost:${backstagePort}/docs/default/component/local/`,
|
||||
);
|
||||
logger.info(
|
||||
`Serving docs in Backstage at http://localhost:${backstagePort}/docs/default/component/local/\nOpening browser.`,
|
||||
);
|
||||
});
|
||||
|
||||
await waitForSignal([mkdocsChildProcess]);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
const PROJECT_ROOT_DIR = path.resolve(__dirname, '..');
|
||||
const FIXTURE_DIR = path.resolve(PROJECT_ROOT_DIR, 'src/fixture');
|
||||
|
||||
describe('end-to-end', () => {
|
||||
it('shows help text', async () => {
|
||||
jest.setTimeout(10000);
|
||||
const proc = await executeTechDocsCliCommand(['--help']);
|
||||
|
||||
expect(proc.combinedStdOutErr).toContain('Usage: techdocs-cli [options]');
|
||||
expect(proc.exit).toEqual(0);
|
||||
});
|
||||
|
||||
it('can generate', async () => {
|
||||
jest.setTimeout(10000);
|
||||
const proc = await executeTechDocsCliCommand(['generate', '--no-docker'], {
|
||||
cwd: FIXTURE_DIR,
|
||||
killAfter: 8000,
|
||||
});
|
||||
|
||||
expect(proc.combinedStdOutErr).toContain('Successfully generated docs');
|
||||
expect(proc.exit).toEqual(0);
|
||||
});
|
||||
|
||||
it('can serve in mkdocs', async () => {
|
||||
jest.setTimeout(10000);
|
||||
const proc = await executeTechDocsCliCommand(
|
||||
['serve:mkdocs', '--no-docker'],
|
||||
{
|
||||
cwd: FIXTURE_DIR,
|
||||
killAfter: 8000,
|
||||
},
|
||||
);
|
||||
|
||||
expect(proc.combinedStdOutErr).toContain('Starting mkdocs server');
|
||||
expect(proc.exit).toEqual(0);
|
||||
});
|
||||
|
||||
it('can serve in backstage', async () => {
|
||||
jest.setTimeout(10000);
|
||||
const proc = await executeTechDocsCliCommand(
|
||||
['serve', '--no-docker', '--mkdocs-port=8888'],
|
||||
{
|
||||
cwd: FIXTURE_DIR,
|
||||
killAfter: 8000,
|
||||
},
|
||||
);
|
||||
|
||||
expect(proc.combinedStdOutErr).toContain('Starting mkdocs server');
|
||||
expect(proc.combinedStdOutErr).toContain('Serving docs in Backstage at');
|
||||
expect(proc.exit).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
type CommandResponse = {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
combinedStdOutErr: string;
|
||||
exit: number;
|
||||
};
|
||||
|
||||
type ExecuteCommandOptions = {
|
||||
killAfter?: number;
|
||||
cwd?: string;
|
||||
};
|
||||
|
||||
function executeTechDocsCliCommand(
|
||||
args: string[],
|
||||
opts: ExecuteCommandOptions = {},
|
||||
): Promise<CommandResponse> {
|
||||
return new Promise(resolve => {
|
||||
const pathToCli = path.resolve(PROJECT_ROOT_DIR, 'bin/techdocs-cli');
|
||||
const commandResponse = {
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
combinedStdOutErr: '',
|
||||
exit: 0,
|
||||
};
|
||||
|
||||
const listen = spawn(pathToCli, args, {
|
||||
cwd: opts.cwd,
|
||||
});
|
||||
|
||||
const stdOutChunks: any[] = [];
|
||||
const stdErrChunks: any[] = [];
|
||||
const combinedChunks: any[] = [];
|
||||
|
||||
listen.stdout.on('data', data => {
|
||||
stdOutChunks.push(data);
|
||||
combinedChunks.push(data);
|
||||
});
|
||||
|
||||
listen.stderr.on('data', data => {
|
||||
stdErrChunks.push(data);
|
||||
combinedChunks.push(data);
|
||||
});
|
||||
|
||||
listen.on('exit', code => {
|
||||
commandResponse.exit = code as number;
|
||||
commandResponse.stdout = Buffer.concat(stdOutChunks).toString('utf8');
|
||||
commandResponse.stderr = Buffer.concat(stdErrChunks).toString('utf8');
|
||||
commandResponse.combinedStdOutErr =
|
||||
Buffer.concat(combinedChunks).toString('utf8');
|
||||
resolve(commandResponse);
|
||||
});
|
||||
|
||||
if (opts.killAfter) {
|
||||
setTimeout(() => {
|
||||
listen.kill('SIGTERM');
|
||||
}, opts.killAfter);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
# Test Fixture
|
||||
@@ -0,0 +1,8 @@
|
||||
site_name: docs-test-fixture
|
||||
site_description: Documentation site test fixture
|
||||
|
||||
nav:
|
||||
- HOME: README.md
|
||||
|
||||
plugins:
|
||||
- techdocs-core
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import program from 'commander';
|
||||
import { registerCommands } from './commands';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const main = (argv: string[]) => {
|
||||
program.name('techdocs-cli').version(version);
|
||||
|
||||
registerCommands(program);
|
||||
|
||||
program.parse(argv);
|
||||
};
|
||||
|
||||
main(process.argv);
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { PublisherConfig } from './PublisherConfig';
|
||||
|
||||
describe('getValidPublisherConfig', () => {
|
||||
it('should not allow unknown publisher types', () => {
|
||||
const invalidConfig = {
|
||||
publisherType: 'unknown publisher',
|
||||
} as unknown as Command;
|
||||
|
||||
expect(() => PublisherConfig.getValidConfig(invalidConfig)).toThrowError(
|
||||
`Unknown publisher type ${invalidConfig.publisherType}`,
|
||||
);
|
||||
});
|
||||
|
||||
describe('for azureBlobStorage', () => {
|
||||
it('should require --azureAccountName', () => {
|
||||
const config = {
|
||||
publisherType: 'azureBlobStorage',
|
||||
} as unknown as Command;
|
||||
|
||||
expect(() => PublisherConfig.getValidConfig(config)).toThrowError(
|
||||
'azureBlobStorage requires --azureAccountName to be specified',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return valid ConfigReader', () => {
|
||||
const config = {
|
||||
publisherType: 'azureBlobStorage',
|
||||
azureAccountName: 'someAccountName',
|
||||
storageName: 'someContainer',
|
||||
} as unknown as Command;
|
||||
|
||||
const actualConfig = PublisherConfig.getValidConfig(config);
|
||||
expect(actualConfig.getString('techdocs.publisher.type')).toBe(
|
||||
'azureBlobStorage',
|
||||
);
|
||||
expect(
|
||||
actualConfig.getString(
|
||||
'techdocs.publisher.azureBlobStorage.containerName',
|
||||
),
|
||||
).toBe('someContainer');
|
||||
expect(
|
||||
actualConfig.getString(
|
||||
'techdocs.publisher.azureBlobStorage.credentials.accountName',
|
||||
),
|
||||
).toBe('someAccountName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('for awsS3', () => {
|
||||
it('should return valid ConfigReader', () => {
|
||||
const config = {
|
||||
publisherType: 'awsS3',
|
||||
storageName: 'someStorageName',
|
||||
} as unknown as Command;
|
||||
|
||||
const actualConfig = PublisherConfig.getValidConfig(config);
|
||||
expect(actualConfig.getString('techdocs.publisher.type')).toBe('awsS3');
|
||||
expect(
|
||||
actualConfig.getString('techdocs.publisher.awsS3.bucketName'),
|
||||
).toBe('someStorageName');
|
||||
});
|
||||
});
|
||||
|
||||
describe('for openStackSwift', () => {
|
||||
it('should throw error on missing parameters', () => {
|
||||
const config = {
|
||||
publisherType: 'openStackSwift',
|
||||
osCredentialId: 'someCredentialId',
|
||||
osSecret: 'someSecret',
|
||||
} as unknown as Command;
|
||||
|
||||
expect(() => PublisherConfig.getValidConfig(config)).toThrowError(
|
||||
`openStackSwift requires the following params to be specified: ${[
|
||||
'osAuthUrl',
|
||||
'osSwiftUrl',
|
||||
].join(', ')}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return valid ConfigReader', () => {
|
||||
const config = {
|
||||
publisherType: 'openStackSwift',
|
||||
storageName: 'someStorageName',
|
||||
osCredentialId: 'someCredentialId',
|
||||
osSecret: 'someSecret',
|
||||
osAuthUrl: 'someAuthUrl',
|
||||
osSwiftUrl: 'someSwiftUrl',
|
||||
} as unknown as Command;
|
||||
|
||||
const actualConfig = PublisherConfig.getValidConfig(config);
|
||||
expect(actualConfig.getString('techdocs.publisher.type')).toBe(
|
||||
'openStackSwift',
|
||||
);
|
||||
expect(
|
||||
actualConfig.getConfig('techdocs.publisher.openStackSwift').get(),
|
||||
).toMatchObject({
|
||||
containerName: 'someStorageName',
|
||||
credentials: {
|
||||
id: 'someCredentialId',
|
||||
secret: 'someSecret',
|
||||
},
|
||||
authUrl: 'someAuthUrl',
|
||||
swiftUrl: 'someSwiftUrl',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('for googleGcs', () => {
|
||||
it('should return valid ConfigReader', () => {
|
||||
const config = {
|
||||
publisherType: 'googleGcs',
|
||||
storageName: 'someStorageName',
|
||||
} as unknown as Command;
|
||||
|
||||
const actualConfig = PublisherConfig.getValidConfig(config);
|
||||
expect(actualConfig.getString('techdocs.publisher.type')).toBe(
|
||||
'googleGcs',
|
||||
);
|
||||
expect(
|
||||
actualConfig.getString('techdocs.publisher.googleGcs.bucketName'),
|
||||
).toBe('someStorageName');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Command } from 'commander';
|
||||
|
||||
type Publisher = keyof typeof PublisherConfig['configFactories'];
|
||||
type PublisherConfiguration = {
|
||||
[p in Publisher]?: any;
|
||||
} & {
|
||||
type: Publisher;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper when working with publisher-related configurations.
|
||||
*/
|
||||
export class PublisherConfig {
|
||||
/**
|
||||
* Maps publisher-specific config keys to config getters.
|
||||
*/
|
||||
private static configFactories = {
|
||||
awsS3: PublisherConfig.getValidAwsS3Config,
|
||||
azureBlobStorage: PublisherConfig.getValidAzureConfig,
|
||||
googleGcs: PublisherConfig.getValidGoogleGcsConfig,
|
||||
openStackSwift: PublisherConfig.getValidOpenStackSwiftConfig,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns Backstage config suitable for use when instantiating a Publisher. If
|
||||
* there are any missing or invalid options provided, an error is thrown.
|
||||
*
|
||||
* Note: This assumes that proper credentials are set in Environment
|
||||
* variables for the respective GCS/AWS clients to work.
|
||||
*/
|
||||
static getValidConfig(cmd: Command): ConfigReader {
|
||||
const publisherType = cmd.publisherType;
|
||||
|
||||
if (!PublisherConfig.isKnownPublisher(publisherType)) {
|
||||
throw new Error(`Unknown publisher type ${cmd.publisherType}`);
|
||||
}
|
||||
|
||||
return new ConfigReader({
|
||||
// This backend config is not used at all. Just something needed a create a mock discovery instance.
|
||||
backend: {
|
||||
baseUrl: 'http://localhost:7000',
|
||||
listen: {
|
||||
port: 7000,
|
||||
},
|
||||
},
|
||||
techdocs: {
|
||||
publisher: PublisherConfig.configFactories[publisherType](cmd),
|
||||
legacyUseCaseSensitiveTripletPaths:
|
||||
cmd.legacyUseCaseSensitiveTripletPaths,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Typeguard to ensure the publisher has a known config getter.
|
||||
*/
|
||||
private static isKnownPublisher(
|
||||
type: string,
|
||||
): type is keyof typeof PublisherConfig['configFactories'] {
|
||||
return PublisherConfig.configFactories.hasOwnProperty(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve valid AWS S3 configuration based on the command.
|
||||
*/
|
||||
private static getValidAwsS3Config(cmd: Command): PublisherConfiguration {
|
||||
return {
|
||||
type: 'awsS3',
|
||||
awsS3: {
|
||||
bucketName: cmd.storageName,
|
||||
...(cmd.awsRoleArn && { credentials: { roleArn: cmd.awsRoleArn } }),
|
||||
...(cmd.awsEndpoint && { endpoint: cmd.awsEndpoint }),
|
||||
...(cmd.awsS3ForcePathStyle && { s3ForcePathStyle: true }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve valid Azure Blob Storage configuration based on the command.
|
||||
*/
|
||||
private static getValidAzureConfig(cmd: Command): PublisherConfiguration {
|
||||
if (!cmd.azureAccountName) {
|
||||
throw new Error(
|
||||
`azureBlobStorage requires --azureAccountName to be specified`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'azureBlobStorage',
|
||||
azureBlobStorage: {
|
||||
containerName: cmd.storageName,
|
||||
credentials: {
|
||||
accountName: cmd.azureAccountName,
|
||||
accountKey: cmd.azureAccountKey,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve valid GCS configuration based on the command.
|
||||
*/
|
||||
private static getValidGoogleGcsConfig(cmd: Command): PublisherConfiguration {
|
||||
return {
|
||||
type: 'googleGcs',
|
||||
googleGcs: {
|
||||
bucketName: cmd.storageName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves valid OpenStack Swift configuration based on the command.
|
||||
*/
|
||||
private static getValidOpenStackSwiftConfig(
|
||||
cmd: Command,
|
||||
): PublisherConfiguration {
|
||||
const missingParams = [
|
||||
'osCredentialId',
|
||||
'osSecret',
|
||||
'osAuthUrl',
|
||||
'osSwiftUrl',
|
||||
].filter((param: string) => !cmd[param]);
|
||||
|
||||
if (missingParams.length) {
|
||||
throw new Error(
|
||||
`openStackSwift requires the following params to be specified: ${missingParams.join(
|
||||
', ',
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'openStackSwift',
|
||||
openStackSwift: {
|
||||
containerName: cmd.storageName,
|
||||
credentials: {
|
||||
id: cmd.osCredentialId,
|
||||
secret: cmd.osSecret,
|
||||
},
|
||||
authUrl: cmd.osAuthUrl,
|
||||
swiftUrl: cmd.osSwiftUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import serveHandler from 'serve-handler';
|
||||
import http from 'http';
|
||||
import httpProxy from 'http-proxy';
|
||||
import { createLogger } from './utility';
|
||||
|
||||
export default class HTTPServer {
|
||||
private readonly proxyEndpoint: string;
|
||||
private readonly backstageBundleDir: string;
|
||||
private readonly backstagePort: number;
|
||||
private readonly mkdocsPort: number;
|
||||
private readonly verbose: boolean;
|
||||
|
||||
constructor(
|
||||
backstageBundleDir: string,
|
||||
backstagePort: number,
|
||||
mkdocsPort: number,
|
||||
verbose: boolean,
|
||||
) {
|
||||
this.proxyEndpoint = '/api/';
|
||||
this.backstageBundleDir = backstageBundleDir;
|
||||
this.backstagePort = backstagePort;
|
||||
this.mkdocsPort = mkdocsPort;
|
||||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
// Create a Proxy for mkdocs server
|
||||
private createProxy() {
|
||||
const proxy = httpProxy.createProxyServer({
|
||||
target: `http://localhost:${this.mkdocsPort}`,
|
||||
});
|
||||
|
||||
return (request: http.IncomingMessage): [httpProxy, string] => {
|
||||
// If the request goes to /api/ we want to remove /api/ from the prefix of the request URL.
|
||||
// e.g. ['/', 'api', pathChunks]
|
||||
const [, , ...pathChunks] = request.url?.split('/') ?? [];
|
||||
const forwardPath = pathChunks.join('/');
|
||||
|
||||
return [proxy, forwardPath];
|
||||
};
|
||||
}
|
||||
|
||||
public async serve(): Promise<http.Server> {
|
||||
return new Promise<http.Server>((resolve, reject) => {
|
||||
const proxyHandler = this.createProxy();
|
||||
const server = http.createServer(
|
||||
(request: http.IncomingMessage, response: http.ServerResponse) => {
|
||||
if (request.url?.startsWith(this.proxyEndpoint)) {
|
||||
const [proxy, forwardPath] = proxyHandler(request);
|
||||
|
||||
proxy.on('error', (error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
response.setHeader('Access-Control-Allow-Origin', '*');
|
||||
response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||
|
||||
request.url = forwardPath;
|
||||
return proxy.web(request, response);
|
||||
}
|
||||
|
||||
return serveHandler(request, response, {
|
||||
public: this.backstageBundleDir,
|
||||
trailingSlash: true,
|
||||
rewrites: [{ source: '**', destination: 'index.html' }],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const logger = createLogger({ verbose: false });
|
||||
server.listen(this.backstagePort, () => {
|
||||
if (this.verbose) {
|
||||
logger.info(
|
||||
`[techdocs-preview-bundle] Running local version of Backstage at http://localhost:${this.backstagePort}`,
|
||||
);
|
||||
}
|
||||
resolve(server);
|
||||
});
|
||||
|
||||
server.on('error', (error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { runMkdocsServer } from './mkdocsServer';
|
||||
import { run } from './run';
|
||||
|
||||
jest.mock('./run', () => {
|
||||
return {
|
||||
run: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('runMkdocsServer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('docker', () => {
|
||||
it('should run docker directly by default', async () => {
|
||||
await runMkdocsServer({});
|
||||
|
||||
const quotedCwd = `"${process.cwd()}":/content`;
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining([
|
||||
'run',
|
||||
quotedCwd,
|
||||
'8000:8000',
|
||||
'serve',
|
||||
'--dev-addr',
|
||||
'0.0.0.0:8000',
|
||||
'spotify/techdocs',
|
||||
]),
|
||||
expect.objectContaining({}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept port option', async () => {
|
||||
await runMkdocsServer({ port: '5678' });
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining(['5678:5678', '0.0.0.0:5678']),
|
||||
expect.objectContaining({}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept custom docker image', async () => {
|
||||
await runMkdocsServer({ dockerImage: 'my-org/techdocs' });
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'docker',
|
||||
expect.arrayContaining(['my-org/techdocs']),
|
||||
expect.objectContaining({}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mkdocs', () => {
|
||||
it('should run mkdocs if specified', async () => {
|
||||
await runMkdocsServer({ useDocker: false });
|
||||
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'mkdocs',
|
||||
expect.arrayContaining(['serve', '--dev-addr', '127.0.0.1:8000']),
|
||||
expect.objectContaining({}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept port option', async () => {
|
||||
await runMkdocsServer({ useDocker: false, port: '5678' });
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
'mkdocs',
|
||||
expect.arrayContaining(['127.0.0.1:5678']),
|
||||
expect.objectContaining({}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChildProcess } from 'child_process';
|
||||
import { run, LogFunc } from './run';
|
||||
|
||||
export const runMkdocsServer = async (options: {
|
||||
port?: string;
|
||||
useDocker?: boolean;
|
||||
dockerImage?: string;
|
||||
stdoutLogFunc?: LogFunc;
|
||||
stderrLogFunc?: LogFunc;
|
||||
}): Promise<ChildProcess> => {
|
||||
const port = options.port ?? '8000';
|
||||
const useDocker = options.useDocker ?? true;
|
||||
const dockerImage = options.dockerImage ?? 'spotify/techdocs';
|
||||
|
||||
if (useDocker) {
|
||||
return await run(
|
||||
'docker',
|
||||
[
|
||||
'run',
|
||||
'--rm',
|
||||
'-w',
|
||||
'/content',
|
||||
'-v',
|
||||
`"${process.cwd()}":/content`,
|
||||
'-p',
|
||||
`${port}:${port}`,
|
||||
dockerImage,
|
||||
'serve',
|
||||
'--dev-addr',
|
||||
`0.0.0.0:${port}`,
|
||||
],
|
||||
{
|
||||
stdoutLogFunc: options.stdoutLogFunc,
|
||||
stderrLogFunc: options.stderrLogFunc,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return await run('mkdocs', ['serve', '--dev-addr', `127.0.0.1:${port}`], {
|
||||
stdoutLogFunc: options.stdoutLogFunc,
|
||||
stderrLogFunc: options.stderrLogFunc,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { spawn, SpawnOptions, ChildProcess } from 'child_process';
|
||||
|
||||
export type LogFunc = (data: Buffer | string) => void;
|
||||
type SpawnOptionsPartialEnv = Omit<SpawnOptions, 'env'> & {
|
||||
env?: Partial<NodeJS.ProcessEnv>;
|
||||
// Pipe stdout to this log function
|
||||
stdoutLogFunc?: LogFunc;
|
||||
// Pipe stderr to this log function
|
||||
stderrLogFunc?: LogFunc;
|
||||
};
|
||||
|
||||
// TODO: Accept log functions to pipe logs with.
|
||||
// Runs a child command, returning the child process instance.
|
||||
// Use it along with waitForSignal to run a long running process e.g. mkdocs serve
|
||||
export const run = async (
|
||||
name: string,
|
||||
args: string[] = [],
|
||||
options: SpawnOptionsPartialEnv = {},
|
||||
): Promise<ChildProcess> => {
|
||||
const { stdoutLogFunc, stderrLogFunc } = options;
|
||||
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
FORCE_COLOR: 'true',
|
||||
...(options.env ?? {}),
|
||||
};
|
||||
|
||||
// Refer: https://nodejs.org/api/child_process.html#child_process_subprocess_stdio
|
||||
const stdio = [
|
||||
'inherit',
|
||||
stdoutLogFunc ? 'pipe' : 'inherit',
|
||||
stderrLogFunc ? 'pipe' : 'inherit',
|
||||
] as ('inherit' | 'pipe')[];
|
||||
|
||||
const childProcess = spawn(name, args, {
|
||||
stdio: stdio,
|
||||
shell: true,
|
||||
...options,
|
||||
env,
|
||||
});
|
||||
|
||||
if (stdoutLogFunc && childProcess.stdout) {
|
||||
childProcess.stdout.on('data', stdoutLogFunc);
|
||||
}
|
||||
if (stderrLogFunc && childProcess.stderr) {
|
||||
childProcess.stderr.on('data', stderrLogFunc);
|
||||
}
|
||||
|
||||
return childProcess;
|
||||
};
|
||||
|
||||
// Block indefinitely and wait for a signal to kill the child process(es)
|
||||
// Throw error if any child process errors
|
||||
// Resolves only when all processes exit with status code 0
|
||||
export async function waitForSignal(
|
||||
childProcesses: Array<ChildProcess>,
|
||||
): Promise<void> {
|
||||
const promises: Array<Promise<void>> = [];
|
||||
|
||||
childProcesses.forEach(childProcess => {
|
||||
if (typeof childProcess.exitCode === 'number') {
|
||||
if (childProcess.exitCode) {
|
||||
throw new Error(`Non zero exit code from child process`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
||||
process.on(signal, () => {
|
||||
childProcess.kill(signal);
|
||||
// exit instead of resolve. The process is shutting down and resolving a promise here logs an error
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
promises.push(
|
||||
new Promise<void>((resolve, reject) => {
|
||||
childProcess.once('error', error => reject(error));
|
||||
childProcess.once('exit', code => {
|
||||
if (code) {
|
||||
reject(new Error(`Non zero exit code from child process`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
RemoteProtocol,
|
||||
ParsedLocationAnnotation,
|
||||
} from '@backstage/techdocs-common';
|
||||
import * as winston from 'winston';
|
||||
|
||||
export const convertTechDocsRefToLocationAnnotation = (
|
||||
techdocsRef: string,
|
||||
): ParsedLocationAnnotation => {
|
||||
// Split on the first colon for the protocol and the rest after the first split
|
||||
// is the location.
|
||||
const [type, target] = techdocsRef.split(/:(.+)/) as [
|
||||
RemoteProtocol?,
|
||||
string?,
|
||||
];
|
||||
|
||||
if (!type || !target) {
|
||||
throw new Error(
|
||||
`Can not parse --techdocs-ref ${techdocsRef}. Should be of type HOST:URL.`,
|
||||
);
|
||||
}
|
||||
|
||||
return { type, target };
|
||||
};
|
||||
|
||||
export const createLogger = ({
|
||||
verbose = false,
|
||||
}: {
|
||||
verbose: boolean;
|
||||
}): winston.Logger => {
|
||||
const logger = winston.createLogger({
|
||||
level: verbose ? 'verbose' : 'info',
|
||||
transports: [
|
||||
new winston.transports.Console({ format: winston.format.simple() }),
|
||||
],
|
||||
});
|
||||
|
||||
return logger;
|
||||
};
|
||||
@@ -99,6 +99,7 @@ const SKIPPED_PACKAGES = [
|
||||
join('packages', 'codemods'),
|
||||
join('packages', 'create-app'),
|
||||
join('packages', 'e2e-test'),
|
||||
join('packages', 'embedded-techdocs-app'),
|
||||
join('packages', 'storybook'),
|
||||
join('packages', 'techdocs-cli'),
|
||||
];
|
||||
|
||||
@@ -6825,6 +6825,14 @@
|
||||
dependencies:
|
||||
classnames "*"
|
||||
|
||||
"@types/clean-css@*":
|
||||
version "4.2.5"
|
||||
resolved "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.5.tgz#69ce62cc13557c90ca40460133f672dc52ceaf89"
|
||||
integrity sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
source-map "^0.6.0"
|
||||
|
||||
"@types/codemirror@^0.0.108":
|
||||
version "0.0.108"
|
||||
resolved "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.108.tgz#e640422b666bf49251b384c390cdeb2362585bde"
|
||||
@@ -6856,6 +6864,13 @@
|
||||
resolved "https://registry.npmjs.org/@types/command-exists/-/command-exists-1.2.0.tgz#d97e0ed10097090e4ab0367ed425b0312fad86f3"
|
||||
integrity sha512-ugsxEJfsCuqMLSuCD4PIJkp5Uk2z6TCMRCgYVuhRo5cYQY3+1xXTQkSlPtkpGHuvWMjS2KTeVQXxkXRACMbM6A==
|
||||
|
||||
"@types/commander@^2.12.2":
|
||||
version "2.12.2"
|
||||
resolved "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae"
|
||||
integrity sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==
|
||||
dependencies:
|
||||
commander "*"
|
||||
|
||||
"@types/compression@^1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.npmjs.org/@types/compression/-/compression-1.7.0.tgz#8dc2a56604873cf0dd4e746d9ae4d31ae77b2390"
|
||||
@@ -7112,6 +7127,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/fs-extra@^9.0.6":
|
||||
version "9.0.13"
|
||||
resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
|
||||
integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/git-url-parse@^9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.0.tgz#aac1315a44fa4ed5a52c3820f6c3c2fb79cbd12d"
|
||||
@@ -7169,6 +7191,24 @@
|
||||
resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880"
|
||||
integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==
|
||||
|
||||
"@types/html-minifier@*":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-4.0.1.tgz#9486ffc144f8d7b8f75b07939c500ac3d73617a0"
|
||||
integrity sha512-6u58FWQbWP45bsxVeMJo0yk2LEsjjZsCwn0JDe/i5Edk3L+b9TR5eZ2FGaMCrLdtGYpME5AGxUqv8o+3hWKogw==
|
||||
dependencies:
|
||||
"@types/clean-css" "*"
|
||||
"@types/relateurl" "*"
|
||||
"@types/uglify-js" "*"
|
||||
|
||||
"@types/html-webpack-plugin@*":
|
||||
version "3.2.6"
|
||||
resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.6.tgz#07951aaf0fa260dbf626f9644f1d13106d537625"
|
||||
integrity sha512-U8uJSvlf9lwrKG6sKFnMhqY4qJw2QXad+PHlX9sqEXVUMilVt96aVvFde73tzsdXD+QH9JS6kEytuGO2JcYZog==
|
||||
dependencies:
|
||||
"@types/html-minifier" "*"
|
||||
"@types/tapable" "^1"
|
||||
"@types/webpack" "^4"
|
||||
|
||||
"@types/http-assert@*":
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b"
|
||||
@@ -7692,6 +7732,17 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dev-utils@^9.0.4":
|
||||
version "9.0.8"
|
||||
resolved "https://registry.npmjs.org/@types/react-dev-utils/-/react-dev-utils-9.0.8.tgz#7e4d63d1e1c71cd236c9055bc0c0dbaa3772bcf9"
|
||||
integrity sha512-H/R8BvtCf9BUWPLL9a2agUWWBOKQQPkBIe5osdrgGaDJHZggQRiNN3emH16rQkzm5zi6TVuslOFrYrfMx+QTjw==
|
||||
dependencies:
|
||||
"@types/eslint" "*"
|
||||
"@types/express" "*"
|
||||
"@types/html-webpack-plugin" "*"
|
||||
"@types/webpack" "^4"
|
||||
"@types/webpack-dev-server" "^3"
|
||||
|
||||
"@types/react-dom@*", "@types/react-dom@>=16.9.0":
|
||||
version "17.0.9"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
|
||||
@@ -7802,6 +7853,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/regression/-/regression-2.0.2.tgz#a1ad747fbcc6726643a8eb2c42bb804bbf34ce02"
|
||||
integrity sha512-i7KOGl6xdkfpq5+p2ooC+/XFIRUMkYymZ29SD8p+Ko9lesKGUsh6860ey3YM7Y+ZG7kEDGcjzyLO3sOhozqEeA==
|
||||
|
||||
"@types/relateurl@*":
|
||||
version "0.2.29"
|
||||
resolved "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.29.tgz#68ccecec3d4ffdafb9c577fe764f912afc050fe6"
|
||||
integrity sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg==
|
||||
|
||||
"@types/request@^2.47.1":
|
||||
version "2.48.5"
|
||||
resolved "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz#019b8536b402069f6d11bee1b2c03e7f232937a0"
|
||||
@@ -7863,6 +7919,13 @@
|
||||
resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.8.tgz#508a27995498d7586dcecd77c25e289bfaf90c59"
|
||||
integrity sha512-D/2EJvAlCEtYFEYmmlGwbGXuK886HzyCc3nZX/tkFTQdEU8jZDAgiv08P162yB17y4ZXZoq7yFAnW4GDBb9Now==
|
||||
|
||||
"@types/serve-handler@^6.1.0":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.1.tgz#629dc9a62b201ab79a216e1e46e162aa4c8d1455"
|
||||
integrity sha512-bIwSmD+OV8w0t2e7EWsuQYlGoS1o5aEdVktgkXaa43Zm0qVWi21xaSRb3DQA1UXD+DJ5bRq1Rgu14ZczB+CjIQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/serve-static@*":
|
||||
version "1.13.9"
|
||||
resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e"
|
||||
@@ -8076,6 +8139,17 @@
|
||||
"@types/expect" "^1.20.4"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/webpack-dev-server@^3":
|
||||
version "3.11.6"
|
||||
resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.6.tgz#d8888cfd2f0630203e13d3ed7833a4d11b8a34dc"
|
||||
integrity sha512-XCph0RiiqFGetukCTC3KVnY1jwLcZ84illFRMbyFzCcWl90B/76ew0tSqF46oBhnLC4obNDG7dMO0JfTN0MgMQ==
|
||||
dependencies:
|
||||
"@types/connect-history-api-fallback" "*"
|
||||
"@types/express" "*"
|
||||
"@types/serve-static" "*"
|
||||
"@types/webpack" "^4"
|
||||
http-proxy-middleware "^1.0.0"
|
||||
|
||||
"@types/webpack-dev-server@^3.11.5":
|
||||
version "3.11.5"
|
||||
resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.5.tgz#f4a254a3dd0667c8ee4af90d42afdb4ad1d607f3"
|
||||
@@ -8092,6 +8166,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.0.tgz#8c0a9435dfa7b3b1be76562f3070efb3f92637b4"
|
||||
integrity sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw==
|
||||
|
||||
"@types/webpack-env@^1.15.3":
|
||||
version "1.16.3"
|
||||
resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"
|
||||
integrity sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw==
|
||||
|
||||
"@types/webpack-sources@*":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.6.tgz#3d21dfc2ec0ad0c77758e79362426a9ba7d7cbcb"
|
||||
@@ -11289,6 +11368,11 @@ command-exists@^1.2.9:
|
||||
resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
|
||||
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
|
||||
|
||||
commander@*:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
|
||||
commander@7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
|
||||
@@ -11507,6 +11591,11 @@ contains-path@^0.1.0:
|
||||
resolved "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
|
||||
integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
|
||||
|
||||
content-disposition@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
|
||||
integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
|
||||
|
||||
content-disposition@0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
||||
@@ -14364,6 +14453,13 @@ fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3:
|
||||
resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53"
|
||||
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
|
||||
|
||||
fast-url-parser@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
|
||||
integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=
|
||||
dependencies:
|
||||
punycode "^1.3.2"
|
||||
|
||||
fastest-stable-stringify@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
|
||||
@@ -20342,6 +20438,18 @@ mime-db@1.49.0:
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d"
|
||||
integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==
|
||||
|
||||
mime-db@~1.33.0:
|
||||
version "1.33.0"
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
|
||||
integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
|
||||
|
||||
mime-types@2.1.18:
|
||||
version "2.1.18"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
|
||||
integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
|
||||
dependencies:
|
||||
mime-db "~1.33.0"
|
||||
|
||||
mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.32"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
|
||||
@@ -22227,7 +22335,7 @@ path-is-absolute@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
path-is-inside@^1.0.2:
|
||||
path-is-inside@1.0.2, path-is-inside@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||
@@ -22264,6 +22372,11 @@ path-to-regexp@0.1.7:
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
path-to-regexp@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
|
||||
integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
@@ -23428,7 +23541,7 @@ punycode@1.3.2:
|
||||
resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
|
||||
|
||||
punycode@^1.2.4:
|
||||
punycode@^1.2.4, punycode@^1.3.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||
@@ -23577,6 +23690,11 @@ randomfill@^1.0.3:
|
||||
randombytes "^2.0.5"
|
||||
safe-buffer "^5.1.0"
|
||||
|
||||
range-parser@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||
integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
|
||||
|
||||
range-parser@^1.2.1, range-parser@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||
@@ -25428,6 +25546,20 @@ serve-favicon@^2.5.0:
|
||||
parseurl "~1.3.2"
|
||||
safe-buffer "5.1.1"
|
||||
|
||||
serve-handler@^6.1.3:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
|
||||
integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
content-disposition "0.5.2"
|
||||
fast-url-parser "1.1.3"
|
||||
mime-types "2.1.18"
|
||||
minimatch "3.0.4"
|
||||
path-is-inside "1.0.2"
|
||||
path-to-regexp "2.2.1"
|
||||
range-parser "1.2.0"
|
||||
|
||||
serve-index@^1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||
|
||||