adding new action confluence to markdown for GitHub repos onboarded onto techdocs

Signed-off-by: Tracey <tradcliffe@expediagroup.com>
This commit is contained in:
Tracey
2023-03-10 17:36:40 -08:00
parent 297118ce46
commit 03ebad97d2
10 changed files with 973 additions and 10 deletions
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,126 @@
# @backstage/plugin-scaffolder-backend-module-confluence-to-markdowwn
Welcome to the `transform:confluence-to-markdown` action for the `scaffolder-backend`.
## Getting started
You need to configure the action in your backend:
## From your Backstage root directory
```bash
# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-scaffolder-backend-module-confluence-to-markdowwn
```
Configure the action:
(you can check the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to see all options):
```typescript
// packages/backend/src/plugins/scaffolder.ts
import { createBuiltinActions } from '@backstage/plugin-scaffolder-backend';
import { ScmIntegrations } from '@backstage/integration';
import { createConfluenceToMarkdownAction } from '@backstage/plugin-scaffolder-backend-module-confluence-to-markdowwn';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const catalogClient = new CatalogClient({ discoveryApi: env.discovery });
const integrations = ScmIntegrations.fromConfig(env.config);
const builtInActions = createBuiltinActions({
integrations,
catalogClient,
config: env.config,
reader: env.reader,
});
const actions = [
...builtInActions,
createConfluenceToMarkdownAction({
integrations,
config: env.config,
reader: env.reader,
}),
];
return createRouter({
actions,
catalogClient: catalogClient,
logger: env.logger,
config: env.config,
database: env.database,
reader: env.reader,
});
}
```
You will also need an access token for authorization with `Read` permissions. You can create a Personal Access Token (PAT) in confluence and add the PAT to your `app-config.yaml`
```
confluence:
baseUrl: ${CONFLUENCE_BASE_URL}
token: ${CONFLUENCE_TOKEN}
```
After that you can use the action in your template:
```
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: confluence-to-markdown
title: Confluence to Markdown
description: This template converts a single confluence document to Markdown for Techdocs and adds it to a given GitHub repo.
tags:
- do-not-use
- poc
spec:
owner: <YOUR_EMAIL>
type: service
parameters:
- title: Confluence and Github Repo Information
properties:
confluenceUrls:
type: array
description: Urls for confluence doc to be converted to markdown. In format <CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE> or <CONFLUENCE_BASE_URL>/pages/viewpage.action?spacekey=<SPACEKEY>&title=<PAGE_TITLE>
items:
type: string
default: confluence url
ui:options:
addable: true
minItems: 1
maxItems: 5
repoUrl:
type: string
title: GitHub url mkdocs.yaml link
description: The GitHub repo url to your mkdocs.yaml file. Example <https://github.com/blob/master/mkdocs.yml>
steps:
- id: create-docs
name: Get markdown file created and update markdown.yaml file
action: transform:confluence-to-markdown
input:
confluenceUrls: ${{ parameters.confluenceUrls }}
repoUrl: ${{ parameters.repoUrl }}
- id: publish
name: Publish PR to GitHub
action: publish:github:pull-request
input:
repoUrl: <GITHUB_BASE_URL>?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }}
branchName: confluence-to-markdown
title: Confluence to Markdown
description: PR for converting confluence page to mkdocs
- id: merge
name: Merge PR
action: publish:github:merge-pull-request
input:
prUrl: ${{ steps['publish'].output.remoteUrl }}
forceAdmin: true
```
Replace `<GITHUB_BASE_URL>` with your GitHub url without `https://`.
You can find a list of all registred actions including their parameters at the /create/actions route in your Backstage application.
@@ -0,0 +1,49 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdowwn",
"description": "The confluence-to-markdowwn module for @backstage/plugin-scaffolder-backend",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "backend-plugin-module"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack",
"help": "backstage-cli help"
},
"dependencies": {
"@backstage/backend-common": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/types": "workspace:^",
"fs": "^0.0.1-security",
"fs-extra": "^11.1.0",
"git-url-parse": "^13.1.0",
"node-fetch": "2.6.7",
"node-html-markdown": "^1.3.0",
"yaml": "^2.2.1"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"mock-fs": "^5.2.0",
"os": "^0.1.2"
},
"files": [
"dist"
]
}
@@ -0,0 +1,316 @@
/*
* Copyright 2023 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 { PassThrough } from 'stream';
import { createConfluenceToMarkdownAction } from './confluenceToMarkdown';
import { getVoidLogger } from '@backstage/backend-common';
import { UrlReader } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import mockFs from 'mock-fs';
import os from 'os';
import fetch from 'node-fetch';
import type { ActionContext } from '@backstage/plugin-scaffolder-node';
import { readFile, writeFile } from 'fs-extra';
import { Readable } from 'stream';
jest.mock('node-fetch');
jest.mock('fs-extra', () => ({
mkdirSync: jest.fn(),
readFile: jest.fn().mockResolvedValue('File contents'),
writeFile: jest.fn().mockImplementation(() => {
return Promise.resolve();
}),
outputFile: jest.fn(),
openSync: jest.fn(),
createWriteStream: jest.fn().mockReturnValue(new PassThrough()),
writeStream: jest.fn(),
}));
const { Response } = jest.requireActual('node-fetch');
describe('transform:confluence-to-markdown', () => {
const config = new ConfigReader({
confluence: {
baseUrl: 'https://nodomain.confluence.com/',
token: 'fake_token',
},
});
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
integrations: {
github: [{ host: 'github.com', token: 'token' }],
},
}),
);
let reader: UrlReader;
let mockContext: ActionContext<{
confluenceUrls: string[];
repoUrl: string;
}>;
const logger = getVoidLogger();
jest.spyOn(logger, 'info');
const mockTmpDir = os.tmpdir();
beforeEach(() => {
reader = {
readUrl: jest.fn(),
readTree: jest.fn().mockResolvedValue({
dir: jest.fn(),
}),
search: jest.fn(),
};
mockContext = {
input: {
confluenceUrls: [
'https://nodomain.confluence.com/display/testing/mkdocs',
],
repoUrl: 'https://notreal.github.com/space/backstage/mkdocs.yml',
},
workspacePath: '/tmp',
logger,
logStream: new PassThrough(),
output: jest.fn(),
createTemporaryDirectory: jest.fn().mockResolvedValue(mockTmpDir),
};
mockFs({ [`${mockTmpDir}/src/docs`]: {} });
});
afterEach(() => {
jest.clearAllMocks();
mockFs.restore();
});
it('should call confluence to markdown action successfully with results array', async () => {
const options = {
reader,
integrations,
config,
};
const responseBody = {
results: [
{
id: '4444444',
type: 'page',
title: 'Testing',
body: {
export_view: {
value: '<p>hello world</p>',
},
},
},
],
};
const responseBodyTwo = {
results: [
{
id: '4444444',
type: 'attachment',
title: 'testing.pdf',
metadata: {
mediaType: 'application/pdf',
},
_links: {
download: '/download/attachments/4444444/testing.pdf',
},
},
],
};
(fetch as jest.MockedFunction<typeof fetch>)
.mockResolvedValueOnce(
new Response(JSON.stringify(responseBody), {
status: 200,
statusText: 'OK',
}),
)
.mockResolvedValueOnce(
new Response(JSON.stringify(responseBodyTwo), {
status: 200,
statusText: 'OK',
}),
)
.mockResolvedValueOnce(
new Response(Readable.from('Hello world'), {
status: 200,
statusText: 'OK',
}),
);
const mockCreateWriteStream = jest.fn().mockReturnValue({
on: jest.fn(),
once: jest.fn((_event, callback) => {
callback();
}),
});
jest.mock('fs', () => ({
openSync: jest.fn(),
createWriteStream: jest.fn().mockReturnValue(mockCreateWriteStream),
}));
const action = createConfluenceToMarkdownAction(options);
await action.handler(mockContext);
expect(logger.info).toHaveBeenCalledWith(
`Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`,
);
expect(logger.info).toHaveBeenCalledTimes(5);
expect(fetch).toHaveBeenCalledTimes(3);
expect(readFile).toHaveBeenCalledTimes(1);
expect(writeFile).toHaveBeenCalledTimes(1);
});
it('should call confluence to markdown action successfully with empty results array', async () => {
const options = {
reader,
integrations,
config,
};
const responseBody = {
results: [
{
id: '4444444',
type: 'page',
title: 'Testing',
body: {
export_view: {
value: '<p>hello world</p>',
},
},
},
],
};
const responseBodyTwo = {
results: [],
};
(fetch as jest.MockedFunction<typeof fetch>)
.mockResolvedValueOnce(
new Response(JSON.stringify(responseBody), {
status: 200,
statusText: 'OK',
}),
)
.mockResolvedValueOnce(
new Response(JSON.stringify(responseBodyTwo), {
status: 200,
statusText: 'OK',
}),
);
jest.mock('fs-extra', () => ({
readFile: jest.fn().mockResolvedValue('File contents'),
}));
const action = createConfluenceToMarkdownAction(options);
await action.handler(mockContext);
expect(logger.info).toHaveBeenCalledWith(
`Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`,
);
expect(logger.info).toHaveBeenCalledTimes(5);
expect(fetch).toHaveBeenCalledTimes(2);
expect(readFile).toHaveBeenCalledTimes(1);
expect(writeFile).toHaveBeenCalledTimes(1);
});
it('shoud fail on the first fetch call with response.ok set to false', async () => {
const options = {
reader,
integrations,
config,
};
const responseBody = {};
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
new Response(JSON.stringify(responseBody), {
status: 401,
statusText: 'npot',
ok: false,
}),
);
const action = createConfluenceToMarkdownAction(options);
await expect(async () => {
await action.handler(mockContext);
}).rejects.toThrow('Request failed with 401 Error');
});
it('should return nothing in results from the first api call and fail', async () => {
const options = {
reader,
integrations,
config,
};
const responseBody = {
results: [],
};
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValueOnce(
new Response(JSON.stringify(responseBody), {
status: 200,
statusText: 'ok',
}),
);
const action = createConfluenceToMarkdownAction(options);
await expect(async () => {
await action.handler(mockContext);
}).rejects.toThrow(
'Could not find document https://nodomain.confluence.com/display/testing/mkdocs. Please check your input.',
);
});
it('should fail on the second fetch call to confluence', async () => {
const options = {
reader,
integrations,
config,
};
const responseBody = {
results: [
{
id: '4444444',
type: 'page',
title: 'Testing',
body: {
export_view: {
value: '<p>hello world</p>',
},
},
},
],
};
const responseBodyTwo = {};
(fetch as jest.MockedFunction<typeof fetch>)
.mockResolvedValueOnce(
new Response(JSON.stringify(responseBody), {
status: 200,
statusText: 'OK',
}),
)
.mockResolvedValueOnce(
new Response(JSON.stringify(responseBodyTwo), {
status: 404,
statusText: 'notok',
}),
);
const action = createConfluenceToMarkdownAction(options);
await expect(async () => {
await action.handler(mockContext);
}).rejects.toThrow('Request failed with 404 Error');
});
});
@@ -0,0 +1,187 @@
/*
* Copyright 2023 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 { Config } from '@backstage/config';
import { UrlReader } from '@backstage/backend-common';
import { ScmIntegrations } from '@backstage/integration';
import { createFetchPlainAction } from '@backstage/plugin-scaffolder-backend';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { InputError, ConflictError } from '@backstage/errors';
import { NodeHtmlMarkdown } from 'node-html-markdown';
import fs from 'fs-extra';
import parseGitUrl from 'git-url-parse';
import {
parseFromYaml,
readFileAsString,
transformToYaml,
updateFile,
fetchConfluence,
getAndWriteAttachments,
createConfluenceVariables,
} from './helpers';
export const createConfluenceToMarkdownAction = (options: {
reader: UrlReader;
integrations: ScmIntegrations;
config: Config;
}) => {
const { config, reader, integrations } = options;
const fetchPlainAction = createFetchPlainAction({ reader, integrations });
type Obj = {
[key: string]: string;
};
return createTemplateAction<{
confluenceUrls: string[];
repoUrl: string;
}>({
id: 'transform:confluence-to-markdown',
schema: {
input: {
properties: {
confluenceUrls: {
type: 'array',
title: 'Confluence URL',
description:
'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title}',
items: {
type: 'string',
default: 'Confluence URL',
},
},
repoUrl: {
type: 'string',
title: 'GitHub Repo Url',
description:
'mkdocs.yml file location inside the github repo you want to store the document',
},
},
},
},
async handler(ctx) {
const { confluenceUrls, repoUrl } = ctx.input;
const parsedRepoUrl = parseGitUrl(`${repoUrl}`);
const filePathToMkdocs = parsedRepoUrl.filepath.substring(
0,
parsedRepoUrl.filepath.lastIndexOf('/') + 1,
);
const dirPath = ctx.workspacePath;
let productArray: string[][] = [];
ctx.logger.info(`Fetching the mkdocs.yml catalog from ${repoUrl}`);
ctx.logger.info(confluenceUrls);
const params = new URL(confluenceUrls[0]);
ctx.logger.info(params.searchParams);
// This grabs the files from Github
const repoFileDir = `${dirPath}/${parsedRepoUrl.filepath}`;
await fetchPlainAction.handler({
...ctx,
input: {
url: `https://${parsedRepoUrl.resource}/${parsedRepoUrl.owner}/${parsedRepoUrl.name}`,
targetPath: dirPath,
},
});
for (let p = 0; p < confluenceUrls.length; p++) {
const confluenceUrl = confluenceUrls[p];
const { spacekey, title, titleWithSpaces } =
await createConfluenceVariables(confluenceUrl);
ctx.logger.info(spacekey);
ctx.logger.info(title);
ctx.logger.info(titleWithSpaces);
// This calls confluence to get the page html and page id
const getConfluenceDoc = await fetchConfluence(
`/rest/api/content?title=${title}&spaceKey=${spacekey}&expand=body.export_view`,
config,
);
if (getConfluenceDoc.results.length === 0) {
throw new InputError(
`Could not find document ${confluenceUrl}. Please check your input.`,
);
}
// This gets attachements for the confluence page if they exist
const getDocAttachments = await fetchConfluence(
`/rest/api/content/${getConfluenceDoc.results[0].id}/child/attachment`,
config,
);
if (getDocAttachments.results.length) {
fs.mkdirSync(`${dirPath}/${filePathToMkdocs}docs/img`, {
recursive: true,
});
productArray = await getAndWriteAttachments(
getDocAttachments,
dirPath,
config,
filePathToMkdocs,
);
}
ctx.logger.info(
`starting action for converting ${titleWithSpaces} from Confluence To Markdown`,
);
// This reads mkdocs.yml file
const mkdocsFileContent = await readFileAsString(repoFileDir);
const mkdocsFile = parseFromYaml(mkdocsFileContent);
ctx.logger.info(
`Adding new file - ${titleWithSpaces} to the current mkdocs.yml file`,
);
// This modifies the mkdocs.yml file
if (mkdocsFile !== undefined && mkdocsFile.hasOwnProperty('nav')) {
const { nav } = mkdocsFile;
if (!nav.some((i: Obj) => i.hasOwnProperty(titleWithSpaces))) {
nav.push({
[titleWithSpaces]: `${titleWithSpaces.replace(/\s+/g, '-')}.md`,
});
mkdocsFile.nav = nav;
} else {
throw new ConflictError(
'This document looks to exist inside the GitHub repo. Will end the action.',
);
}
}
const newTemplateFileContent = transformToYaml(mkdocsFile);
await updateFile(newTemplateFileContent, repoFileDir);
// This grabs the confluence html and converts it to markdown and adds attachments
const html = getConfluenceDoc.results[0].body.export_view.value;
const markdownToPublish = NodeHtmlMarkdown.translate(html);
let newString: string = markdownToPublish;
productArray.map((product: string[]) => {
const regex = product[1].includes('pdf')
? new RegExp(`(\\[.*?\\]\\()(.*?${product[0]}.*?)(\\))`, 'gi')
: new RegExp(`(\\!\\[.*?\\]\\()(.*?${product[0]}.*?)(\\))`, 'gi');
newString = newString.replace(regex, `$1./img/${product[1]}$3`);
});
ctx.logger.info(`Adding new file to repo.`);
await fs.outputFile(
`${dirPath}/${filePathToMkdocs}docs/${titleWithSpaces.replace(
/\s+/g,
'-',
)}.md`,
newString,
);
}
ctx.output('repo', parsedRepoUrl.name);
ctx.output('owner', parsedRepoUrl.owner);
},
});
};
@@ -0,0 +1,142 @@
/*
* Copyright 2023 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 { Config } from '@backstage/config';
import { ResponseError, ConflictError, InputError } from '@backstage/errors';
import fs from 'fs-extra';
import YAML from 'yaml';
import fetch, { Response } from 'node-fetch';
interface Links {
webui: string;
download: string;
thumbnail: string;
self: string;
}
interface Metadata {
mediaType: string;
}
export interface Result {
id: string;
type: string;
status: string;
title: string;
metadata: Metadata;
_links: Links;
}
export interface Results {
results: Result[];
}
export const readFileAsString = async (fileDir: string) => {
const content = await fs.readFile(fileDir, 'utf-8');
return content.toString();
};
export const parseFromYaml = (content: string) => {
return YAML.parse(content);
};
export const transformToYaml = (content: any) => {
return YAML.stringify(content);
};
export const updateFile = async (content: string, file: string) => {
await fs.writeFile(file, content);
};
export const fetchConfluence = async (relativeUrl: string, config: Config) => {
const baseUrl = config.getString('confluence.baseUrl');
const token = config.getString('confluence.token');
const response: Response = await fetch(`${baseUrl}${relativeUrl}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
return response.json() as Promise<any>;
};
export const getAndWriteAttachments = async (
arr: Results,
workspace: string,
config: Config,
mkdocsDir: string,
) => {
const productArr: string[][] = [];
const baseUrl = config.getString('confluence.baseUrl');
const token = config.getString('confluence.token');
await Promise.all(
await arr.results.map(async (result: Result) => {
const downloadLink = result._links.download;
const downloadTitle = result.title.replace(/ /g, '-');
if (result.metadata.mediaType !== 'application/gliffy+json') {
productArr.push([result.title.replace(/ /g, '%20'), downloadTitle]);
}
const res = await fetch(`${baseUrl}${downloadLink}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
// console.log(`${workspace}/${mkdocsDir}docs/img/${downloadTitle}`)
if (!res.ok) {
throw ResponseError.fromResponse(res);
} else if (res.body !== null) {
fs.openSync(`${workspace}/${mkdocsDir}docs/img/${downloadTitle}`, 'w');
const writeStream = fs.createWriteStream(
`${workspace}/${mkdocsDir}docs/img/${downloadTitle}`,
);
res.body.pipe(writeStream);
await new Promise((resolve, reject) => {
writeStream.on('finish', () => {
resolve(`${workspace}/${mkdocsDir}docs/img/${downloadTitle}`);
});
writeStream.on('error', reject);
});
} else {
throw new ConflictError(
'No Body on the response. Can not save images from Confluence Doc',
);
}
}),
);
return productArr;
};
export const createConfluenceVariables = async (url: string) => {
let spacekey: string | null = null;
let title: string | null = null;
let titleWithSpaces: string | null = '';
const params = new URL(url);
if (url.includes('display')) {
spacekey = params.pathname.split('/')[2];
title = params.pathname.split('/')[3];
titleWithSpaces = title?.replace(/\+/g, ' ');
return { spacekey, title, titleWithSpaces };
}
throw new InputError(
'The Url format for Confluence is incorrect. Acceptable format is `<CONFLUENCE_BASE_URL>/display/<SPACEKEY>/<PAGE+TITLE>`',
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2023 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 { createConfluenceToMarkdownAction } from './confluenceToMarkdown';
@@ -0,0 +1,16 @@
/*
* Copyright 2023 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 './confluence';
@@ -0,0 +1,23 @@
/*
* Copyright 2023 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.
*/
/**
* The confluence-to-markdowwn module for @backstage/plugin-scaffolder-backend.
*
* @packageDocumentation
*/
export * from './actions';
+97 -10
View File
@@ -7651,6 +7651,29 @@ __metadata:
languageName: unknown
linkType: soft
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdowwn@workspace:plugins/scaffolder-backend-module-confluence-to-markdowwn":
version: 0.0.0-use.local
resolution: "@backstage/plugin-scaffolder-backend-module-confluence-to-markdowwn@workspace:plugins/scaffolder-backend-module-confluence-to-markdowwn"
dependencies:
"@backstage/backend-common": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
"@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/types": "workspace:^"
fs: ^0.0.1-security
fs-extra: ^11.1.0
git-url-parse: ^13.1.0
mock-fs: ^5.2.0
node-fetch: 2.6.7
node-html-markdown: ^1.3.0
os: ^0.1.2
yaml: ^2.2.1
languageName: unknown
linkType: soft
"@backstage/plugin-scaffolder-backend-module-cookiecutter@workspace:plugins/scaffolder-backend-module-cookiecutter":
version: 0.0.0-use.local
resolution: "@backstage/plugin-scaffolder-backend-module-cookiecutter@workspace:plugins/scaffolder-backend-module-cookiecutter"
@@ -20175,6 +20198,19 @@ __metadata:
languageName: node
linkType: hard
"css-select@npm:^5.1.0":
version: 5.1.0
resolution: "css-select@npm:5.1.0"
dependencies:
boolbase: ^1.0.0
css-what: ^6.1.0
domhandler: ^5.0.2
domutils: ^3.0.1
nth-check: ^2.0.1
checksum: 2772c049b188d3b8a8159907192e926e11824aea525b8282981f72ba3f349cf9ecd523fdf7734875ee2cb772246c22117fc062da105b6d59afe8dcd5c99c9bda
languageName: node
linkType: hard
"css-to-react-native@npm:^3.0.0":
version: 3.0.0
resolution: "css-to-react-native@npm:3.0.0"
@@ -20229,6 +20265,13 @@ __metadata:
languageName: node
linkType: hard
"css-what@npm:^6.1.0":
version: 6.1.0
resolution: "css-what@npm:6.1.0"
checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe
languageName: node
linkType: hard
"css.escape@npm:1.5.1, css.escape@npm:^1.5.1":
version: 1.5.1
resolution: "css.escape@npm:1.5.1"
@@ -23898,6 +23941,17 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^11.1.0":
version: 11.1.0
resolution: "fs-extra@npm:11.1.0"
dependencies:
graceful-fs: ^4.2.0
jsonfile: ^6.0.1
universalify: ^2.0.0
checksum: 5ca476103fa1f5ff4a9b3c4f331548f8a3c1881edaae323a4415d3153b5dc11dc6a981c8d1dd93eec8367ceee27b53f8bd27eecbbf66ffcdd04927510c171e7f
languageName: node
linkType: hard
"fs-extra@npm:^7.0.1, fs-extra@npm:~7.0.1":
version: 7.0.1
resolution: "fs-extra@npm:7.0.1"
@@ -23955,6 +24009,13 @@ __metadata:
languageName: node
linkType: hard
"fs@npm:^0.0.1-security":
version: 0.0.1-security
resolution: "fs@npm:0.0.1-security"
checksum: 53c6230e1faae9fa32c1df82c16a84b51b1243d20f3da2b64bd110bb472b73b9185169b703e008356e3cdc92d155088b617d9d39a63b5227a30b3621baad7f5d
languageName: node
linkType: hard
"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2":
version: 2.3.2
resolution: "fsevents@npm:2.3.2"
@@ -24228,7 +24289,7 @@ __metadata:
languageName: node
linkType: hard
"git-url-parse@npm:^13.0.0":
"git-url-parse@npm:^13.0.0, git-url-parse@npm:^13.1.0":
version: 13.1.0
resolution: "git-url-parse@npm:13.1.0"
dependencies:
@@ -24900,7 +24961,7 @@ __metadata:
languageName: node
linkType: hard
"he@npm:^1.2.0":
"he@npm:1.2.0, he@npm:^1.2.0":
version: 1.2.0
resolution: "he@npm:1.2.0"
bin:
@@ -30598,6 +30659,25 @@ __metadata:
languageName: node
linkType: hard
"node-html-markdown@npm:^1.3.0":
version: 1.3.0
resolution: "node-html-markdown@npm:1.3.0"
dependencies:
node-html-parser: ^6.1.1
checksum: 303affd75bf11e92f004047e283aa05da5c8ae8696c1f28f811e9f61762c55b4480e0e5986a9a4b399791485b835380af8ab0554ebf867d6cf9be362abedcf5d
languageName: node
linkType: hard
"node-html-parser@npm:^6.1.1":
version: 6.1.5
resolution: "node-html-parser@npm:6.1.5"
dependencies:
css-select: ^5.1.0
he: 1.2.0
checksum: b54257b31954be17d473c86a2fede53d5e238708f3d88577c98881c28ab66013fd37044903f6f3cd662b56d882681fb511bca75a80a80b62f7111b32b39eebae
languageName: node
linkType: hard
"node-int64@npm:^0.4.0":
version: 0.4.0
resolution: "node-int64@npm:0.4.0"
@@ -30898,12 +30978,12 @@ __metadata:
languageName: node
linkType: hard
"nth-check@npm:^2.0.0":
version: 2.0.1
resolution: "nth-check@npm:2.0.1"
"nth-check@npm:^2.0.0, nth-check@npm:^2.0.1":
version: 2.1.1
resolution: "nth-check@npm:2.1.1"
dependencies:
boolbase: ^1.0.0
checksum: 5386d035c48438ff304fe687704d93886397349d1bed136de97aeae464caba10e8ffac55a04b215b86b3bc8897f33e0a5aa1045a9d8b2f251ae61b2a3ad3e450
checksum: 5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3
languageName: node
linkType: hard
@@ -31278,6 +31358,13 @@ __metadata:
languageName: node
linkType: hard
"os@npm:^0.1.2":
version: 0.1.2
resolution: "os@npm:0.1.2"
checksum: dc2d99759eef13f5dc47ddb12c67b9760a7196fd83a35a7aec2d75b82f91163ca1d4e8872238f8c2a35f4cddd5adf5ce6638a234c0563c748d3cd1d69a9f7153
languageName: node
linkType: hard
"ospath@npm:^1.2.2":
version: 1.2.2
resolution: "ospath@npm:1.2.2"
@@ -39460,10 +39547,10 @@ __metadata:
languageName: node
linkType: hard
"yaml@npm:^2.0.0, yaml@npm:^2.1.1, yaml@npm:^2.1.3":
version: 2.1.3
resolution: "yaml@npm:2.1.3"
checksum: 91316062324a93f9cb547469092392e7d004ff8f70c40fecb420f042a4870b2181557350da56c92f07bd44b8f7a252b0be26e6ade1f548e1f4351bdd01c9d3c7
"yaml@npm:^2.0.0, yaml@npm:^2.1.1, yaml@npm:^2.1.3, yaml@npm:^2.2.1":
version: 2.2.1
resolution: "yaml@npm:2.2.1"
checksum: 84f68cbe462d5da4e7ded4a8bded949ffa912bc264472e5a684c3d45b22d8f73a3019963a32164023bdf3d83cfb6f5b58ff7b2b10ef5b717c630f40bd6369a23
languageName: node
linkType: hard