@backstage-community/plugin-search-backend-module-azure-devops 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +59 -0
- package/config.d.ts +66 -0
- package/dist/collator/AzureDevOpsWikiArticleCollatorFactory.cjs.js +151 -0
- package/dist/collator/AzureDevOpsWikiArticleCollatorFactory.cjs.js.map +1 -0
- package/dist/collator/AzureDevOpsWikiReader.cjs.js +83 -0
- package/dist/collator/AzureDevOpsWikiReader.cjs.js.map +1 -0
- package/dist/config.cjs.js +27 -0
- package/dist/config.cjs.js.map +1 -0
- package/dist/index.cjs.js +10 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/module.cjs.js +34 -0
- package/dist/module.cjs.js.map +1 -0
- package/dist/types.cjs.js +6 -0
- package/dist/types.cjs.js.map +1 -0
- package/dist/utils.cjs.js +24 -0
- package/dist/utils.cjs.js.map +1 -0
- package/package.json +59 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @backstage-community/plugin-search-backend-module-azure-devops
|
|
2
|
+
|
|
3
|
+
This plugin provides the pieces necessary to have Backstage index articles and entries from a wiki in an Azure DevOps project to make them available to search with the Backstage Search feature.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- You must have an Azure DevOps project with a wiki present
|
|
8
|
+
- You must be using the Backstage Search feature
|
|
9
|
+
- You must be using the new backend system in Backstage
|
|
10
|
+
|
|
11
|
+
## Getting started
|
|
12
|
+
|
|
13
|
+
In the root directory of your Backstage project:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add --cwd packages/backend @backstage-community/plugin-search-backend-module-azure-devops
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Add the necessary configuration for this plugin to your `app-config.yaml`:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
# app-config.yaml
|
|
23
|
+
|
|
24
|
+
# multiple wikis
|
|
25
|
+
azureDevOpsWikiCollator:
|
|
26
|
+
baseUrl: https://my-azure-instance.com # The URL of your Azure DevOps instance. Required
|
|
27
|
+
token: ${AZURE_TOKEN} # The PAT used to authenticate to the Azure DevOps REST API. Required.
|
|
28
|
+
wikis:
|
|
29
|
+
- wikiIdentifier: Wiki-Identifier.wiki # The identifier of the wiki. This can be found by looking at the URL of the wiki in ADO. It is typically something like '{nameOfWiki}.wiki'. Required.
|
|
30
|
+
organization: MyOrganization # The name of the organization the wiki is contained in. Required.
|
|
31
|
+
project: MyProject # The name of the project the wiki is contained in. Required.
|
|
32
|
+
titleSuffix: ' - My Suffix' # A string to append to the title of articles to make them easier to identify as search results from the wiki. Optional
|
|
33
|
+
- wikiIdentifier: Wiki-Identifier2.wiki
|
|
34
|
+
organization: MyOrganization
|
|
35
|
+
project: MyProject
|
|
36
|
+
titleSuffix: ' - Suffix 2'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Add the plugin to your backend:
|
|
40
|
+
|
|
41
|
+
```diff
|
|
42
|
+
import { createBackend } from '@backstage/backend-defaults';
|
|
43
|
+
|
|
44
|
+
const backend = createBackend();
|
|
45
|
+
|
|
46
|
+
// ... other feature additions
|
|
47
|
+
|
|
48
|
+
+ backend.add(import('@backstage-community/plugin-search-backend-module-azure-devops'));
|
|
49
|
+
|
|
50
|
+
backend.start();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
From here, the collator will begin indexing all articles in the wiki into search. Once the indexing is done, the articles and their content will be searchable via the Backstage search feature.
|
|
54
|
+
|
|
55
|
+
If there are any errors with indexing the articles, they will be reported in the Backstage logs.
|
|
56
|
+
|
|
57
|
+
## Previously maintained by
|
|
58
|
+
|
|
59
|
+
- [arhill05](https://github.com/arhill05/backstage-plugin-search-backend-module-azure-devops-wiki/blob/main/README.md)
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 The Backstage Authors
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
|
|
18
|
+
|
|
19
|
+
export interface Config {
|
|
20
|
+
search?: {
|
|
21
|
+
collators?: {
|
|
22
|
+
/**
|
|
23
|
+
* Configuration options for the azure-devops backend module for the search plugin
|
|
24
|
+
*/
|
|
25
|
+
azureDevOpsWikiCollator?: {
|
|
26
|
+
/**
|
|
27
|
+
* The schedule for how often to run the collation job.
|
|
28
|
+
*/
|
|
29
|
+
schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
|
|
30
|
+
/**
|
|
31
|
+
* URL of your Azure Devops instances; required
|
|
32
|
+
*/
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
/**
|
|
35
|
+
* Personal Access Token to authenticate to the Azure DevOps instance; required
|
|
36
|
+
* @visibility secret
|
|
37
|
+
*/
|
|
38
|
+
token: string;
|
|
39
|
+
/**
|
|
40
|
+
* Array of one or more wikis to collate
|
|
41
|
+
*/
|
|
42
|
+
wikis?: [
|
|
43
|
+
{
|
|
44
|
+
/**
|
|
45
|
+
* The identifier of the wiki. This can be found by looking at the URL of the wiki in Azure DevOps.
|
|
46
|
+
* It is typically something like '{nameOfWiki}.wiki'; required
|
|
47
|
+
*/
|
|
48
|
+
wikiIdentifier: string;
|
|
49
|
+
/**
|
|
50
|
+
* The name of the organization the wiki is contained in; required.
|
|
51
|
+
*/
|
|
52
|
+
organization: string;
|
|
53
|
+
/**
|
|
54
|
+
* The name of the project the wiki is contained in; required.
|
|
55
|
+
*/
|
|
56
|
+
project: string;
|
|
57
|
+
/**
|
|
58
|
+
* A string to append to the title of articles to make them easier to identify as search results from the wiki; optional
|
|
59
|
+
*/
|
|
60
|
+
titleSuffix: string;
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var stream = require('stream');
|
|
4
|
+
var types = require('../types.cjs.js');
|
|
5
|
+
var AzureDevOpsWikiReader = require('./AzureDevOpsWikiReader.cjs.js');
|
|
6
|
+
|
|
7
|
+
class AzureDevOpsWikiArticleCollatorFactory {
|
|
8
|
+
baseUrl;
|
|
9
|
+
logger;
|
|
10
|
+
token;
|
|
11
|
+
wikis;
|
|
12
|
+
type = "azure-devops-wiki-article";
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.baseUrl = options.baseUrl;
|
|
15
|
+
this.token = options.token;
|
|
16
|
+
this.logger = options.logger;
|
|
17
|
+
this.wikis = options.wikis;
|
|
18
|
+
}
|
|
19
|
+
static fromConfig(config, options) {
|
|
20
|
+
const baseUrl = config.getOptionalString(`${types.CONFIG_SECTION_NAME}.baseUrl`);
|
|
21
|
+
const token = config.getOptionalString(`${types.CONFIG_SECTION_NAME}.token`);
|
|
22
|
+
const wikisConfig = config.getOptionalConfigArray(
|
|
23
|
+
`${types.CONFIG_SECTION_NAME}.wikis`
|
|
24
|
+
);
|
|
25
|
+
const wikis = wikisConfig?.map((wikiConfig) => {
|
|
26
|
+
return {
|
|
27
|
+
organization: wikiConfig.getString("organization"),
|
|
28
|
+
project: wikiConfig.getString("project"),
|
|
29
|
+
wikiIdentifier: wikiConfig.getString("wikiIdentifier"),
|
|
30
|
+
titleSuffix: wikiConfig.getOptionalString("titleSuffix")
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return new AzureDevOpsWikiArticleCollatorFactory({
|
|
34
|
+
...options,
|
|
35
|
+
baseUrl,
|
|
36
|
+
token,
|
|
37
|
+
wikis
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async getCollator() {
|
|
41
|
+
return stream.Readable.from(this.execute());
|
|
42
|
+
}
|
|
43
|
+
async *execute() {
|
|
44
|
+
if (this.validateNecessaryConfigurationOptions() === false) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const articles = await this.readAllArticlesFromAllWikis();
|
|
48
|
+
for (const article of articles) {
|
|
49
|
+
if (article === null || article === void 0) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
yield article;
|
|
53
|
+
}
|
|
54
|
+
this.logger.info("Done indexing Azure DevOps wiki documents");
|
|
55
|
+
}
|
|
56
|
+
validateNecessaryConfigurationOptions() {
|
|
57
|
+
if (this.wikis === void 0) {
|
|
58
|
+
this.logger.error(`No wikis configured in your app-config.yaml`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if ([
|
|
62
|
+
this.validateSingleConfigurationOptionExists(
|
|
63
|
+
this.baseUrl,
|
|
64
|
+
`${types.CONFIG_SECTION_NAME}.baseUrl`
|
|
65
|
+
),
|
|
66
|
+
this.validateSingleConfigurationOptionExists(
|
|
67
|
+
this.token,
|
|
68
|
+
`${types.CONFIG_SECTION_NAME}.token`
|
|
69
|
+
),
|
|
70
|
+
...this.wikis.flatMap((wiki, index) => {
|
|
71
|
+
return [
|
|
72
|
+
this.validateSingleConfigurationOptionExists(
|
|
73
|
+
wiki.organization,
|
|
74
|
+
`${types.CONFIG_SECTION_NAME}.wikis[${index}].organization`
|
|
75
|
+
),
|
|
76
|
+
this.validateSingleConfigurationOptionExists(
|
|
77
|
+
wiki.project,
|
|
78
|
+
`${types.CONFIG_SECTION_NAME}.wikis[${index}].project`
|
|
79
|
+
),
|
|
80
|
+
this.validateSingleConfigurationOptionExists(
|
|
81
|
+
wiki.wikiIdentifier,
|
|
82
|
+
`${types.CONFIG_SECTION_NAME}.wikis[${index}].wikiIdentifier`
|
|
83
|
+
)
|
|
84
|
+
];
|
|
85
|
+
})
|
|
86
|
+
].some((result) => !result)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
async readAllArticlesFromAllWikis() {
|
|
92
|
+
const promises = [];
|
|
93
|
+
this.wikis?.forEach(
|
|
94
|
+
(wiki) => promises.push(this.readAllArticlesFromSingleWiki(wiki))
|
|
95
|
+
);
|
|
96
|
+
const settledPromises = await Promise.allSettled(promises);
|
|
97
|
+
const fulfilledPromises = settledPromises.filter(
|
|
98
|
+
(p) => p.status === "fulfilled"
|
|
99
|
+
);
|
|
100
|
+
const articles = fulfilledPromises.flatMap((p) => p.value);
|
|
101
|
+
return articles;
|
|
102
|
+
}
|
|
103
|
+
async readAllArticlesFromSingleWiki(wiki) {
|
|
104
|
+
const reader = new AzureDevOpsWikiReader.AzureDevOpsWikiReader(
|
|
105
|
+
this.baseUrl,
|
|
106
|
+
wiki.organization,
|
|
107
|
+
wiki.project,
|
|
108
|
+
this.token,
|
|
109
|
+
wiki.wikiIdentifier,
|
|
110
|
+
this.logger,
|
|
111
|
+
wiki.titleSuffix
|
|
112
|
+
);
|
|
113
|
+
const listOfAllArticles = await reader.getListOfAllWikiPages();
|
|
114
|
+
this.logger.info(
|
|
115
|
+
`Indexing ${listOfAllArticles.length} Azure DevOps wiki documents`
|
|
116
|
+
);
|
|
117
|
+
const batchSize = 100;
|
|
118
|
+
const settledPromises = [];
|
|
119
|
+
while (listOfAllArticles.length) {
|
|
120
|
+
settledPromises.push(
|
|
121
|
+
...await Promise.allSettled(
|
|
122
|
+
listOfAllArticles.splice(0, batchSize).map((article) => reader.readSingleWikiPage(article.id))
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const result = settledPromises.map((p) => {
|
|
127
|
+
const article = p.status === "fulfilled" ? p.value : null;
|
|
128
|
+
if (article === null || article === void 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const splitPath = article?.path?.split("/");
|
|
132
|
+
const title = splitPath?.[splitPath.length - 1] ?? "Unknown Title";
|
|
133
|
+
return {
|
|
134
|
+
title: `${title}${reader.titleSuffix ?? ""}`,
|
|
135
|
+
location: article?.remoteUrl ?? "",
|
|
136
|
+
text: article?.content ?? ""
|
|
137
|
+
};
|
|
138
|
+
}).filter((article) => article !== null);
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
validateSingleConfigurationOptionExists(option, optionName) {
|
|
142
|
+
if (option === void 0) {
|
|
143
|
+
this.logger.error(`No ${optionName} configured in your app-config.yaml`);
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
exports.AzureDevOpsWikiArticleCollatorFactory = AzureDevOpsWikiArticleCollatorFactory;
|
|
151
|
+
//# sourceMappingURL=AzureDevOpsWikiArticleCollatorFactory.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AzureDevOpsWikiArticleCollatorFactory.cjs.js","sources":["../../src/collator/AzureDevOpsWikiArticleCollatorFactory.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { Readable } from 'stream';\nimport {\n DocumentCollatorFactory,\n IndexableDocument,\n} from '@backstage/plugin-search-common';\nimport {\n CONFIG_SECTION_NAME,\n WikiArticleCollatorFactoryOptions,\n WikiArticleCollatorOptions,\n WikiPage,\n} from '../types';\nimport { AzureDevOpsWikiReader } from './AzureDevOpsWikiReader';\n\nexport class AzureDevOpsWikiArticleCollatorFactory\n implements DocumentCollatorFactory\n{\n private readonly baseUrl: string | undefined;\n private readonly logger: LoggerService;\n private readonly token: string | undefined;\n private readonly wikis: WikiArticleCollatorOptions[] | undefined;\n public readonly type: string = 'azure-devops-wiki-article';\n\n private constructor(options: WikiArticleCollatorFactoryOptions) {\n this.baseUrl = options.baseUrl;\n this.token = options.token;\n this.logger = options.logger;\n this.wikis = options.wikis;\n }\n\n static fromConfig(\n config: Config,\n options: WikiArticleCollatorFactoryOptions,\n ) {\n const baseUrl = config.getOptionalString(`${CONFIG_SECTION_NAME}.baseUrl`);\n const token = config.getOptionalString(`${CONFIG_SECTION_NAME}.token`);\n const wikisConfig = config.getOptionalConfigArray(\n `${CONFIG_SECTION_NAME}.wikis`,\n );\n\n const wikis = wikisConfig?.map(wikiConfig => {\n return {\n organization: wikiConfig.getString('organization'),\n project: wikiConfig.getString('project'),\n wikiIdentifier: wikiConfig.getString('wikiIdentifier'),\n titleSuffix: wikiConfig.getOptionalString('titleSuffix'),\n };\n });\n\n return new AzureDevOpsWikiArticleCollatorFactory({\n ...options,\n baseUrl,\n token,\n wikis,\n });\n }\n\n async getCollator() {\n return Readable.from(this.execute());\n }\n\n async *execute(): AsyncGenerator<IndexableDocument> {\n if (this.validateNecessaryConfigurationOptions() === false) {\n return;\n }\n\n const articles: (IndexableDocument | null)[] =\n await this.readAllArticlesFromAllWikis();\n\n for (const article of articles) {\n if (article === null || article === undefined) {\n continue;\n }\n yield article;\n }\n\n this.logger.info('Done indexing Azure DevOps wiki documents');\n }\n\n private validateNecessaryConfigurationOptions(): boolean {\n if (this.wikis === undefined) {\n this.logger.error(`No wikis configured in your app-config.yaml`);\n return false;\n }\n if (\n [\n this.validateSingleConfigurationOptionExists(\n this.baseUrl,\n `${CONFIG_SECTION_NAME}.baseUrl`,\n ),\n this.validateSingleConfigurationOptionExists(\n this.token,\n `${CONFIG_SECTION_NAME}.token`,\n ),\n ...this.wikis.flatMap((wiki, index) => {\n return [\n this.validateSingleConfigurationOptionExists(\n wiki.organization,\n `${CONFIG_SECTION_NAME}.wikis[${index}].organization`,\n ),\n this.validateSingleConfigurationOptionExists(\n wiki.project,\n `${CONFIG_SECTION_NAME}.wikis[${index}].project`,\n ),\n this.validateSingleConfigurationOptionExists(\n wiki.wikiIdentifier,\n `${CONFIG_SECTION_NAME}.wikis[${index}].wikiIdentifier`,\n ),\n ];\n }),\n ].some(result => !result)\n ) {\n return false;\n }\n\n return true;\n }\n\n private async readAllArticlesFromAllWikis(): Promise<\n (IndexableDocument | null)[]\n > {\n const promises: Promise<(IndexableDocument | null)[]>[] = [];\n this.wikis?.forEach(wiki =>\n promises.push(this.readAllArticlesFromSingleWiki(wiki)),\n );\n\n const settledPromises = await Promise.allSettled(promises);\n const fulfilledPromises = settledPromises.filter(\n p => p.status === 'fulfilled',\n ) as PromiseFulfilledResult<(IndexableDocument | null)[]>[];\n const articles = fulfilledPromises.flatMap(p => p.value);\n return articles;\n }\n\n private async readAllArticlesFromSingleWiki(\n wiki: WikiArticleCollatorOptions,\n ): Promise<(IndexableDocument | null)[]> {\n const reader = new AzureDevOpsWikiReader(\n this.baseUrl as string,\n wiki.organization as string,\n wiki.project as string,\n this.token as string,\n wiki.wikiIdentifier as string,\n this.logger,\n wiki.titleSuffix,\n );\n\n const listOfAllArticles = await reader.getListOfAllWikiPages();\n this.logger.info(\n `Indexing ${listOfAllArticles.length} Azure DevOps wiki documents`,\n );\n\n const batchSize = 100;\n\n const settledPromises: PromiseSettledResult<WikiPage | undefined>[] = [];\n\n while (listOfAllArticles.length) {\n settledPromises.push(\n ...(await Promise.allSettled(\n listOfAllArticles\n .splice(0, batchSize)\n .map(article => reader.readSingleWikiPage(article.id)),\n )),\n );\n }\n\n const result = settledPromises\n .map(p => {\n const article = p.status === 'fulfilled' ? p.value : null;\n if (article === null || article === undefined) {\n return null;\n }\n const splitPath = article?.path?.split('/');\n const title = splitPath?.[splitPath.length - 1] ?? 'Unknown Title';\n\n return {\n title: `${title}${reader.titleSuffix ?? ''}`,\n location: article?.remoteUrl ?? '',\n text: article?.content ?? '',\n };\n })\n .filter(article => article !== null);\n\n return result;\n }\n\n private validateSingleConfigurationOptionExists(\n option: string | undefined,\n optionName: string,\n ): boolean {\n if (option === undefined) {\n this.logger.error(`No ${optionName} configured in your app-config.yaml`);\n return false;\n }\n\n return true;\n }\n}\n"],"names":["CONFIG_SECTION_NAME","Readable","AzureDevOpsWikiReader"],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var utils = require('../utils.cjs.js');
|
|
4
|
+
|
|
5
|
+
class AzureDevOpsWikiReader {
|
|
6
|
+
constructor(baseUrl, organization, project, token, wikiIdentifier, logger, titleSuffix) {
|
|
7
|
+
this.baseUrl = baseUrl;
|
|
8
|
+
this.token = token;
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
this.titleSuffix = titleSuffix;
|
|
11
|
+
this.organization = organization;
|
|
12
|
+
this.project = project;
|
|
13
|
+
this.wikiIdentifier = wikiIdentifier;
|
|
14
|
+
}
|
|
15
|
+
logger;
|
|
16
|
+
organization;
|
|
17
|
+
project;
|
|
18
|
+
wikiIdentifier;
|
|
19
|
+
titleSuffix;
|
|
20
|
+
getListOfAllWikiPages = async () => {
|
|
21
|
+
this.logger.info(
|
|
22
|
+
`Retrieving list of all Azure DevOps wiki pages for wiki ${this.wikiIdentifier} in project ${this.project} in organization ${this.organization}`
|
|
23
|
+
);
|
|
24
|
+
const wikiPageDetails = [];
|
|
25
|
+
let hasMorePages = true;
|
|
26
|
+
let continuationToken = null;
|
|
27
|
+
this.logger.info(`Reading ADO wiki pages from wiki ${this.wikiIdentifier}`);
|
|
28
|
+
while (hasMorePages) {
|
|
29
|
+
const body = continuationToken !== null ? { continuationToken } : {};
|
|
30
|
+
const response = await this.fetch(
|
|
31
|
+
"pagesBatch?api-version=6.0-preview.1",
|
|
32
|
+
{
|
|
33
|
+
method: "POST",
|
|
34
|
+
body: JSON.stringify(body),
|
|
35
|
+
headers: { "Content-type": "application/json" }
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
for (const item of data.value) {
|
|
40
|
+
wikiPageDetails.push(item);
|
|
41
|
+
}
|
|
42
|
+
continuationToken = response.headers.get("x-ms-continuationtoken");
|
|
43
|
+
if (!continuationToken) {
|
|
44
|
+
hasMorePages = false;
|
|
45
|
+
this.logger.info(
|
|
46
|
+
`Found ${wikiPageDetails.length} pages in wiki ${this.wikiIdentifier} in project ${this.project} in organization ${this.organization}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return wikiPageDetails;
|
|
51
|
+
};
|
|
52
|
+
readSingleWikiPage = async (id) => {
|
|
53
|
+
let rawPageContent;
|
|
54
|
+
try {
|
|
55
|
+
const pageResponse = await this.fetch(`/pages/${id}?includeContent=true`);
|
|
56
|
+
rawPageContent = await pageResponse.json();
|
|
57
|
+
return rawPageContent;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
this.logger.error(
|
|
60
|
+
`Problem reading page with in wiki ${this.wikiIdentifier} with id ${id} - ${err} - ${rawPageContent}`
|
|
61
|
+
);
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
fetch = async (url, options) => {
|
|
66
|
+
const credentials = btoa(`:${this.token}`);
|
|
67
|
+
return utils.fetchWithRetry(
|
|
68
|
+
`${utils.buildBaseUrl(
|
|
69
|
+
this.baseUrl,
|
|
70
|
+
this.organization,
|
|
71
|
+
this.project,
|
|
72
|
+
this.wikiIdentifier
|
|
73
|
+
)}/${url}`,
|
|
74
|
+
{
|
|
75
|
+
...options,
|
|
76
|
+
headers: { ...options?.headers, Authorization: `Basic ${credentials}` }
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exports.AzureDevOpsWikiReader = AzureDevOpsWikiReader;
|
|
83
|
+
//# sourceMappingURL=AzureDevOpsWikiReader.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AzureDevOpsWikiReader.cjs.js","sources":["../../src/collator/AzureDevOpsWikiReader.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { WikiPageDetail, WikiPage } from '../types';\nimport { buildBaseUrl, fetchWithRetry } from '../utils';\n\nexport class AzureDevOpsWikiReader {\n private readonly logger: LoggerService;\n private readonly organization: string;\n private readonly project: string;\n private readonly wikiIdentifier: string;\n public titleSuffix?: string;\n constructor(\n private readonly baseUrl: string,\n organization: string,\n project: string,\n private readonly token: string,\n wikiIdentifier: string,\n logger: LoggerService,\n titleSuffix?: string,\n ) {\n this.logger = logger;\n this.titleSuffix = titleSuffix;\n this.organization = organization;\n this.project = project;\n this.wikiIdentifier = wikiIdentifier;\n }\n\n getListOfAllWikiPages = async () => {\n this.logger.info(\n `Retrieving list of all Azure DevOps wiki pages for wiki ${this.wikiIdentifier} in project ${this.project} in organization ${this.organization}`,\n );\n\n const wikiPageDetails: WikiPageDetail[] = [];\n\n let hasMorePages = true;\n let continuationToken: string | null = null;\n\n this.logger.info(`Reading ADO wiki pages from wiki ${this.wikiIdentifier}`);\n\n while (hasMorePages) {\n const body = continuationToken !== null ? { continuationToken } : {};\n\n const response = await this.fetch(\n 'pagesBatch?api-version=6.0-preview.1',\n {\n method: 'POST',\n body: JSON.stringify(body),\n headers: { 'Content-type': 'application/json' },\n },\n );\n\n const data = (await response.json()) as { value: WikiPageDetail[] };\n\n for (const item of data.value) {\n wikiPageDetails.push(item);\n }\n\n continuationToken = response.headers.get('x-ms-continuationtoken');\n\n if (!continuationToken) {\n hasMorePages = false;\n this.logger.info(\n `Found ${wikiPageDetails.length} pages in wiki ${this.wikiIdentifier} in project ${this.project} in organization ${this.organization}`,\n );\n }\n }\n\n return wikiPageDetails;\n };\n\n readSingleWikiPage = async (id: number): Promise<WikiPage> => {\n let rawPageContent;\n try {\n const pageResponse = await this.fetch(`/pages/${id}?includeContent=true`);\n\n rawPageContent = await pageResponse.json();\n return rawPageContent;\n } catch (err) {\n this.logger.error(\n `Problem reading page with in wiki ${this.wikiIdentifier} with id ${id} - ${err} - ${rawPageContent}`,\n );\n throw err;\n }\n };\n\n fetch: typeof fetchWithRetry = async (url, options) => {\n const credentials = btoa(`:${this.token}`);\n\n return fetchWithRetry(\n `${buildBaseUrl(\n this.baseUrl,\n this.organization,\n this.project,\n this.wikiIdentifier,\n )}/${url}`,\n {\n ...options,\n headers: { ...options?.headers, Authorization: `Basic ${credentials}` },\n },\n );\n };\n}\n"],"names":["fetchWithRetry","buildBaseUrl"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var types = require('./types.cjs.js');
|
|
5
|
+
|
|
6
|
+
const defaults = {
|
|
7
|
+
schedule: {
|
|
8
|
+
frequency: { minutes: 10 },
|
|
9
|
+
timeout: { minutes: 15 },
|
|
10
|
+
initialDelay: { seconds: 3 }
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function readScheduleConfigOptions(configRoot) {
|
|
14
|
+
let schedule = void 0;
|
|
15
|
+
const config = configRoot.getOptionalConfig(types.CONFIG_SECTION_NAME);
|
|
16
|
+
if (config) {
|
|
17
|
+
const scheduleConfig = config.getOptionalConfig("schedule");
|
|
18
|
+
if (scheduleConfig) {
|
|
19
|
+
schedule = backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(scheduleConfig);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return schedule ?? defaults.schedule;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
exports.defaults = defaults;
|
|
26
|
+
exports.readScheduleConfigOptions = readScheduleConfigOptions;
|
|
27
|
+
//# sourceMappingURL=config.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../src/config.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { CONFIG_SECTION_NAME } from './types';\n\nexport const defaults = {\n schedule: {\n frequency: { minutes: 10 },\n timeout: { minutes: 15 },\n initialDelay: { seconds: 3 },\n },\n};\n\nexport function readScheduleConfigOptions(\n configRoot: Config,\n): SchedulerServiceTaskScheduleDefinition {\n let schedule: SchedulerServiceTaskScheduleDefinition | undefined = undefined;\n\n const config = configRoot.getOptionalConfig(CONFIG_SECTION_NAME);\n if (config) {\n const scheduleConfig = config.getOptionalConfig('schedule');\n if (scheduleConfig) {\n schedule =\n readSchedulerServiceTaskScheduleDefinitionFromConfig(scheduleConfig);\n }\n }\n\n return schedule ?? defaults.schedule;\n}\n"],"names":["CONFIG_SECTION_NAME","readSchedulerServiceTaskScheduleDefinitionFromConfig"],"mappings":";;;;;AAuBO,MAAM,QAAW,GAAA;AAAA,EACtB,QAAU,EAAA;AAAA,IACR,SAAA,EAAW,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,IACzB,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,IACvB,YAAA,EAAc,EAAE,OAAA,EAAS,CAAE;AAAA;AAE/B;AAEO,SAAS,0BACd,UACwC,EAAA;AACxC,EAAA,IAAI,QAA+D,GAAA,KAAA,CAAA;AAEnE,EAAM,MAAA,MAAA,GAAS,UAAW,CAAA,iBAAA,CAAkBA,yBAAmB,CAAA;AAC/D,EAAA,IAAI,MAAQ,EAAA;AACV,IAAM,MAAA,cAAA,GAAiB,MAAO,CAAA,iBAAA,CAAkB,UAAU,CAAA;AAC1D,IAAA,IAAI,cAAgB,EAAA;AAClB,MAAA,QAAA,GACEC,sEAAqD,cAAc,CAAA;AAAA;AACvE;AAGF,EAAA,OAAO,YAAY,QAAS,CAAA,QAAA;AAC9B;;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var alpha = require('@backstage/plugin-search-backend-node/alpha');
|
|
5
|
+
var AzureDevOpsWikiArticleCollatorFactory = require('./collator/AzureDevOpsWikiArticleCollatorFactory.cjs.js');
|
|
6
|
+
var config = require('./config.cjs.js');
|
|
7
|
+
|
|
8
|
+
const searchModuleAzureDevOps = backendPluginApi.createBackendModule({
|
|
9
|
+
pluginId: "search",
|
|
10
|
+
moduleId: "azure-devops",
|
|
11
|
+
register(reg) {
|
|
12
|
+
reg.registerInit({
|
|
13
|
+
deps: {
|
|
14
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
15
|
+
scheduler: backendPluginApi.coreServices.scheduler,
|
|
16
|
+
logger: backendPluginApi.coreServices.logger,
|
|
17
|
+
indexRegistry: alpha.searchIndexRegistryExtensionPoint
|
|
18
|
+
},
|
|
19
|
+
async init({ config: config$1, scheduler, logger, indexRegistry }) {
|
|
20
|
+
indexRegistry.addCollator({
|
|
21
|
+
schedule: scheduler.createScheduledTaskRunner(
|
|
22
|
+
config.readScheduleConfigOptions(config$1)
|
|
23
|
+
),
|
|
24
|
+
factory: AzureDevOpsWikiArticleCollatorFactory.AzureDevOpsWikiArticleCollatorFactory.fromConfig(config$1, {
|
|
25
|
+
logger
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
exports.searchModuleAzureDevOps = searchModuleAzureDevOps;
|
|
34
|
+
//# sourceMappingURL=module.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';\nimport { AzureDevOpsWikiArticleCollatorFactory } from './collator';\nimport { readScheduleConfigOptions } from './config';\n\n/** @public */\nexport const searchModuleAzureDevOps = createBackendModule({\n pluginId: 'search',\n moduleId: 'azure-devops',\n register(reg) {\n reg.registerInit({\n deps: {\n config: coreServices.rootConfig,\n scheduler: coreServices.scheduler,\n logger: coreServices.logger,\n indexRegistry: searchIndexRegistryExtensionPoint,\n },\n async init({ config, scheduler, logger, indexRegistry }) {\n indexRegistry.addCollator({\n schedule: scheduler.createScheduledTaskRunner(\n readScheduleConfigOptions(config),\n ),\n factory: AzureDevOpsWikiArticleCollatorFactory.fromConfig(config, {\n logger,\n }),\n });\n },\n });\n },\n});\n"],"names":["createBackendModule","coreServices","searchIndexRegistryExtensionPoint","config","readScheduleConfigOptions","AzureDevOpsWikiArticleCollatorFactory"],"mappings":";;;;;;;AAwBO,MAAM,0BAA0BA,oCAAoB,CAAA;AAAA,EACzD,QAAU,EAAA,QAAA;AAAA,EACV,QAAU,EAAA,cAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,QACxB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,aAAe,EAAAC;AAAA,OACjB;AAAA,MACA,MAAM,IAAK,CAAA,UAAEC,UAAQ,SAAW,EAAA,MAAA,EAAQ,eAAiB,EAAA;AACvD,QAAA,aAAA,CAAc,WAAY,CAAA;AAAA,UACxB,UAAU,SAAU,CAAA,yBAAA;AAAA,YAClBC,iCAA0BD,QAAM;AAAA,WAClC;AAAA,UACA,OAAA,EAASE,2EAAsC,CAAA,UAAA,CAAWF,QAAQ,EAAA;AAAA,YAChE;AAAA,WACD;AAAA,SACF,CAAA;AAAA;AACH,KACD,CAAA;AAAA;AAEL,CAAC;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.cjs.js","sources":["../src/types.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport const CONFIG_SECTION_NAME = 'search.collators.azureDevOpsWikiCollator';\n\nexport type WikiArticleCollatorOptions = {\n organization?: string;\n project?: string;\n wikiIdentifier?: string;\n titleSuffix?: string;\n};\n\nexport type WikiArticleCollatorFactoryOptions = {\n baseUrl?: string;\n token?: string;\n wikis?: WikiArticleCollatorOptions[];\n logger: LoggerService;\n};\n\nexport type WikiPageDetail = {\n id: number;\n path: string;\n};\n\nexport type WikiPage = {\n id: number;\n gitItemPath: string;\n content: string;\n isNonConformant: boolean;\n isParentPage: boolean;\n order: number;\n path: string;\n remoteUrl: string;\n subPages: WikiPage[];\n url: string;\n};\n"],"names":[],"mappings":";;AAkBO,MAAM,mBAAsB,GAAA;;;;"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
const buildBaseUrl = (baseUrl, organization, project, wikiIdentifier) => `${baseUrl}/${organization}/${project}/_apis/wiki/wikis/${wikiIdentifier}`;
|
|
6
|
+
async function fetchWithRetry(url, options = {}, retries = 3, backoff = 300) {
|
|
7
|
+
try {
|
|
8
|
+
const response = await fetch(url, options);
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
throw await errors.ResponseError.fromResponse(response);
|
|
11
|
+
}
|
|
12
|
+
return response;
|
|
13
|
+
} catch (err) {
|
|
14
|
+
if (retries > 0) {
|
|
15
|
+
await new Promise((res) => setTimeout(res, backoff));
|
|
16
|
+
return fetchWithRetry(url, options, retries - 1, backoff * 2);
|
|
17
|
+
}
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.buildBaseUrl = buildBaseUrl;
|
|
23
|
+
exports.fetchWithRetry = fetchWithRetry;
|
|
24
|
+
//# sourceMappingURL=utils.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.cjs.js","sources":["../src/utils.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseError } from '@backstage/errors';\n\nexport const buildBaseUrl = (\n baseUrl: string,\n organization: string,\n project: string,\n wikiIdentifier: string,\n): string =>\n `${baseUrl}/${organization}/${project}/_apis/wiki/wikis/${wikiIdentifier}`;\n\nexport const convertStringToBase64 = (stringToConvert: string): string =>\n Buffer.from(stringToConvert).toString('base64');\n\nexport async function fetchWithRetry(\n url: Parameters<typeof fetch>[0],\n options: Parameters<typeof fetch>[1] = {},\n retries = 3,\n backoff = 300,\n) {\n try {\n const response = await fetch(url, options);\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n return response;\n } catch (err) {\n if (retries > 0) {\n await new Promise(res => setTimeout(res, backoff));\n return fetchWithRetry(url, options, retries - 1, backoff * 2);\n }\n throw err;\n }\n}\n"],"names":["ResponseError"],"mappings":";;;;AAkBO,MAAM,YAAe,GAAA,CAC1B,OACA,EAAA,YAAA,EACA,OACA,EAAA,cAAA,KAEA,CAAG,EAAA,OAAO,CAAI,CAAA,EAAA,YAAY,CAAI,CAAA,EAAA,OAAO,qBAAqB,cAAc,CAAA;AAKpD,eAAA,cAAA,CACpB,KACA,OAAuC,GAAA,IACvC,OAAU,GAAA,CAAA,EACV,UAAU,GACV,EAAA;AACA,EAAI,IAAA;AACF,IAAA,MAAM,QAAW,GAAA,MAAM,KAAM,CAAA,GAAA,EAAK,OAAO,CAAA;AACzC,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAMA,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA;AAAA;AAEjD,IAAO,OAAA,QAAA;AAAA,WACA,GAAK,EAAA;AACZ,IAAA,IAAI,UAAU,CAAG,EAAA;AACf,MAAA,MAAM,IAAI,OAAQ,CAAA,CAAA,GAAA,KAAO,UAAW,CAAA,GAAA,EAAK,OAAO,CAAC,CAAA;AACjD,MAAA,OAAO,eAAe,GAAK,EAAA,OAAA,EAAS,OAAU,GAAA,CAAA,EAAG,UAAU,CAAC,CAAA;AAAA;AAE9D,IAAM,MAAA,GAAA;AAAA;AAEV;;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"license": "Apache-2.0",
|
|
4
|
+
"name": "@backstage-community/plugin-search-backend-module-azure-devops",
|
|
5
|
+
"description": "The azure-devops backend module for the search plugin.",
|
|
6
|
+
"main": "dist/index.cjs.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"main": "dist/index.cjs.js",
|
|
11
|
+
"types": "dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/backstage/community-plugins",
|
|
16
|
+
"directory": "workspaces/azure-devops/plugins/search-backend-module-azure-devops"
|
|
17
|
+
},
|
|
18
|
+
"backstage": {
|
|
19
|
+
"role": "backend-plugin-module",
|
|
20
|
+
"pluginId": "search",
|
|
21
|
+
"pluginPackage": "@backstage/plugin-search-backend",
|
|
22
|
+
"features": {
|
|
23
|
+
".": "@backstage/BackendFeature"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"start": "backstage-cli package start",
|
|
28
|
+
"build": "backstage-cli package build",
|
|
29
|
+
"lint": "backstage-cli package lint",
|
|
30
|
+
"test": "backstage-cli package test",
|
|
31
|
+
"clean": "backstage-cli package clean",
|
|
32
|
+
"prepack": "backstage-cli package prepack",
|
|
33
|
+
"postpack": "backstage-cli package postpack"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@backstage/backend-plugin-api": "^1.5.0",
|
|
37
|
+
"@backstage/config": "^1.3.6",
|
|
38
|
+
"@backstage/errors": "^1.2.7",
|
|
39
|
+
"@backstage/plugin-search-backend-node": "^1.3.17",
|
|
40
|
+
"@backstage/plugin-search-common": "^1.2.21",
|
|
41
|
+
"axios": "^1.4.0",
|
|
42
|
+
"axios-retry": "^3.5.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@backstage/backend-test-utils": "^1.10.0",
|
|
46
|
+
"@backstage/cli": "^0.34.5"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"config.d.ts"
|
|
51
|
+
],
|
|
52
|
+
"typesVersions": {
|
|
53
|
+
"*": {
|
|
54
|
+
"package.json": [
|
|
55
|
+
"package.json"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|