@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-github 0.0.0-snapshot-20251029145101

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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-github
2
+
3
+ The ingestor-github backend module for the ai-assistant plugin.
4
+
5
+ _This plugin was created through the Backstage CLI_
package/config.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * GitHub ingestor configuration
3
+ * @visibility backend
4
+ */
5
+ export interface Config {
6
+ aiAssistant: {
7
+ ingestors: {
8
+ github: {
9
+ /**
10
+ * The GitHub organization or user name
11
+ */
12
+ owner: string;
13
+ /**
14
+ * GitHub App ID
15
+ * @visibility backend
16
+ */
17
+ appId: string | number;
18
+ /**
19
+ * GitHub App private key
20
+ * @visibility secret
21
+ */
22
+ privateKey: string;
23
+ /**
24
+ * GitHub App installation ID
25
+ * @visibility backend
26
+ */
27
+ installationId: string | number;
28
+ /**
29
+ * Optional GitHub API base URL (for GitHub Enterprise). Defaults to https://api.github.com
30
+ */
31
+ baseUrl?: string;
32
+ /**
33
+ * Optional list of file types to ingest (e.g., .md, .json). Defaults to .md and .json if not specified.
34
+ */
35
+ fileTypes?: string[];
36
+ /**
37
+ * Optional batch size for processing files. Defaults to 50 files per batch if not specified.
38
+ */
39
+ filesBatchSize?: number;
40
+ /**
41
+ * Optional list of repositories to ingest. If not specified, all repositories for the owner will be ingested.
42
+ */
43
+ repositories?: {
44
+ /**
45
+ * The name of the repository to ingest
46
+ */
47
+ name: string;
48
+ /**
49
+ * Optional list of file types to ingest for this repository. Overrides the global fileTypes setting for this repository only.
50
+ */
51
+ fileTypes?: string[];
52
+ }[];
53
+ };
54
+ };
55
+ };
56
+ }
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_FILE_BATCH_SIZE = 50;
4
+
5
+ exports.DEFAULT_FILE_BATCH_SIZE = DEFAULT_FILE_BATCH_SIZE;
6
+ //# sourceMappingURL=default-file-batch-size.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-file-batch-size.cjs.js","sources":["../../src/constants/default-file-batch-size.ts"],"sourcesContent":["export const DEFAULT_FILE_BATCH_SIZE = 50;\n"],"names":[],"mappings":";;AAAO,MAAM,uBAAA,GAA0B;;;;"}
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ const MODULE_ID = "github";
4
+
5
+ exports.MODULE_ID = MODULE_ID;
6
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../../src/constants/module.ts"],"sourcesContent":["export const MODULE_ID = 'github';\n"],"names":[],"mappings":";;AAAO,MAAM,SAAA,GAAY;;;;"}
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var module$1 = require('./module.cjs.js');
6
+
7
+
8
+
9
+ exports.default = module$1.aiAssistantModuleIngestorGithub;
10
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
@@ -0,0 +1,5 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+
3
+ declare const aiAssistantModuleIngestorGithub: _backstage_backend_plugin_api.BackendFeature;
4
+
5
+ export { aiAssistantModuleIngestorGithub as default };
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var ingestor = require('./services/ingestor.cjs.js');
5
+ var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
6
+
7
+ const aiAssistantModuleIngestorGithub = backendPluginApi.createBackendModule({
8
+ pluginId: "ai-assistant",
9
+ moduleId: "ingestor-github",
10
+ register(reg) {
11
+ reg.registerInit({
12
+ deps: {
13
+ dataIngestor: backstagePluginAiAssistantNode.dataIngestorExtensionPoint,
14
+ config: backendPluginApi.coreServices.rootConfig,
15
+ logger: backendPluginApi.coreServices.logger
16
+ },
17
+ async init({ config, logger, dataIngestor }) {
18
+ dataIngestor.registerIngestor(
19
+ await ingestor.createGitHubIngestor({ config, logger })
20
+ );
21
+ }
22
+ });
23
+ }
24
+ });
25
+
26
+ exports.aiAssistantModuleIngestorGithub = aiAssistantModuleIngestorGithub;
27
+ //# sourceMappingURL=module.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs.js","sources":["../src/module.ts"],"sourcesContent":["import {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { createGitHubIngestor } from './services/ingestor';\nimport { dataIngestorExtensionPoint } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport const aiAssistantModuleIngestorGithub = createBackendModule({\n pluginId: 'ai-assistant',\n moduleId: 'ingestor-github',\n register(reg) {\n reg.registerInit({\n deps: {\n dataIngestor: dataIngestorExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n },\n async init({ config, logger, dataIngestor }) {\n dataIngestor.registerIngestor(\n await createGitHubIngestor({ config, logger }),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","dataIngestorExtensionPoint","coreServices","createGitHubIngestor"],"mappings":";;;;;;AAOO,MAAM,kCAAkCA,oCAAA,CAAoB;AAAA,EACjE,QAAA,EAAU,cAAA;AAAA,EACV,QAAA,EAAU,iBAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,YAAA,EAAcC,yDAAA;AAAA,QACd,QAAQC,6BAAA,CAAa,UAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa;AAAA,OACvB;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,MAAA,EAAQ,cAAa,EAAG;AAC3C,QAAA,YAAA,CAAa,gBAAA;AAAA,UACX,MAAMC,6BAAA,CAAqB,EAAE,MAAA,EAAQ,QAAQ;AAAA,SAC/C;AAAA,MACF;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const createGitHubService = async ({
4
+ config,
5
+ logger
6
+ }) => {
7
+ const { App } = await import('octokit');
8
+ const owner = config.getString("aiAssistant.ingestors.github.owner");
9
+ const appId = config.getString("aiAssistant.ingestors.github.appId");
10
+ const privateKey = config.getString(
11
+ "aiAssistant.ingestors.github.privateKey"
12
+ );
13
+ const installationId = config.getNumber(
14
+ "aiAssistant.ingestors.github.installationId"
15
+ );
16
+ const baseUrl = config.getOptionalString(
17
+ "aiAssistant.ingestors.github.baseUrl"
18
+ );
19
+ logger.info(`Connecting to GitHub App for owner: ${owner}`);
20
+ if (!owner || !appId || !privateKey || !installationId) {
21
+ throw new Error(
22
+ "GitHub owner, appId, privateKey, and installationId are required"
23
+ );
24
+ }
25
+ const app = new App({
26
+ appId,
27
+ privateKey,
28
+ ...baseUrl && { baseUrl }
29
+ });
30
+ const octokit = await app.getInstallationOctokit(installationId);
31
+ logger.info(`Connected to GitHub App for owner: ${owner}`);
32
+ const getRepos = async () => {
33
+ const { data: repositories } = await octokit.rest.apps.listReposAccessibleToInstallation({
34
+ per_page: 100
35
+ });
36
+ const repos = repositories.repositories.filter(
37
+ (repo) => repo.owner?.login?.toLowerCase() === owner.toLowerCase()
38
+ );
39
+ logger.info(`Found ${repos.length} repositories for owner ${owner}`);
40
+ return repos;
41
+ };
42
+ const getRepoFiles = async (repoName, fileTypes) => {
43
+ const { data: tree } = await octokit.rest.git.getTree({
44
+ owner,
45
+ repo: repoName,
46
+ tree_sha: "HEAD",
47
+ recursive: "true"
48
+ });
49
+ const files = tree.tree.filter((item) => item.type === "blob");
50
+ logger.info(`Found ${files.length} files in GitHub repository ${repoName}`);
51
+ if (fileTypes && fileTypes.length > 0) {
52
+ const filteredFiles = files.filter(
53
+ (file) => fileTypes.some((type) => file.path?.endsWith(type))
54
+ );
55
+ logger.info(
56
+ `Filtered to ${filteredFiles.length} files with types: ${fileTypes.join(
57
+ ", "
58
+ )}`
59
+ );
60
+ return filteredFiles;
61
+ }
62
+ return files;
63
+ };
64
+ const getRepoFileContent = async (repoName, path) => {
65
+ const { data: fileContent } = await octokit.rest.repos.getContent({
66
+ owner,
67
+ repo: repoName,
68
+ path
69
+ });
70
+ if (Array.isArray(fileContent)) {
71
+ throw new Error(`Expected file but got directory for path: ${path}`);
72
+ }
73
+ if (!("content" in fileContent) || fileContent.type !== "file") {
74
+ throw new Error(
75
+ `Expected file but got ${fileContent.type} for path: ${path}`
76
+ );
77
+ }
78
+ const content = Buffer.from(fileContent.content, "base64").toString(
79
+ "utf-8"
80
+ );
81
+ return content;
82
+ };
83
+ return { owner, getRepos, getRepoFiles, getRepoFileContent };
84
+ };
85
+
86
+ exports.createGitHubService = createGitHubService;
87
+ //# sourceMappingURL=github.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.cjs.js","sources":["../../src/services/github.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\n\nexport const createGitHubService = async ({\n config,\n logger,\n}: {\n config: RootConfigService;\n logger: LoggerService;\n}) => {\n // Dynamic import for ESM-only octokit v5\n const { App } = await import('octokit');\n // Get configuration values\n const owner = config.getString('aiAssistant.ingestors.github.owner');\n const appId = config.getString('aiAssistant.ingestors.github.appId');\n const privateKey = config.getString(\n 'aiAssistant.ingestors.github.privateKey',\n );\n const installationId = config.getNumber(\n 'aiAssistant.ingestors.github.installationId',\n );\n const baseUrl = config.getOptionalString(\n 'aiAssistant.ingestors.github.baseUrl',\n );\n\n logger.info(`Connecting to GitHub App for owner: ${owner}`);\n\n if (!owner || !appId || !privateKey || !installationId) {\n throw new Error(\n 'GitHub owner, appId, privateKey, and installationId are required',\n );\n }\n\n // Create GitHub App instance\n const app = new App({\n appId,\n privateKey,\n ...(baseUrl && { baseUrl }),\n });\n\n // Get installation-specific Octokit instance\n const octokit = await app.getInstallationOctokit(installationId);\n\n logger.info(`Connected to GitHub App for owner: ${owner}`);\n\n /**\n * Get a list of repositories for the specified GitHub owner\n * @returns List of repositories for the specified GitHub owner\n */\n const getRepos = async () => {\n const { data: repositories } =\n await octokit.rest.apps.listReposAccessibleToInstallation({\n per_page: 100,\n });\n\n // Filter repositories by owner if needed\n const repos = repositories.repositories.filter(\n repo => repo.owner?.login?.toLowerCase() === owner.toLowerCase(),\n );\n\n logger.info(`Found ${repos.length} repositories for owner ${owner}`);\n\n return repos;\n };\n\n /**\n * Get a list of files in the specified GitHub repository\n * @param repoName The name of the repository\n * @param fileTypes Optional list of file types to filter by\n * @returns List of files in the specified GitHub repository\n */\n const getRepoFiles = async (repoName: string, fileTypes?: string[]) => {\n const { data: tree } = await octokit.rest.git.getTree({\n owner,\n repo: repoName,\n tree_sha: 'HEAD',\n recursive: 'true',\n });\n\n // Filter to only files (not directories)\n const files = tree.tree.filter((item: any) => item.type === 'blob');\n\n logger.info(`Found ${files.length} files in GitHub repository ${repoName}`);\n\n if (fileTypes && fileTypes.length > 0) {\n const filteredFiles = files.filter((file: any) =>\n fileTypes.some(type => file.path?.endsWith(type)),\n );\n logger.info(\n `Filtered to ${filteredFiles.length} files with types: ${fileTypes.join(\n ', ',\n )}`,\n );\n return filteredFiles;\n }\n\n return files;\n };\n\n /**\n * Get the content of a specific file in a GitHub repository\n * @param repoName The name of the repository\n * @param path The path of the file\n * @returns The content of the file\n */\n const getRepoFileContent = async (repoName: string, path: string) => {\n const { data: fileContent } = await octokit.rest.repos.getContent({\n owner,\n repo: repoName,\n path,\n });\n\n if (Array.isArray(fileContent)) {\n throw new Error(`Expected file but got directory for path: ${path}`);\n }\n\n if (!('content' in fileContent) || fileContent.type !== 'file') {\n throw new Error(\n `Expected file but got ${fileContent.type} for path: ${path}`,\n );\n }\n\n // Decode base64 content\n const content = Buffer.from(fileContent.content, 'base64').toString(\n 'utf-8',\n );\n\n return content;\n };\n\n return { owner, getRepos, getRepoFiles, getRepoFileContent };\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+
3
+ var github = require('./github.cjs.js');
4
+ var module$1 = require('../constants/module.cjs.js');
5
+ var backstagePluginAiAssistantCommon = require('@sweetoburrito/backstage-plugin-ai-assistant-common');
6
+ var defaultFileBatchSize = require('../constants/default-file-batch-size.cjs.js');
7
+
8
+ const createGitHubIngestor = async ({
9
+ config,
10
+ logger
11
+ }) => {
12
+ const defaultFileTypes = [".md", ".json"];
13
+ const repositoriesFilter = config.getOptional("aiAssistant.ingestors.github.repositories");
14
+ const fileTypes = config.getOptionalStringArray("aiAssistant.ingestors.github.fileTypes") ?? defaultFileTypes;
15
+ const filesBatchSize = config.getOptionalNumber("aiAssistant.ingestors.github.filesBatchSize") ?? defaultFileBatchSize.DEFAULT_FILE_BATCH_SIZE;
16
+ const githubService = await github.createGitHubService({ config, logger });
17
+ const ingestRepositoryByFileBatch = async ({
18
+ repo,
19
+ files,
20
+ saveDocumentsBatch
21
+ }) => {
22
+ logger.info(
23
+ `Processing ${files.length} files from repository "${repo.name}" in batches of ${filesBatchSize}`
24
+ );
25
+ let totalDocumentsIngested = 0;
26
+ const totalBatches = Math.ceil(files.length / filesBatchSize);
27
+ for (let batchStart = 0; batchStart < files.length; batchStart += filesBatchSize) {
28
+ const batchEnd = Math.min(batchStart + filesBatchSize, files.length);
29
+ const filesBatch = files.slice(batchStart, batchEnd);
30
+ const batchNumber = Math.floor(batchStart / filesBatchSize) + 1;
31
+ logger.info(
32
+ `Processing batch ${batchNumber}/${totalBatches} (${filesBatch.length} files) for repository "${repo.name}"`
33
+ );
34
+ const documents = [];
35
+ for (let index = 0; index < filesBatch.length; index++) {
36
+ const file = filesBatch[index];
37
+ const globalIndex = batchStart + index;
38
+ try {
39
+ const content = await githubService.getRepoFileContent(
40
+ repo.name,
41
+ file.path
42
+ );
43
+ const completionStats = backstagePluginAiAssistantCommon.getProgressStats(
44
+ globalIndex + 1,
45
+ files.length
46
+ );
47
+ logger.info(
48
+ `Retrieved content for GitHub file: "${file.path}" in repository: "${repo.name}" [Progress: ${completionStats.completed}/${completionStats.total} (${completionStats.percentage}%) completed of repository]`
49
+ );
50
+ const githubUrl = `https://github.com/${githubService.owner}/${repo.name}/blob/${repo.default_branch || "main"}/${file.path}`;
51
+ const enhancedContent = `Repository: ${repo.name}
52
+ File Path: ${file.path}
53
+ GitHub URL: ${githubUrl}
54
+ ${repo.description ? `Repository Description: ${repo.description}` : ""}
55
+
56
+ Content:
57
+ ${content}`;
58
+ const document = {
59
+ metadata: {
60
+ source: module$1.MODULE_ID,
61
+ id: `${repo.id}:${file.path}`,
62
+ url: githubUrl,
63
+ owner: githubService.owner,
64
+ repository: repo.name,
65
+ filePath: file.path,
66
+ fileName: file.path?.split("/").pop() || "",
67
+ branch: repo.default_branch || "main",
68
+ repositoryDescription: repo.description || ""
69
+ },
70
+ content: enhancedContent
71
+ };
72
+ documents.push(document);
73
+ } catch (error) {
74
+ logger.warn(
75
+ `Failed to retrieve content for GitHub file: ${file.path}. Error: ${error}`
76
+ );
77
+ continue;
78
+ }
79
+ }
80
+ await saveDocumentsBatch(documents);
81
+ totalDocumentsIngested += documents.length;
82
+ logger.info(
83
+ `Batch ${batchNumber}/${totalBatches} completed: ${documents.length} documents ingested for GitHub repository: ${repo.name}`
84
+ );
85
+ }
86
+ return { totalDocumentsIngested };
87
+ };
88
+ const ingestGitHubBatch = async (saveDocumentsBatch) => {
89
+ const repositoriesList = await githubService.getRepos();
90
+ if (repositoriesList.length === 0) {
91
+ logger.warn("No repositories found for the GitHub owner");
92
+ return;
93
+ }
94
+ logger.info(
95
+ `Filtering for repositories: ${repositoriesFilter?.map((repo) => repo.name).join(", ")}`
96
+ );
97
+ const repositoriesToIngest = repositoriesFilter ? repositoriesList.filter(
98
+ (repo) => repositoriesFilter?.some(
99
+ (filteredRepo) => filteredRepo.name.toLowerCase() === repo.name.toLowerCase()
100
+ )
101
+ ) : repositoriesList;
102
+ if (repositoriesToIngest.length === 0) {
103
+ logger.warn(
104
+ "No repositories found for ingestion after applying the filter"
105
+ );
106
+ return;
107
+ }
108
+ logger.info(
109
+ `Ingesting ${repositoriesToIngest.length} repositories from GitHub`
110
+ );
111
+ for (const repo of repositoriesToIngest) {
112
+ logger.info(
113
+ `Beginning ingestion for repository: ${repo.name} (${repo.id})`
114
+ );
115
+ const repositoryFileTypesFilter = repositoriesFilter?.find(
116
+ (r) => r.name.toLowerCase() === repo.name.toLowerCase()
117
+ )?.fileTypes ?? fileTypes;
118
+ logger.info(
119
+ `Processing file types for repository ${repo.name}: [${repositoryFileTypesFilter.join(", ")}]`
120
+ );
121
+ const files = await githubService.getRepoFiles(
122
+ repo.name,
123
+ repositoryFileTypesFilter
124
+ );
125
+ if (files.length === 0) {
126
+ logger.warn(
127
+ `No files found for ingestion in the GitHub repository ${repo.name} (${repo.id}) with the specified file types filter: [${repositoryFileTypesFilter.join(
128
+ ", "
129
+ )}]`
130
+ );
131
+ continue;
132
+ }
133
+ logger.debug(`Files: ${JSON.stringify(files, null, 2)}`);
134
+ const { totalDocumentsIngested } = await ingestRepositoryByFileBatch({
135
+ repo,
136
+ files,
137
+ saveDocumentsBatch
138
+ });
139
+ if (totalDocumentsIngested === 0) {
140
+ logger.warn(
141
+ `No documents were ingested and sent for embedding from the GitHub repository ${repo.name} (${repo.id})`
142
+ );
143
+ continue;
144
+ }
145
+ logger.info(
146
+ `Repository ingestion completed: ${totalDocumentsIngested} total documents ingested and sent for embedding for GitHub repository: ${repo.name}`
147
+ );
148
+ }
149
+ };
150
+ const ingest = async ({ saveDocumentsBatch }) => {
151
+ await ingestGitHubBatch(saveDocumentsBatch);
152
+ };
153
+ return {
154
+ id: module$1.MODULE_ID,
155
+ ingest
156
+ };
157
+ };
158
+
159
+ exports.createGitHubIngestor = createGitHubIngestor;
160
+ //# sourceMappingURL=ingestor.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingestor.cjs.js","sources":["../../src/services/ingestor.ts"],"sourcesContent":["import {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { createGitHubService } from './github';\nimport {\n EmbeddingDocument,\n Ingestor,\n IngestorOptions,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { MODULE_ID } from '../constants/module';\nimport { Config } from '../../config';\nimport { getProgressStats } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { DEFAULT_FILE_BATCH_SIZE } from '../constants/default-file-batch-size';\n\nexport const createGitHubIngestor = async ({\n config,\n logger,\n}: {\n config: RootConfigService;\n logger: LoggerService;\n}): Promise<Ingestor> => {\n // Default to common file types if none are specified\n const defaultFileTypes = ['.md', '.json'];\n\n // Get configuration values\n const repositoriesFilter = config.getOptional<\n Config['aiAssistant']['ingestors']['github']['repositories']\n >('aiAssistant.ingestors.github.repositories');\n\n const fileTypes =\n config.getOptionalStringArray('aiAssistant.ingestors.github.fileTypes') ??\n defaultFileTypes;\n\n // Get batch size for processing files (default to 50 files per batch)\n const filesBatchSize =\n config.getOptionalNumber('aiAssistant.ingestors.github.filesBatchSize') ??\n DEFAULT_FILE_BATCH_SIZE;\n\n // Create GitHub service\n const githubService = await createGitHubService({ config, logger });\n\n /** Ingest GitHub repository files in batches\n * @param repo - The repository to ingest files from\n * @param files - The list of files to ingest from the repository\n * @param saveDocumentsBatch - Function to save a batch of embedding documents\n * @returns Total number of documents ingested and sent for embedding from the repository\n */\n const ingestRepositoryByFileBatch = async ({\n repo,\n files,\n saveDocumentsBatch,\n }: {\n repo: any;\n files: any[];\n saveDocumentsBatch: IngestorOptions['saveDocumentsBatch'];\n }) => {\n logger.info(\n `Processing ${files.length} files from repository \"${repo.name}\" in batches of ${filesBatchSize}`,\n );\n\n let totalDocumentsIngested = 0;\n\n // Process files in batches to manage memory and performance\n\n // Calculate total number of batches\n const totalBatches = Math.ceil(files.length / filesBatchSize);\n\n // Process each batch\n for (\n let batchStart = 0;\n batchStart < files.length;\n batchStart += filesBatchSize\n ) {\n const batchEnd = Math.min(batchStart + filesBatchSize, files.length);\n const filesBatch = files.slice(batchStart, batchEnd);\n const batchNumber = Math.floor(batchStart / filesBatchSize) + 1;\n\n logger.info(\n `Processing batch ${batchNumber}/${totalBatches} (${filesBatch.length} files) for repository \"${repo.name}\"`,\n );\n\n // Generate embedding documents for each file in the current batch\n const documents: EmbeddingDocument[] = [];\n\n for (let index = 0; index < filesBatch.length; index++) {\n const file = filesBatch[index];\n const globalIndex = batchStart + index;\n\n try {\n const content = await githubService.getRepoFileContent(\n repo.name,\n file.path!,\n );\n\n const completionStats = getProgressStats(\n globalIndex + 1,\n files.length,\n );\n\n logger.info(\n `Retrieved content for GitHub file: \"${file.path}\" in repository: \"${repo.name}\" [Progress: ${completionStats.completed}/${completionStats.total} (${completionStats.percentage}%) completed of repository]`,\n );\n\n // Generate proper GitHub URL for the file\n const githubUrl = `https://github.com/${githubService.owner}/${\n repo.name\n }/blob/${repo.default_branch || 'main'}/${file.path}`;\n\n // Create enhanced content with URL reference and metadata\n const enhancedContent = `Repository: ${repo.name}\n File Path: ${file.path}\n GitHub URL: ${githubUrl}\n ${\n repo.description\n ? `Repository Description: ${repo.description}`\n : ''\n }\n \n Content:\n ${content}`;\n\n const document: EmbeddingDocument = {\n metadata: {\n source: MODULE_ID,\n id: `${repo.id}:${file.path}`,\n url: githubUrl,\n owner: githubService.owner,\n repository: repo.name,\n filePath: file.path,\n fileName: file.path?.split('/').pop() || '',\n branch: repo.default_branch || 'main',\n repositoryDescription: repo.description || '',\n },\n content: enhancedContent,\n };\n\n documents.push(document);\n } catch (error) {\n logger.warn(\n `Failed to retrieve content for GitHub file: ${file.path}. Error: ${error}`,\n );\n // Continue with other files even if one fails\n continue;\n }\n }\n\n // Save the current batch of documents\n await saveDocumentsBatch(documents);\n\n totalDocumentsIngested += documents.length;\n\n logger.info(\n `Batch ${batchNumber}/${totalBatches} completed: ${documents.length} documents ingested for GitHub repository: ${repo.name}`,\n );\n }\n\n return { totalDocumentsIngested };\n };\n\n /** Ingest GitHub repositories in batches */\n const ingestGitHubBatch = async (\n saveDocumentsBatch: IngestorOptions['saveDocumentsBatch'],\n ) => {\n const repositoriesList = await githubService.getRepos();\n\n if (repositoriesList.length === 0) {\n logger.warn('No repositories found for the GitHub owner');\n return;\n }\n\n logger.info(\n `Filtering for repositories: ${repositoriesFilter\n ?.map(repo => repo.name)\n .join(', ')}`,\n );\n\n // Filter repositories if a filter is provided in the config\n const repositoriesToIngest = repositoriesFilter\n ? repositoriesList.filter(repo =>\n repositoriesFilter?.some(\n filteredRepo =>\n filteredRepo.name.toLowerCase() === repo.name.toLowerCase(),\n ),\n )\n : repositoriesList;\n\n if (repositoriesToIngest.length === 0) {\n logger.warn(\n 'No repositories found for ingestion after applying the filter',\n );\n return;\n }\n\n logger.info(\n `Ingesting ${repositoriesToIngest.length} repositories from GitHub`,\n );\n\n // Get files from each repository and create documents to be embedded\n for (const repo of repositoriesToIngest) {\n logger.info(\n `Beginning ingestion for repository: ${repo.name} (${repo.id})`,\n );\n\n // Determine the file types to use for this repository or use default\n const repositoryFileTypesFilter =\n repositoriesFilter?.find(\n r => r.name.toLowerCase() === repo.name.toLowerCase(),\n )?.fileTypes ?? fileTypes;\n\n logger.info(\n `Processing file types for repository ${\n repo.name\n }: [${repositoryFileTypesFilter.join(', ')}]`,\n );\n\n // Get the files to be ingested from the repository based on the file types filter\n const files = await githubService.getRepoFiles(\n repo.name,\n repositoryFileTypesFilter,\n );\n\n if (files.length === 0) {\n logger.warn(\n `No files found for ingestion in the GitHub repository ${\n repo.name\n } (${\n repo.id\n }) with the specified file types filter: [${repositoryFileTypesFilter.join(\n ', ',\n )}]`,\n );\n continue;\n }\n\n logger.debug(`Files: ${JSON.stringify(files, null, 2)}`);\n\n const { totalDocumentsIngested } = await ingestRepositoryByFileBatch({\n repo,\n files,\n saveDocumentsBatch,\n });\n\n if (totalDocumentsIngested === 0) {\n logger.warn(\n `No documents were ingested and sent for embedding from the GitHub repository ${repo.name} (${repo.id})`,\n );\n continue;\n }\n\n logger.info(\n `Repository ingestion completed: ${totalDocumentsIngested} total documents ingested and sent for embedding for GitHub repository: ${repo.name}`,\n );\n }\n };\n\n const ingest: Ingestor['ingest'] = async ({ saveDocumentsBatch }) => {\n await ingestGitHubBatch(saveDocumentsBatch);\n };\n\n return {\n id: MODULE_ID,\n ingest,\n };\n};\n"],"names":["DEFAULT_FILE_BATCH_SIZE","createGitHubService","getProgressStats","MODULE_ID"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-github",
3
+ "version": "0.0.0-snapshot-20251029145101",
4
+ "license": "Apache-2.0",
5
+ "description": "The ingestor-github backend module for the ai-assistant 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
+ "backstage": {
14
+ "role": "backend-plugin-module",
15
+ "pluginId": "ai-assistant",
16
+ "pluginPackage": "@sweetoburrito/backstage-plugin-ai-assistant-backend",
17
+ "features": {
18
+ ".": "@backstage/BackendFeature"
19
+ }
20
+ },
21
+ "scripts": {
22
+ "start": "backstage-cli package start",
23
+ "build": "backstage-cli package build",
24
+ "lint": "backstage-cli package lint",
25
+ "test": "backstage-cli package test",
26
+ "clean": "backstage-cli package clean",
27
+ "prepack": "backstage-cli package prepack",
28
+ "postpack": "backstage-cli package postpack"
29
+ },
30
+ "dependencies": {
31
+ "@backstage/backend-plugin-api": "^1.4.1",
32
+ "@sweetoburrito/backstage-plugin-ai-assistant-common": "0.0.0-snapshot-20251029145101",
33
+ "@sweetoburrito/backstage-plugin-ai-assistant-node": "^0.5.0",
34
+ "octokit": "^5.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@backstage/backend-test-utils": "^1.7.0",
38
+ "@backstage/cli": "^0.33.1"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "config.d.ts"
43
+ ],
44
+ "configSchema": "config.d.ts",
45
+ "typesVersions": {
46
+ "*": {
47
+ "package.json": [
48
+ "package.json"
49
+ ]
50
+ }
51
+ }
52
+ }