@@ -33,6 +33,9 @@
|
||||
'@backstage/plugin-app-backend': patch
|
||||
'@backstage/plugin-auth-node': patch
|
||||
'@backstage/cli': patch
|
||||
'@backstage/backend-defaults': patch
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch
|
||||
---
|
||||
|
||||
Use native fetch instead of node-fetch
|
||||
|
||||
@@ -26,7 +26,6 @@ import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { gzipSync } from 'node:zlib';
|
||||
import { ensureDir } from 'fs-extra';
|
||||
import { program } from 'commander';
|
||||
import fetch from 'node-fetch';
|
||||
import ora from 'ora';
|
||||
import readdir from 'recursive-readdir';
|
||||
import { parse } from 'yaml';
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { fetch } from 'node-fetch';
|
||||
|
||||
createBackendPlugin({
|
||||
pluginId: 'example',
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { fetch } from 'node-fetch';
|
||||
|
||||
createBackendPlugin({
|
||||
pluginId: 'example',
|
||||
|
||||
@@ -106,15 +106,12 @@ Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq
|
||||
Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/search-backend-module-faq-snippets-collator/src/factory.ts` file:
|
||||
|
||||
```ts
|
||||
import fetch from 'node-fetch';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import {
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
|
||||
|
||||
import { FaqSnippetDocument } from './types';
|
||||
|
||||
const DEFAULT_BASE_URL = 'https://backstage.example.biz/faq-snippets';
|
||||
|
||||
@@ -415,17 +415,29 @@ export interface ReadTreeResponseFactory {
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ReadTreeResponseFactoryOptions = {
|
||||
stream: Readable;
|
||||
subpath?: string;
|
||||
etag: string;
|
||||
filter?: (
|
||||
path: string,
|
||||
info?: {
|
||||
size: number;
|
||||
},
|
||||
) => boolean;
|
||||
};
|
||||
export type ReadTreeResponseFactoryOptions =
|
||||
| {
|
||||
stream: Readable;
|
||||
subpath?: string;
|
||||
etag: string;
|
||||
filter?: (
|
||||
path: string,
|
||||
info?: {
|
||||
size: number;
|
||||
},
|
||||
) => boolean;
|
||||
}
|
||||
| {
|
||||
response: Response;
|
||||
subpath?: string;
|
||||
etag?: string;
|
||||
filter?: (
|
||||
path: string,
|
||||
info?: {
|
||||
size: number;
|
||||
},
|
||||
) => boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class ReadUrlResponseFactory {
|
||||
@@ -437,6 +449,9 @@ export class ReadUrlResponseFactory {
|
||||
stream: Readable,
|
||||
options?: ReadUrlResponseFactoryFromStreamOptions,
|
||||
): Promise<UrlReaderServiceReadUrlResponse>;
|
||||
static fromResponse(
|
||||
response: Response,
|
||||
): Promise<UrlReaderServiceReadUrlResponse>;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -32,9 +32,7 @@ import {
|
||||
ScmIntegrations,
|
||||
AzureIntegration,
|
||||
} from '@backstage/integration';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import { ReadTreeResponseFactory, ReaderFactory } from './types';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
@@ -101,7 +99,7 @@ export class AzureUrlReader implements UrlReaderService {
|
||||
|
||||
// for private repos when PAT is not valid, Azure API returns a http status code 203 with sign in page html
|
||||
if (response.ok && response.status !== 203) {
|
||||
return ReadUrlResponseFactory.fromNodeJSReadable(response.body);
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${builtUrl}, ${response.status} ${response.statusText}`;
|
||||
@@ -172,7 +170,7 @@ export class AzureUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromZipArchive({
|
||||
stream: Readable.from(archiveAzureResponse.body),
|
||||
response: archiveAzureResponse,
|
||||
etag: commitSha,
|
||||
filter,
|
||||
subpath,
|
||||
|
||||
@@ -32,14 +32,11 @@ import {
|
||||
getBitbucketCloudRequestOptions,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { trimEnd } from 'lodash';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { ReaderFactory, ReadTreeResponseFactory } from './types';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import { parseLastModified } from './util';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Cloud.
|
||||
@@ -116,12 +113,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return ReadUrlResponseFactory.fromNodeJSReadable(response.body, {
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
lastModifiedAt: parseLastModified(
|
||||
response.headers.get('Last-Modified'),
|
||||
),
|
||||
});
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;
|
||||
@@ -159,7 +151,7 @@ export class BitbucketCloudUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: Readable.from(archiveResponse.body),
|
||||
response: archiveResponse,
|
||||
subpath: filepath,
|
||||
etag: lastCommitShortHash,
|
||||
filter: options?.filter,
|
||||
|
||||
+2
-10
@@ -31,14 +31,11 @@ import {
|
||||
getBitbucketServerRequestOptions,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { trimEnd } from 'lodash';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { ReaderFactory, ReadTreeResponseFactory } from './types';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import { parseLastModified } from './util';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket Server APIs.
|
||||
@@ -107,12 +104,7 @@ export class BitbucketServerUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return ReadUrlResponseFactory.fromNodeJSReadable(response.body, {
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
lastModifiedAt: parseLastModified(
|
||||
response.headers.get('Last-Modified'),
|
||||
),
|
||||
});
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;
|
||||
@@ -150,7 +142,7 @@ export class BitbucketServerUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: Readable.from(archiveResponse.body),
|
||||
response: archiveResponse,
|
||||
subpath: filepath,
|
||||
etag: lastCommitShortHash,
|
||||
filter: options?.filter,
|
||||
|
||||
@@ -32,15 +32,12 @@ import {
|
||||
getBitbucketRequestOptions,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { trimEnd } from 'lodash';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { Readable } from 'stream';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ReaderFactory, ReadTreeResponseFactory } from './types';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import { parseLastModified } from './util';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files from Bitbucket v1 and v2 APIs, such
|
||||
@@ -127,12 +124,7 @@ export class BitbucketUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return ReadUrlResponseFactory.fromNodeJSReadable(response.body, {
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
lastModifiedAt: parseLastModified(
|
||||
response.headers.get('Last-Modified'),
|
||||
),
|
||||
});
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`;
|
||||
@@ -170,7 +162,7 @@ export class BitbucketUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: Readable.from(archiveBitbucketResponse.body),
|
||||
response: archiveBitbucketResponse,
|
||||
subpath: filepath,
|
||||
etag: lastCommitShortHash,
|
||||
filter: options?.filter,
|
||||
|
||||
@@ -22,11 +22,9 @@ import {
|
||||
UrlReaderServiceSearchResponse,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { NotFoundError, NotModifiedError } from '@backstage/errors';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { ReaderFactory } from './types';
|
||||
import path from 'path';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import { parseLastModified } from './util';
|
||||
|
||||
const isInRange = (num: number, [start, end]: [number, number]) => {
|
||||
return num >= start && num <= end;
|
||||
@@ -150,12 +148,7 @@ export class FetchUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return ReadUrlResponseFactory.fromNodeJSReadable(response.body, {
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
lastModifiedAt: parseLastModified(
|
||||
response.headers.get('Last-Modified'),
|
||||
),
|
||||
});
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `could not read ${url}, ${response.status} ${response.statusText}`;
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { ReaderFactory, ReadTreeResponseFactory } from './types';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import {
|
||||
AuthenticationError,
|
||||
@@ -149,7 +148,7 @@ export class GiteaUrlReader implements UrlReaderService {
|
||||
const parsedUri = parseGiteaUrl(this.integration.config, url);
|
||||
|
||||
return this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: Readable.from(response.body),
|
||||
response: response,
|
||||
subpath: parsedUri.path,
|
||||
etag: lastCommitHash,
|
||||
filter: options?.filter,
|
||||
|
||||
@@ -34,11 +34,8 @@ import {
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { trimEnd, trimStart } from 'lodash';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { Readable } from 'stream';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import { ReadTreeResponseFactory, ReaderFactory } from './types';
|
||||
import { parseLastModified } from './util';
|
||||
|
||||
/**
|
||||
* Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for files on GitLab.
|
||||
@@ -101,12 +98,7 @@ export class GitlabUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
return ReadUrlResponseFactory.fromNodeJSReadable(response.body, {
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
lastModifiedAt: parseLastModified(
|
||||
response.headers.get('Last-Modified'),
|
||||
),
|
||||
});
|
||||
return ReadUrlResponseFactory.fromResponse(response);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${builtUrl}, ${response.status} ${response.statusText}`;
|
||||
@@ -228,7 +220,7 @@ export class GitlabUrlReader implements UrlReaderService {
|
||||
}
|
||||
|
||||
return await this.deps.treeResponseFactory.fromTarArchive({
|
||||
stream: Readable.from(archiveGitLabResponse.body),
|
||||
response: archiveGitLabResponse,
|
||||
subpath: filepath,
|
||||
etag: commitSha,
|
||||
filter: options?.filter,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UrlReaderServiceReadUrlResponse } from '@backstage/backend-plugin-api';
|
||||
import getRawBody from 'raw-body';
|
||||
import { Readable } from 'stream';
|
||||
import { ReadUrlResponseFactoryFromStreamOptions } from './types';
|
||||
import { parseLastModified, responseToReadable } from './util';
|
||||
|
||||
/**
|
||||
* Utility class for UrlReader implementations to create valid ReadUrlResponse
|
||||
@@ -28,7 +29,7 @@ import { ReadUrlResponseFactoryFromStreamOptions } from './types';
|
||||
*/
|
||||
export class ReadUrlResponseFactory {
|
||||
/**
|
||||
* Resolves a ReadUrlResponse from a Readable stream.
|
||||
* Resolves a UrlReaderServiceReadUrlResponse from a Readable stream.
|
||||
*/
|
||||
static async fromReadable(
|
||||
stream: Readable,
|
||||
@@ -64,7 +65,7 @@ export class ReadUrlResponseFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a ReadUrlResponse from an old-style NodeJS.ReadableStream.
|
||||
* Resolves a UrlReaderServiceReadUrlResponse from an old-style NodeJS.ReadableStream.
|
||||
*/
|
||||
static async fromNodeJSReadable(
|
||||
oldStyleStream: NodeJS.ReadableStream,
|
||||
@@ -73,4 +74,21 @@ export class ReadUrlResponseFactory {
|
||||
const readable = Readable.from(oldStyleStream);
|
||||
return ReadUrlResponseFactory.fromReadable(readable, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a UrlReaderServiceReadUrlResponse from a native fetch response body.
|
||||
*/
|
||||
static async fromResponse(
|
||||
response: Response,
|
||||
): Promise<UrlReaderServiceReadUrlResponse> {
|
||||
const etag = response.headers.get('etag') || undefined;
|
||||
const lastModifiedAt = parseLastModified(
|
||||
response.headers.get('last-modified'),
|
||||
);
|
||||
|
||||
return ReadUrlResponseFactory.fromReadable(responseToReadable(response), {
|
||||
etag,
|
||||
lastModifiedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+26
-4
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import os from 'os';
|
||||
import { Readable } from 'stream';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
ReadTreeResponseFactoryOptions,
|
||||
@@ -25,6 +26,7 @@ import { TarArchiveResponse } from './TarArchiveResponse';
|
||||
import { ZipArchiveResponse } from './ZipArchiveResponse';
|
||||
import { ReadableArrayResponse } from './ReadableArrayResponse';
|
||||
import { UrlReaderServiceReadTreeResponse } from '@backstage/backend-plugin-api';
|
||||
import { responseToReadable } from '../util';
|
||||
|
||||
export class DefaultReadTreeResponseFactory implements ReadTreeResponseFactory {
|
||||
static create(options: { config: Config }): DefaultReadTreeResponseFactory {
|
||||
@@ -41,11 +43,21 @@ export class DefaultReadTreeResponseFactory implements ReadTreeResponseFactory {
|
||||
stripFirstDirectory?: boolean;
|
||||
},
|
||||
): Promise<UrlReaderServiceReadTreeResponse> {
|
||||
let stream: Readable;
|
||||
let etag: string;
|
||||
if ('stream' in options) {
|
||||
stream = options.stream;
|
||||
etag = options.etag;
|
||||
} else {
|
||||
stream = responseToReadable(options.response);
|
||||
etag = (options.etag ?? options.response.headers.get('etag')) || '';
|
||||
}
|
||||
|
||||
return new TarArchiveResponse(
|
||||
options.stream,
|
||||
stream,
|
||||
options.subpath ?? '',
|
||||
this.workDir,
|
||||
options.etag,
|
||||
etag,
|
||||
options.filter,
|
||||
options.stripFirstDirectory ?? true,
|
||||
);
|
||||
@@ -54,11 +66,21 @@ export class DefaultReadTreeResponseFactory implements ReadTreeResponseFactory {
|
||||
async fromZipArchive(
|
||||
options: ReadTreeResponseFactoryOptions,
|
||||
): Promise<UrlReaderServiceReadTreeResponse> {
|
||||
let stream: Readable;
|
||||
let etag: string;
|
||||
if ('stream' in options) {
|
||||
stream = options.stream;
|
||||
etag = options.etag;
|
||||
} else {
|
||||
stream = responseToReadable(options.response);
|
||||
etag = (options.etag ?? options.response.headers.get('etag')) || '';
|
||||
}
|
||||
|
||||
return new ZipArchiveResponse(
|
||||
options.stream,
|
||||
stream,
|
||||
options.subpath ?? '',
|
||||
this.workDir,
|
||||
options.etag,
|
||||
etag,
|
||||
options.filter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,17 +60,28 @@ export type ReadUrlResponseFactoryFromStreamOptions = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ReadTreeResponseFactoryOptions = {
|
||||
// A binary stream of a tar archive.
|
||||
stream: Readable;
|
||||
// If unset, the files at the root of the tree will be read.
|
||||
// subpath must not contain the name of the top level directory.
|
||||
subpath?: string;
|
||||
// etag of the blob
|
||||
etag: string;
|
||||
// Filter passed on from the ReadTreeOptions
|
||||
filter?: (path: string, info?: { size: number }) => boolean;
|
||||
};
|
||||
export type ReadTreeResponseFactoryOptions =
|
||||
| {
|
||||
// A binary stream of a tar archive.
|
||||
stream: Readable;
|
||||
// If unset, the files at the root of the tree will be read.
|
||||
// subpath must not contain the name of the top level directory.
|
||||
subpath?: string;
|
||||
// etag of the blob
|
||||
etag: string;
|
||||
// Filter passed on from the ReadTreeOptions
|
||||
filter?: (path: string, info?: { size: number }) => boolean;
|
||||
}
|
||||
| {
|
||||
/** A response from a fetch request */
|
||||
response: Response;
|
||||
/** If unset, the files at the root of the tree will be read. Subpath must not contain the name of the top level directory. */
|
||||
subpath?: string;
|
||||
/** etag of the blob, to optionally override what the response header may or may not say */
|
||||
etag?: string;
|
||||
/** Filter passed on from the ReadTreeOptions */
|
||||
filter?: (path: string, info?: { size: number }) => boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options that control {@link ReadTreeResponseFactory.fromReadableArray}
|
||||
|
||||
@@ -14,10 +14,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function parseLastModified(value: string | null | undefined) {
|
||||
import { PassThrough, Readable } from 'stream';
|
||||
import { ReadableStream as WebReadableStream } from 'stream/web';
|
||||
|
||||
export function parseLastModified(
|
||||
value: string | null | undefined,
|
||||
): Date | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Date(value);
|
||||
try {
|
||||
const result = new Date(value);
|
||||
result.toISOString(); // triggers exception if input was invalid
|
||||
return result;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function responseToReadable(response: Response): Readable {
|
||||
return response.body
|
||||
? Readable.from(toWeb(response.body))
|
||||
: new PassThrough().end();
|
||||
}
|
||||
|
||||
// The NodeJS ReadableStream is that fetch returns is super basic and not even
|
||||
// iterable. This function converts it to the smarter, iterable stream/web
|
||||
// variant instead.
|
||||
export function toWeb(
|
||||
responseBody: ReadableStream<Uint8Array>,
|
||||
): WebReadableStream<Uint8Array> {
|
||||
const reader = responseBody.getReader();
|
||||
return new WebReadableStream({
|
||||
async pull(controller) {
|
||||
const { value, done } = await reader.read();
|
||||
if (value) {
|
||||
controller.enqueue(value);
|
||||
}
|
||||
if (done) {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { decodeJwt } from 'jose';
|
||||
import fetch from 'node-fetch';
|
||||
import { toInternalBackstageCredentials } from '../auth/helpers';
|
||||
|
||||
export type Options = {
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
"git-url-parse": "^15.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimatch": "^9.0.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"uuid": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -74,7 +73,7 @@
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"luxon": "^3.0.0",
|
||||
"msw": "^1.0.0"
|
||||
"msw": "^2.0.0"
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
mockServices,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
const server = setupServer();
|
||||
@@ -67,10 +67,9 @@ describe('GithubLocationAnalyzer', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.post('http://localhost:7007/locations', async (_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.json({
|
||||
http.post('http://localhost:7007/locations', () =>
|
||||
HttpResponse.json(
|
||||
{
|
||||
location: 'test',
|
||||
exists: false,
|
||||
entities: [
|
||||
@@ -100,9 +99,10 @@ describe('GithubLocationAnalyzer', () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
},
|
||||
{ status: 201 },
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
octokit.repos.get.mockResolvedValue({
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { registerMswTestHooks } from '@backstage/backend-test-utils';
|
||||
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
|
||||
import { graphql as graphqlOctokit } from '@octokit/graphql';
|
||||
import { graphql as graphqlMsw } from 'msw';
|
||||
import { graphql as graphqlMsw, HttpResponse } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { TeamTransformer, UserTransformer } from './defaultTransformers';
|
||||
import {
|
||||
@@ -33,14 +33,13 @@ import {
|
||||
createRemoveEntitiesOperation,
|
||||
createReplaceEntitiesOperation,
|
||||
} from './github';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
describe('github', () => {
|
||||
const graphql = graphqlOctokit.defaults({ request: { fetch } });
|
||||
|
||||
const server = setupServer();
|
||||
registerMswTestHooks(server);
|
||||
|
||||
const graphql = graphqlOctokit.defaults({});
|
||||
|
||||
describe('getOrganizationUsers using defaultUserMapper', () => {
|
||||
it('reads members', async () => {
|
||||
const input: QueryResponse = {
|
||||
@@ -73,7 +72,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('users', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('users', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -136,7 +135,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('users', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('users', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -180,7 +179,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('users', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('users', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
const users = await getOrganizationUsers(
|
||||
@@ -255,7 +254,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('teams', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('teams', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
await expect(getOrganizationTeams(graphql, 'a')).resolves.toEqual(output);
|
||||
@@ -355,7 +354,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('teams', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('teams', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -435,7 +434,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('teams', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('teams', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
const teams = await getOrganizationTeams(
|
||||
@@ -471,7 +470,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('orgs', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('orgs', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
await expect(getOrganizationsFromUser(graphql, 'foo')).resolves.toEqual({
|
||||
@@ -500,7 +499,7 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('members', (_req, res, ctx) => res(ctx.data(input))),
|
||||
graphqlMsw.query('members', () => HttpResponse.json({ data: input })),
|
||||
);
|
||||
|
||||
await expect(getTeamMembers(graphql, 'a', 'b')).resolves.toEqual(output);
|
||||
@@ -587,8 +586,8 @@ describe('github', () => {
|
||||
};
|
||||
|
||||
server.use(
|
||||
graphqlMsw.query('repositories', (_req, res, ctx) =>
|
||||
res(ctx.data(input)),
|
||||
graphqlMsw.query('repositories', () =>
|
||||
HttpResponse.json({ data: input }),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"@backstage/plugin-scaffolder-node": "workspace:^",
|
||||
"fs-extra": "^11.2.0",
|
||||
"git-url-parse": "^15.0.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
|
||||
+4
-2
@@ -17,7 +17,7 @@
|
||||
import { Config } from '@backstage/config';
|
||||
import { ResponseError, ConflictError, InputError } from '@backstage/errors';
|
||||
import fs from 'fs-extra';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
interface Links {
|
||||
webui: string;
|
||||
@@ -164,7 +164,9 @@ export const getAndWriteAttachments = async (
|
||||
const writeStream = fs.createWriteStream(
|
||||
`${workspace}/${mkdocsDir}docs/img/${downloadTitle}`,
|
||||
);
|
||||
res.body.pipe(writeStream);
|
||||
// TODO(freben): This cast is sketchy, but for some reason the node types don't quite line up here
|
||||
// https://stackoverflow.com/questions/44672942/stream-response-to-file-using-fetch-api-and-fs-createwritestream/73879265#73879265
|
||||
Readable.fromWeb(res.body as any).pipe(writeStream);
|
||||
await new Promise((resolve, reject) => {
|
||||
writeStream.on('finish', () => {
|
||||
resolve(`${workspace}/${mkdocsDir}docs/img/${downloadTitle}`);
|
||||
|
||||
+1
-3
@@ -13,17 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
import yaml from 'yaml';
|
||||
import { createGitlabProjectAccessTokenAction } from './gitlabProjectAccessTokenCreate'; // Adjust the import based on your project structure
|
||||
import { examples } from './gitlabProjectAccessTokenCreate.examples';
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
jest.mock('node-fetch');
|
||||
|
||||
const mockGitlabClient = {
|
||||
ProjectAccessTokens: {
|
||||
create: jest.fn(),
|
||||
|
||||
+1
-3
@@ -13,15 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
|
||||
import { createGitlabProjectAccessTokenAction } from './gitlabProjectAccessTokenCreate'; // Adjust the import based on your project structure
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
jest.mock('node-fetch');
|
||||
|
||||
const mockGitlabClient = {
|
||||
ProjectAccessTokens: {
|
||||
create: jest.fn(),
|
||||
|
||||
@@ -5921,8 +5921,7 @@ __metadata:
|
||||
lodash: ^4.17.21
|
||||
luxon: ^3.0.0
|
||||
minimatch: ^9.0.0
|
||||
msw: ^1.0.0
|
||||
node-fetch: ^2.7.0
|
||||
msw: ^2.0.0
|
||||
uuid: ^11.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -7483,7 +7482,6 @@ __metadata:
|
||||
fs-extra: ^11.2.0
|
||||
git-url-parse: ^15.0.0
|
||||
msw: ^1.0.0
|
||||
node-fetch: ^2.7.0
|
||||
node-html-markdown: ^1.3.0
|
||||
yaml: ^2.0.0
|
||||
languageName: unknown
|
||||
|
||||
Reference in New Issue
Block a user