diff --git a/.changeset/warm-beds-flow.md b/.changeset/warm-beds-flow.md new file mode 100644 index 0000000000..7d34336739 --- /dev/null +++ b/.changeset/warm-beds-flow.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +Updated the HTTP server to allow for simplification of the development of the CLI itself. diff --git a/packages/techdocs-cli-embedded-app/app-config.dev.yaml b/packages/techdocs-cli-embedded-app/app-config.dev.yaml deleted file mode 100644 index 02d68c940e..0000000000 --- a/packages/techdocs-cli-embedded-app/app-config.dev.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# NOTE: This file is used for testing techdocs-cli locally - -app: - title: Techdocs Preview App - baseUrl: http://localhost:3000 - -backend: - baseUrl: http://localhost:7007 - -techdocs: - builder: 'external' - requestUrl: http://localhost:7007/api diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 6eb3119b06..2df050ef86 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -39,9 +39,8 @@ "start-server-and-test": "^1.10.11" }, "scripts": { - "start": "backstage-cli app:serve --config ./app-config.yaml --config ./app-config.dev.yaml", + "start": "backstage-cli app:serve --config ./app-config.yaml", "build": "backstage-cli app:build --config ./app-config.yaml", - "build:dev": "backstage-cli app:build --config ./app-config.dev.yaml", "clean": "backstage-cli clean", "test": "backstage-cli test", "lint": "backstage-cli lint", diff --git a/packages/techdocs-cli-embedded-app/src/App.test.tsx b/packages/techdocs-cli-embedded-app/src/App.test.tsx index f177433d95..75658d271e 100644 --- a/packages/techdocs-cli-embedded-app/src/App.test.tsx +++ b/packages/techdocs-cli-embedded-app/src/App.test.tsx @@ -18,25 +18,24 @@ import React from 'react'; import { renderWithEffects } from '@backstage/test-utils'; import App from './App'; +jest.mock('./config', () => ({ + configLoader: async () => [ + { + data: { + app: { title: 'Test' }, + backend: { baseUrl: 'http://localhost:7007' }, + techdocs: { + storageUrl: 'http://localhost:7007/api/techdocs/static/docs', + }, + }, + context: 'test', + }, + ], +})); + describe('App', () => { it('should render', async () => { - process.env = { - NODE_ENV: 'test', - APP_CONFIG: [ - { - data: { - app: { title: 'Test' }, - backend: { baseUrl: 'http://localhost:7007' }, - techdocs: { - storageUrl: 'http://localhost:7007/api/techdocs/static/docs', - }, - }, - context: 'test', - }, - ] as any, - }; - const rendered = await renderWithEffects(); - expect(rendered.baseElement).toBeInTheDocument(); + expect(rendered.getByText('Docs Preview')).toBeInTheDocument(); }); }); diff --git a/packages/techdocs-cli-embedded-app/src/App.tsx b/packages/techdocs-cli-embedded-app/src/App.tsx index 00b232d8e1..51bdfbbb11 100644 --- a/packages/techdocs-cli-embedded-app/src/App.tsx +++ b/packages/techdocs-cli-embedded-app/src/App.tsx @@ -29,9 +29,11 @@ import { apis } from './apis'; import { Root } from './components/Root'; import { techDocsPage } from './components/TechDocsPage'; import * as plugins from './plugins'; +import { configLoader } from './config'; const app = createApp({ apis, + configLoader, plugins: Object.values(plugins), }); diff --git a/packages/techdocs-cli-embedded-app/src/config.ts b/packages/techdocs-cli-embedded-app/src/config.ts new file mode 100644 index 0000000000..482ceb41ec --- /dev/null +++ b/packages/techdocs-cli-embedded-app/src/config.ts @@ -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 { defaultConfigLoader } from '@backstage/core-app-api'; + +const PRODUCTION_CONFIG = { + backend: { + baseUrl: 'http://localhost:3000', + }, + techdocs: { + builder: 'external', + requestUrl: 'http://localhost:3000/api', + }, +}; + +const DEVELOPMENT_CONFIG = { + backend: { + baseUrl: 'http://localhost:7007', + }, + techdocs: { + builder: 'external', + requestUrl: 'http://localhost:7007/api', + }, +}; + +async function isProductionServe() { + const res = await fetch('/.detect'); + if (!res.ok) { + return false; + } + const text = await res.text(); + return text.trim() === 'techdocs-cli-server'; +} + +export async function configLoader() { + const defaultConfigs = await defaultConfigLoader(); + const isProduction = await isProductionServe(); + + return [ + ...defaultConfigs, + { + context: 'detected', + data: isProduction ? PRODUCTION_CONFIG : DEVELOPMENT_CONFIG, + }, + ]; +} diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 8d2d952e84..2076e25bed 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -21,12 +21,13 @@ "types": "", "scripts": { "start": "nodemon --", - "build": "./scripts/build.sh", + "build": "backstage-cli build --outputs cjs", "clean": "backstage-cli clean", "lint": "backstage-cli lint", "test": "backstage-cli test --testPathIgnorePatterns=src/e2e.test.ts", "test:e2e": "backstage-cli test src/e2e.test.ts", - "test:e2e:ci": "backstage-cli test --watchAll=false --ci src/e2e.test.ts" + "test:e2e:ci": "backstage-cli test --watchAll=false --ci src/e2e.test.ts", + "prepack": "./scripts/prepack.sh" }, "bin": { "techdocs-cli": "bin/techdocs-cli" @@ -57,6 +58,7 @@ "dependencies": { "@backstage/backend-common": "^0.10.6", "@backstage/catalog-model": "^0.9.10", + "@backstage/cli-common": "^0.1.6", "@backstage/config": "^0.1.13", "@backstage/techdocs-common": "^0.11.6", "@types/dockerode": "^3.3.0", diff --git a/packages/techdocs-cli/scripts/build.sh b/packages/techdocs-cli/scripts/prepack.sh similarity index 54% rename from packages/techdocs-cli/scripts/build.sh rename to packages/techdocs-cli/scripts/prepack.sh index 31c2eae6c3..fa24b10167 100755 --- a/packages/techdocs-cli/scripts/build.sh +++ b/packages/techdocs-cli/scripts/prepack.sh @@ -20,25 +20,7 @@ SCRIPT_DIR=$(dirname $0) TECHDOCS_CLI_DIR="$SCRIPT_DIR"/.. TECHDOCS_CLI_EMBEDDED_APP_DIR="$TECHDOCS_CLI_DIR"/../techdocs-cli-embedded-app -compile_and_build_cli() { - echo "📄 Compiling..." - yarn workspace @techdocs/cli tsc > /dev/null - echo "📦️ Building..." - pushd $TECHDOCS_CLI_DIR > /dev/null - npx backstage-cli build --outputs cjs > /dev/null - popd > /dev/null -} - -build_and_embed_app() { - echo "🚚 Embedding app..." - if [ "$TECHDOCS_CLI_DEV_MODE" = "true" ] ; then - yarn workspace techdocs-cli-embedded-app build:dev > /dev/null - else - yarn workspace techdocs-cli-embedded-app build > /dev/null - fi - cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/techdocs-preview-bundle > /dev/null -} - -compile_and_build_cli -build_and_embed_app +echo "🚚 Copying embedded app into dist/embedded-app" +rm -r "$TECHDOCS_CLI_DIR"/dist/embedded-app +cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/embedded-app echo "🏁 Ready!" diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 4d71de3e52..a6a671c2df 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -17,11 +17,30 @@ import { Command } from 'commander'; import path from 'path'; import openBrowser from 'react-dev-utils/openBrowser'; +import { findPaths } from '@backstage/cli-common'; import HTTPServer from '../../lib/httpServer'; import { runMkdocsServer } from '../../lib/mkdocsServer'; import { LogFunc, waitForSignal } from '../../lib/run'; import { createLogger } from '../../lib/utility'; +function findPreviewBundlePath(): string { + try { + return path.join( + path.dirname(require.resolve('techdocs-cli-embedded-app/package.json')), + 'dist', + ); + } catch { + // If the techdocs-cli-embedded-app package is not available it means we're + // running a published package. For published packages the preview bundle is + // copied to dist/embedded-app be the prepack script. + // + // This can be tested by running `yarn pack` and extracting the resulting tarball into a directory. + // Within the extracted directory, run `npm install --only=prod`. + // Once that's done you can test the CLI in any directory using `node /package `. + return findPaths(__dirname).resolveOwn('dist/embedded-app'); + } +} + export default async function serve(cmd: Command) { const logger = createLogger({ verbose: cmd.verbose }); @@ -91,16 +110,9 @@ export default async function serve(cmd: Command) { ); } - // Run the embedded-techdocs Backstage app - const techdocsPreviewBundlePath = path.join( - path.dirname(require.resolve('@techdocs/cli/package.json')), - 'dist', - 'techdocs-preview-bundle', - ); - const port = isDevMode ? backstageBackendPort : backstagePort; const httpServer = new HTTPServer( - techdocsPreviewBundlePath, + findPreviewBundlePath(), port, cmd.mkdocsPort, cmd.verbose, diff --git a/packages/techdocs-cli/src/lib/httpServer.ts b/packages/techdocs-cli/src/lib/httpServer.ts index 0402ce4e69..92fc5abc87 100644 --- a/packages/techdocs-cli/src/lib/httpServer.ts +++ b/packages/techdocs-cli/src/lib/httpServer.ts @@ -71,10 +71,18 @@ export default class HTTPServer { response.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); request.url = forwardPath; - return proxy.web(request, response); + proxy.web(request, response); + return; } - return serveHandler(request, response, { + // This endpoint is used by the frontend to detect where the backend is running. + if (request.url === '/.detect') { + response.setHeader('Content-Type', 'text/plain'); + response.end('techdocs-cli-server'); + return; + } + + serveHandler(request, response, { public: this.backstageBundleDir, trailingSlash: true, rewrites: [{ source: '**', destination: 'index.html' }],