diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index 6ab208ad03..2268ea3fcc 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -61,7 +61,7 @@ beforeEach(() => { }); describe('GoogleGCSPublish', () => { - it('should publish a directory', () => { + it('should publish a directory', async () => { mockFs({ '/path/to/generatedDirectory': { 'index.html': '', @@ -73,8 +73,12 @@ describe('GoogleGCSPublish', () => { }); const entity = createMockEntity(); - return expect( - publisher.publish({ entity, directory: '/path/to/generatedDirectory' }), - ).resolves.toBeUndefined(); + expect( + await publisher.publish({ + entity, + directory: '/path/to/generatedDirectory', + }), + ).toBeUndefined(); + mockFs.restore(); }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index 21742a272e..5f8dc6a3fc 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -14,15 +14,15 @@ * limitations under the License. */ import express from 'express'; -import { Storage, UploadResponse } from '@google-cloud/storage'; +import { + Storage, + UploadResponse, + FileExistsResponse, +} from '@google-cloud/storage'; import { Logger } from 'winston'; import { Entity, EntityName } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { - getHeadersForFileExtension, - supportedFileType, - getFileTreeRecursively, -} from './helpers'; +import { getHeadersForFileExtension, getFileTreeRecursively } from './helpers'; import { PublisherBase, PublishRequest } from './types'; export class GoogleGCSPublish implements PublisherBase { @@ -168,9 +168,7 @@ export class GoogleGCSPublish implements PublisherBase { // Files with different extensions (CSS, HTML) need to be served with different headers const fileExtension = filePath.split('.')[filePath.split('.').length - 1]; - const responseHeaders = getHeadersForFileExtension( - fileExtension as supportedFileType, - ); + const responseHeaders = getHeadersForFileExtension(fileExtension); const fileStreamChunks: Array = []; this.storageClient @@ -208,12 +206,12 @@ export class GoogleGCSPublish implements PublisherBase { this.storageClient .bucket(this.bucketName) .file(`${entityRootDir}/index.html`) - .createReadStream() - .on('error', () => { - resolve(false); + .exists() + .then((response: FileExistsResponse) => { + resolve(response[0]); }) - .on('data', () => { - resolve(true); + .catch(() => { + resolve(false); }); }); } diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts index 2f8fbadae0..e4f0c12b79 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.test.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts @@ -14,7 +14,33 @@ * limitations under the License. */ import mockFs from 'mock-fs'; -import { getFileTreeRecursively } from './helpers'; +import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; + +describe('getHeadersForFileExtension', () => { + it('returns correct header for default extensions', () => { + const headers = getHeadersForFileExtension('xyz'); + const expectedHeaders = { + 'Content-Type': 'text/plain', + }; + expect(headers).toEqual(expectedHeaders); + }); + + it('returns correct header for html', () => { + const headers = getHeadersForFileExtension('html'); + const expectedHeaders = { + 'Content-Type': 'text/html; charset=UTF-8', + }; + expect(headers).toEqual(expectedHeaders); + }); + + it('returns correct header for css', () => { + const headers = getHeadersForFileExtension('css'); + const expectedHeaders = { + 'Content-Type': 'text/css; charset=UTF-8', + }; + expect(headers).toEqual(expectedHeaders); + }); +}); describe('getFileTreeRecursively', () => { beforeEach(() => { diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts index 2939bbfc1c..38ff27e0a0 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.ts @@ -15,14 +15,17 @@ */ import recursiveReadDir from 'recursive-readdir'; -export type supportedFileType = 'html' | 'css'; - export type responseHeadersType = { 'Content-Type': string; }; +/** + * Some files need special headers to be used correctly by the frontend. This function + * generates headers in the response to those file requests. + * @param {string} fileExtension html, css, js etc. + */ export const getHeadersForFileExtension = ( - fileType: supportedFileType, + fileExtension: string, ): responseHeadersType => { const headersCommon = { 'Content-Type': 'text/plain', @@ -37,7 +40,7 @@ export const getHeadersForFileExtension = ( 'Content-Type': 'text/css; charset=UTF-8', }; - switch (fileType) { + switch (fileExtension) { case 'html': return headersHTML; case 'css': diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index a790284c3f..665ba28c52 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -24,6 +24,23 @@ import { import { ConfigReader } from '@backstage/config'; import { LocalPublish } from './local'; +jest.mock('fs-extra', () => { + const fsOriginal = jest.requireActual('fs-extra'); + return { + ...fsOriginal, + access: jest.fn().mockImplementation((path, checkType, callback) => { + if ( + path.includes('http://localhost:7000/static') && + checkType === fs.constants.F_OK + ) { + callback(); + } else { + callback(new Error()); + } + }), + }; +}); + const createMockEntity = (annotations = {}) => { return { apiVersion: 'version', @@ -42,7 +59,7 @@ const logger = getVoidLogger(); describe('local publisher', () => { it('should publish generated documentation dir', async () => { const testDiscovery: jest.Mocked = { - getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7000'), + getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000'), getExternalBaseUrl: jest.fn(), }; @@ -52,27 +69,24 @@ describe('local publisher', () => { data: { techdocs: { requestUrl: 'http://localhost:7000', + storageUrl: 'http://localhost:7000/static/docs', }, }, }, ]); const publisher = new LocalPublish(mockConfig, logger, testDiscovery); - const mockEntity = createMockEntity(); - const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`); - expect(tempDir).toBeTruthy(); fs.closeSync(fs.openSync(path.join(tempDir, '/mock-file'), 'w')); - await publisher.publish({ entity: mockEntity, directory: tempDir }); + const publishDir = path.resolve( __dirname, `../../../../../plugins/techdocs-backend/static/docs/${mockEntity.metadata.name}`, ); - const resultDir = path.resolve( __dirname, `../../../../../plugins/techdocs-backend/static/docs/default/${mockEntity.kind}/${mockEntity.metadata.name}`, @@ -81,6 +95,8 @@ describe('local publisher', () => { expect(fs.existsSync(resultDir)).toBeTruthy(); expect(fs.existsSync(path.join(resultDir, '/mock-file'))).toBeTruthy(); + expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true); + fs.removeSync(publishDir); fs.removeSync(tempDir); }); diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts index bc24867f50..2739940530 100644 --- a/packages/techdocs-common/src/stages/publish/local.ts +++ b/packages/techdocs-common/src/stages/publish/local.ts @@ -123,6 +123,7 @@ export class LocalPublish implements PublisherBase { } async hasDocsBeenGenerated(entity: Entity): Promise { + const namespace = entity.metadata.namespace ?? 'default'; return new Promise(resolve => { this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => { const storageUrl = new URL( @@ -130,11 +131,16 @@ export class LocalPublish implements PublisherBase { techdocsApiUrl, ).toString(); - const entityRootDir = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`; + const entityRootDir = `${namespace}/${entity.kind}/${entity.metadata.name}`; const indexHtmlUrl = `${storageUrl}/${entityRootDir}/index.html`; - fetch(indexHtmlUrl) - .then(() => resolve(true)) - .catch(() => resolve(false)); + // Check if the file exists + fs.access(indexHtmlUrl, fs.constants.F_OK, err => { + if (err) { + resolve(false); + } else { + resolve(true); + } + }); }); }); }