@nospt/plugin-dev-ai-hub-backend 0.2.4 → 0.3.1

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.
Files changed (46) hide show
  1. package/config.d.ts +22 -0
  2. package/dist/database/TelemetryStore.cjs.js +94 -0
  3. package/dist/database/TelemetryStore.cjs.js.map +1 -0
  4. package/dist/plugin.cjs.js +24 -45
  5. package/dist/plugin.cjs.js.map +1 -1
  6. package/dist/router.cjs.js +147 -202
  7. package/dist/router.cjs.js.map +1 -1
  8. package/dist/service/bodyResolver.cjs.js +56 -0
  9. package/dist/service/bodyResolver.cjs.js.map +1 -0
  10. package/dist/service/telemetryHash.cjs.js +14 -0
  11. package/dist/service/telemetryHash.cjs.js.map +1 -0
  12. package/dist/service/toResourceSummary.cjs.js +31 -0
  13. package/dist/service/toResourceSummary.cjs.js.map +1 -0
  14. package/migrations/001_init.js +13 -42
  15. package/migrations/002_install_count.js +7 -12
  16. package/migrations/003_install_paths.js +7 -14
  17. package/migrations/004_label.js +7 -12
  18. package/migrations/005_remove_unused_fields.js +6 -16
  19. package/migrations/006_resources_content.js +10 -13
  20. package/migrations/007_telemetry.js +28 -0
  21. package/migrations/008_drop_legacy_tables.js +24 -0
  22. package/migrations/009_telemetry_settings.js +25 -0
  23. package/package.json +18 -8
  24. package/.eslintrc.js +0 -1
  25. package/dev/index.ts +0 -8
  26. package/dist/database/AiAssetStore.cjs.js +0 -225
  27. package/dist/database/AiAssetStore.cjs.js.map +0 -1
  28. package/dist/service/AiAssetSyncService.cjs.js +0 -235
  29. package/dist/service/AiAssetSyncService.cjs.js.map +0 -1
  30. package/dist/service/AssetParser.cjs.js +0 -74
  31. package/dist/service/AssetParser.cjs.js.map +0 -1
  32. package/dist/service/McpServerService.cjs.js +0 -366
  33. package/dist/service/McpServerService.cjs.js.map +0 -1
  34. package/package.json-prepack +0 -71
  35. package/src/database/AiAssetStore.test.ts +0 -294
  36. package/src/database/AiAssetStore.ts +0 -256
  37. package/src/index.ts +0 -2
  38. package/src/plugin.ts +0 -84
  39. package/src/router.smoke.test.ts +0 -262
  40. package/src/router.ts +0 -338
  41. package/src/service/AiAssetSyncService.ts +0 -298
  42. package/src/service/AssetParser.test.ts +0 -329
  43. package/src/service/AssetParser.ts +0 -103
  44. package/src/service/McpServerService.ts +0 -464
  45. package/src/types.ts +0 -31
  46. package/tsconfig.json +0 -8
@@ -1,294 +0,0 @@
1
- import Knex from 'knex';
2
- import { AiAssetStore } from './AiAssetStore';
3
- import type { AiAssetInput } from '../types';
4
-
5
- let knex: Knex.Knex;
6
- let store: AiAssetStore;
7
-
8
- beforeAll(async () => {
9
- knex = Knex({
10
- client: 'better-sqlite3',
11
- connection: { filename: ':memory:' },
12
- useNullAsDefault: true,
13
- });
14
- store = await AiAssetStore.create({
15
- database: { getClient: async () => knex } as any,
16
- });
17
- });
18
-
19
- afterAll(async () => {
20
- await knex.destroy();
21
- });
22
-
23
- beforeEach(async () => {
24
- await knex('ai_assets').delete();
25
- await knex('ai_asset_sync_status').delete();
26
- });
27
-
28
- function makeInput(overrides?: Partial<AiAssetInput>): AiAssetInput {
29
- return {
30
- id: 'test-id',
31
- providerId: 'provider-1',
32
- name: 'Test Asset',
33
- description: 'A test asset',
34
- type: 'instruction',
35
- tools: ['claude-code'],
36
- tags: ['testing'],
37
- author: 'Test Author',
38
- version: '1.0.0',
39
- content: '# Test content',
40
- yamlRaw: 'name: Test Asset\n',
41
- yamlPath: 'instructions/test.yaml',
42
- mdPath: 'instructions/test.md',
43
- repoUrl: 'https://github.com/org/repo',
44
- branch: 'main',
45
- ...overrides,
46
- };
47
- }
48
-
49
- describe('upsertAsset + getAsset', () => {
50
- it('inserts a new asset and retrieves it by id', async () => {
51
- await store.upsertAsset(makeInput());
52
- const asset = await store.getAsset('test-id');
53
-
54
- expect(asset).not.toBeNull();
55
- expect(asset!.id).toBe('test-id');
56
- expect(asset!.name).toBe('Test Asset');
57
- expect(asset!.description).toBe('A test asset');
58
- expect(asset!.type).toBe('instruction');
59
- expect(asset!.tools).toEqual(['claude-code']);
60
- expect(asset!.tags).toEqual(['testing']);
61
- expect(asset!.content).toBe('# Test content');
62
- expect(asset!.providerId).toBe('provider-1');
63
- expect(asset!.branch).toBe('main');
64
- });
65
-
66
- it('updates an existing asset on conflict (upsert)', async () => {
67
- await store.upsertAsset(makeInput({ name: 'Original Name' }));
68
- await store.upsertAsset(makeInput({ name: 'Updated Name' }));
69
-
70
- const asset = await store.getAsset('test-id');
71
- expect(asset!.name).toBe('Updated Name');
72
- });
73
-
74
- it('returns null for a non-existent id', async () => {
75
- const asset = await store.getAsset('does-not-exist');
76
- expect(asset).toBeNull();
77
- });
78
-
79
- it('persists optional fields correctly', async () => {
80
- await store.upsertAsset(
81
- makeInput({
82
- installPath: 'custom/path.md',
83
- installPaths: { 'claude-code': 'custom/claude.md' },
84
- metadata: { resources: ['scripts/setup.sh'] },
85
- commitSha: 'abc123',
86
- }),
87
- );
88
-
89
- const asset = await store.getAsset('test-id');
90
- expect(asset!.installPath).toBe('custom/path.md');
91
- expect(asset!.installPaths).toEqual({ 'claude-code': 'custom/claude.md' });
92
- expect(asset!.metadata?.resources).toBeDefined();
93
- expect(asset!.commitSha).toBe('abc123');
94
- });
95
- });
96
-
97
- describe('listAssets', () => {
98
- it('returns all assets when no filter is applied', async () => {
99
- await store.upsertAsset(makeInput({ id: 'a1', name: 'Asset 1', type: 'instruction' }));
100
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Asset 2', type: 'agent' }));
101
-
102
- const { items, totalCount } = await store.listAssets({ page: 1, pageSize: 20 });
103
- expect(totalCount).toBe(2);
104
- expect(items).toHaveLength(2);
105
- });
106
-
107
- it('filters by type', async () => {
108
- await store.upsertAsset(makeInput({ id: 'a1', type: 'instruction' }));
109
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Agent', type: 'agent' }));
110
-
111
- const { items, totalCount } = await store.listAssets({ type: 'agent', page: 1, pageSize: 20 });
112
- expect(totalCount).toBe(1);
113
- expect(items[0].type).toBe('agent');
114
- });
115
-
116
- it('filters by tool', async () => {
117
- await store.upsertAsset(makeInput({ id: 'a1', tools: ['claude-code'] }));
118
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Copilot Asset', tools: ['github-copilot'] }));
119
-
120
- const { items } = await store.listAssets({ tool: 'claude-code', page: 1, pageSize: 20 });
121
- expect(items).toHaveLength(1);
122
- expect(items[0].id).toBe('a1');
123
- });
124
-
125
- it('includes assets with tool "all" in any tool filter', async () => {
126
- await store.upsertAsset(makeInput({ id: 'a1', tools: ['all'] }));
127
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Copilot Asset', tools: ['github-copilot'] }));
128
-
129
- const { items } = await store.listAssets({ tool: 'claude-code', page: 1, pageSize: 20 });
130
- expect(items).toHaveLength(1);
131
- expect(items[0].id).toBe('a1');
132
- });
133
-
134
- it('filters by providerId', async () => {
135
- await store.upsertAsset(makeInput({ id: 'a1', providerId: 'provider-1' }));
136
- await store.upsertAsset(makeInput({ id: 'a2', name: 'P2 Asset', providerId: 'provider-2' }));
137
-
138
- const { items } = await store.listAssets({ providerId: 'provider-2', page: 1, pageSize: 20 });
139
- expect(items).toHaveLength(1);
140
- expect(items[0].id).toBe('a2');
141
- });
142
-
143
- it('filters by search term (matches name)', async () => {
144
- await store.upsertAsset(makeInput({ id: 'a1', name: 'Python Linting' }));
145
- await store.upsertAsset(makeInput({ id: 'a2', name: 'TypeScript Config' }));
146
-
147
- const { items } = await store.listAssets({ search: 'Python', page: 1, pageSize: 20 });
148
- expect(items).toHaveLength(1);
149
- expect(items[0].id).toBe('a1');
150
- });
151
-
152
- it('filters by search term (matches description)', async () => {
153
- await store.upsertAsset(makeInput({ id: 'a1', description: 'Instructions for FastAPI projects' }));
154
- await store.upsertAsset(makeInput({ id: 'a2', name: 'React Asset', description: 'Instructions for React projects' }));
155
-
156
- const { items } = await store.listAssets({ search: 'FastAPI', page: 1, pageSize: 20 });
157
- expect(items).toHaveLength(1);
158
- expect(items[0].id).toBe('a1');
159
- });
160
-
161
- it('filters by tags', async () => {
162
- await store.upsertAsset(makeInput({ id: 'a1', tags: ['python', 'linting'] }));
163
- await store.upsertAsset(makeInput({ id: 'a2', name: 'TS Asset', tags: ['typescript'] }));
164
-
165
- const { items } = await store.listAssets({ tags: ['python'], page: 1, pageSize: 20 });
166
- expect(items).toHaveLength(1);
167
- expect(items[0].id).toBe('a1');
168
- });
169
-
170
- it('respects pagination', async () => {
171
- for (let i = 0; i < 5; i++) {
172
- await store.upsertAsset(makeInput({ id: `a${i}`, name: `Asset ${i}` }));
173
- }
174
-
175
- const { items, totalCount } = await store.listAssets({ page: 2, pageSize: 2 });
176
- expect(totalCount).toBe(5);
177
- expect(items).toHaveLength(2);
178
- });
179
-
180
- it('returns items sorted by name ascending', async () => {
181
- await store.upsertAsset(makeInput({ id: 'a1', name: 'Zebra' }));
182
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Apple' }));
183
-
184
- const { items } = await store.listAssets({ page: 1, pageSize: 20 });
185
- expect(items[0].name).toBe('Apple');
186
- expect(items[1].name).toBe('Zebra');
187
- });
188
- });
189
-
190
- describe('incrementInstallCount', () => {
191
- it('increments the install count by 1 on each call', async () => {
192
- await store.upsertAsset(makeInput());
193
-
194
- await store.incrementInstallCount('test-id');
195
- await store.incrementInstallCount('test-id');
196
-
197
- const asset = await store.getAsset('test-id');
198
- expect(asset!.installCount).toBe(2);
199
- });
200
- });
201
-
202
- describe('deleteAssetsNotIn', () => {
203
- it('deletes assets from a provider that are not in the keep list', async () => {
204
- await store.upsertAsset(makeInput({ id: 'a1', providerId: 'p1' }));
205
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Asset 2', providerId: 'p1' }));
206
- await store.upsertAsset(makeInput({ id: 'a3', name: 'Asset 3', providerId: 'p2' }));
207
-
208
- await store.deleteAssetsNotIn('p1', ['a1']);
209
-
210
- expect(await store.getAsset('a1')).not.toBeNull();
211
- expect(await store.getAsset('a2')).toBeNull();
212
- expect(await store.getAsset('a3')).not.toBeNull(); // different provider, untouched
213
- });
214
-
215
- it('deletes all assets for a provider when keep list is empty', async () => {
216
- await store.upsertAsset(makeInput({ id: 'a1', providerId: 'p1' }));
217
-
218
- await store.deleteAssetsNotIn('p1', []);
219
-
220
- expect(await store.getAsset('a1')).toBeNull();
221
- });
222
- });
223
-
224
- describe('sync status', () => {
225
- it('upserts and retrieves sync status', async () => {
226
- await store.upsertSyncStatus({
227
- providerId: 'p1',
228
- status: 'syncing',
229
- assetCount: 10,
230
- lastSync: '2026-01-01T00:00:00Z',
231
- lastCommit: 'abc123',
232
- });
233
-
234
- const status = await store.getSyncStatus('p1');
235
- expect(status).not.toBeNull();
236
- expect(status!.providerId).toBe('p1');
237
- expect(status!.status).toBe('syncing');
238
- expect(status!.assetCount).toBe(10);
239
- expect(status!.lastSync).toBe('2026-01-01T00:00:00Z');
240
- expect(status!.lastCommit).toBe('abc123');
241
- });
242
-
243
- it('updates existing sync status on conflict', async () => {
244
- await store.upsertSyncStatus({ providerId: 'p1', status: 'idle', assetCount: 5 });
245
- await store.upsertSyncStatus({ providerId: 'p1', status: 'error', assetCount: 0, error: 'boom' });
246
-
247
- const status = await store.getSyncStatus('p1');
248
- expect(status!.status).toBe('error');
249
- expect(status!.error).toBe('boom');
250
- expect(status!.assetCount).toBe(0);
251
- });
252
-
253
- it('returns null for an unknown provider', async () => {
254
- const status = await store.getSyncStatus('unknown');
255
- expect(status).toBeNull();
256
- });
257
-
258
- it('getAllSyncStatuses returns statuses for all providers', async () => {
259
- await store.upsertSyncStatus({ providerId: 'p1', status: 'idle', assetCount: 3 });
260
- await store.upsertSyncStatus({ providerId: 'p2', status: 'syncing', assetCount: 7 });
261
-
262
- const statuses = await store.getAllSyncStatuses();
263
- expect(statuses).toHaveLength(2);
264
- const ids = statuses.map(s => s.providerId).sort();
265
- expect(ids).toEqual(['p1', 'p2']);
266
- });
267
- });
268
-
269
- describe('getStats', () => {
270
- it('returns correct totals by type, tool, and provider', async () => {
271
- await store.upsertAsset(makeInput({ id: 'a1', type: 'instruction', tools: ['claude-code'], providerId: 'p1' }));
272
- await store.upsertAsset(makeInput({ id: 'a2', name: 'Agent', type: 'agent', tools: ['github-copilot'], providerId: 'p1' }));
273
- await store.upsertAsset(makeInput({ id: 'a3', name: 'P2 Instruction', type: 'instruction', tools: ['claude-code'], providerId: 'p2' }));
274
-
275
- const stats = await store.getStats();
276
-
277
- expect(stats.totalAssets).toBe(3);
278
- expect(stats.byType.instruction).toBe(2);
279
- expect(stats.byType.agent).toBe(1);
280
- expect(stats.byType.skill).toBe(0);
281
- expect(stats.byTool['claude-code']).toBe(2);
282
- expect(stats.byTool['github-copilot']).toBe(1);
283
- expect(stats.byProvider.p1).toBe(2);
284
- expect(stats.byProvider.p2).toBe(1);
285
- });
286
-
287
- it('returns zero totals for an empty store', async () => {
288
- const stats = await store.getStats();
289
-
290
- expect(stats.totalAssets).toBe(0);
291
- expect(stats.byType.instruction).toBe(0);
292
- expect(stats.lastSync).toBeUndefined();
293
- });
294
- });
@@ -1,256 +0,0 @@
1
- import type { Knex } from 'knex';
2
- import { resolvePackagePath, type DatabaseService } from '@backstage/backend-plugin-api';
3
- import type { AiAsset, AiAssetSummary, AiHubStats, AssetListFilter, AssetType } from '@nospt/plugin-dev-ai-hub-common';
4
- import type { AiAssetInput, SyncStatus } from '../types';
5
-
6
- export class AiAssetStore {
7
- private constructor(private readonly db: Knex) {}
8
-
9
- static async create(options: { database: DatabaseService }): Promise<AiAssetStore> {
10
- const db = await options.database.getClient();
11
- await db.migrate.latest({
12
- directory: resolvePackagePath('@nospt/plugin-dev-ai-hub-backend', 'migrations'),
13
- loadExtensions: ['.js'],
14
- });
15
- return new AiAssetStore(db);
16
- }
17
-
18
- async upsertAsset(input: AiAssetInput): Promise<void> {
19
- const now = new Date().toISOString();
20
- const row = {
21
- id: input.id,
22
- provider_id: input.providerId,
23
- name: input.name,
24
- label: input.label ?? null,
25
- description: input.description,
26
- type: input.type,
27
- tools: JSON.stringify(input.tools),
28
- tags: JSON.stringify(input.tags),
29
- author: input.author,
30
- icon: input.icon ?? null,
31
- version: input.version,
32
- install_path: input.installPath ?? null,
33
- install_paths: input.installPaths ? JSON.stringify(input.installPaths) : null,
34
- content: input.content,
35
- yaml_raw: input.yamlRaw,
36
- metadata: input.metadata ? JSON.stringify(input.metadata) : null,
37
- resources_content: input.resourcesContent ? JSON.stringify(input.resourcesContent) : null,
38
- yaml_path: input.yamlPath,
39
- md_path: input.mdPath,
40
- repo_url: input.repoUrl,
41
- branch: input.branch,
42
- commit_sha: input.commitSha ?? null,
43
- synced_at: now,
44
- updated_at: now,
45
- };
46
-
47
- await this.db('ai_assets')
48
- .insert({ ...row, created_at: now })
49
- .onConflict('id')
50
- .merge(Object.keys(row) as (keyof typeof row)[]);
51
- }
52
-
53
- async listAssets(
54
- filter: AssetListFilter,
55
- ): Promise<{ items: AiAssetSummary[]; totalCount: number }> {
56
- let query = this.db<Record<string, unknown>>('ai_assets');
57
-
58
- if (filter.type) {
59
- query = query.where('type', filter.type);
60
- }
61
- if (filter.tool) {
62
- query = query.where(function toolFilter() {
63
- this.where('tools', 'like', `%"${filter.tool}"%`)
64
- .orWhere('tools', 'like', `%"all"%`);
65
- });
66
- }
67
- if (filter.providerId) {
68
- query = query.where('provider_id', filter.providerId);
69
- }
70
- if (filter.search) {
71
- // Split into words so "Python linting" matches items containing both words
72
- const words = filter.search.trim().split(/\s+/).filter(Boolean);
73
- for (const word of words) {
74
- const term = `%${word}%`;
75
- query = query.where(function searchFilter() {
76
- this.where('name', 'like', term)
77
- .orWhere('label', 'like', term)
78
- .orWhere('description', 'like', term)
79
- .orWhere('tags', 'like', term)
80
- .orWhere('content', 'like', term);
81
- });
82
- }
83
- }
84
- if (filter.tags && filter.tags.length > 0) {
85
- for (const tag of filter.tags) {
86
- query = query.where('tags', 'like', `%"${tag}"%`);
87
- }
88
- }
89
-
90
- const countResult = await query.clone().count('id as count').first();
91
- const totalCount = Number(countResult?.count ?? 0);
92
-
93
- const page = Math.max(1, filter.page ?? 1);
94
- const pageSize = Math.min(100, Math.max(1, filter.pageSize ?? 20));
95
-
96
- const rows = await query
97
- .select('id', 'provider_id', 'name', 'label', 'description', 'type', 'tools', 'tags',
98
- 'author', 'icon', 'version', 'install_count', 'synced_at', 'created_at', 'updated_at')
99
- .orderBy('name', 'asc')
100
- .limit(pageSize)
101
- .offset((page - 1) * pageSize);
102
-
103
- return { items: rows.map(r => this.rowToAssetSummary(r)), totalCount };
104
- }
105
-
106
- async getAsset(id: string): Promise<AiAsset | null> {
107
- const row = await this.db('ai_assets').where('id', id).first();
108
- return row ? this.rowToAsset(row) : null;
109
- }
110
-
111
- async incrementInstallCount(id: string): Promise<void> {
112
- await this.db('ai_assets').where('id', id).increment('install_count', 1);
113
- }
114
-
115
- async deleteAssetsNotIn(providerId: string, ids: string[]): Promise<void> {
116
- let query = this.db('ai_assets').where('provider_id', providerId);
117
- if (ids.length > 0) {
118
- query = query.whereNotIn('id', ids);
119
- }
120
- await query.delete();
121
- }
122
-
123
- async upsertSyncStatus(status: SyncStatus): Promise<void> {
124
- const row = {
125
- provider_id: status.providerId,
126
- last_sync: status.lastSync ?? null,
127
- last_commit: status.lastCommit ?? null,
128
- status: status.status,
129
- error: status.error ?? null,
130
- asset_count: status.assetCount,
131
- };
132
- await this.db('ai_asset_sync_status')
133
- .insert(row)
134
- .onConflict('provider_id')
135
- .merge(Object.keys(row) as (keyof typeof row)[]);
136
- }
137
-
138
- async getSyncStatus(providerId: string): Promise<SyncStatus | null> {
139
- const row = await this.db('ai_asset_sync_status')
140
- .where('provider_id', providerId)
141
- .first();
142
- if (!row) return null;
143
- return {
144
- providerId: row.provider_id,
145
- lastSync: row.last_sync ?? undefined,
146
- lastCommit: row.last_commit ?? undefined,
147
- status: row.status,
148
- error: row.error ?? undefined,
149
- assetCount: row.asset_count ?? 0,
150
- };
151
- }
152
-
153
- async getAllSyncStatuses(): Promise<SyncStatus[]> {
154
- const rows = await this.db('ai_asset_sync_status').select('*');
155
- return rows.map(row => ({
156
- providerId: row.provider_id,
157
- lastSync: row.last_sync ?? undefined,
158
- lastCommit: row.last_commit ?? undefined,
159
- status: row.status,
160
- error: row.error ?? undefined,
161
- assetCount: row.asset_count ?? 0,
162
- }));
163
- }
164
-
165
- async getStats(): Promise<AiHubStats> {
166
- const rows = await this.db('ai_assets').select('type', 'tools', 'provider_id');
167
-
168
- const byType: Record<AssetType, number> = {
169
- instruction: 0,
170
- agent: 0,
171
- skill: 0,
172
- workflow: 0,
173
- };
174
- const byTool: Record<string, number> = {};
175
- const byProvider: Record<string, number> = {};
176
-
177
- for (const row of rows) {
178
- byType[row.type as AssetType] = (byType[row.type as AssetType] ?? 0) + 1;
179
- byProvider[row.provider_id] = (byProvider[row.provider_id] ?? 0) + 1;
180
-
181
- const tools: string[] = JSON.parse(row.tools || '[]');
182
- for (const tool of tools) {
183
- byTool[tool] = (byTool[tool] ?? 0) + 1;
184
- }
185
- }
186
-
187
- const lastSyncRow = await this.db('ai_asset_sync_status')
188
- .select(this.db.raw('max(last_sync) as last_sync'))
189
- .first();
190
-
191
- return {
192
- totalAssets: rows.length,
193
- byType,
194
- byTool,
195
- byProvider,
196
- lastSync: (lastSyncRow?.last_sync as string | null) ?? undefined,
197
- };
198
- }
199
-
200
- private rowToAssetSummary(row: Record<string, unknown>): AiAssetSummary {
201
- return {
202
- id: row.id as string,
203
- providerId: row.provider_id as string,
204
- name: row.name as string,
205
- label: (row.label as string | null) ?? undefined,
206
- description: row.description as string,
207
- type: row.type as AssetType,
208
- tools: JSON.parse((row.tools as string) || '[]'),
209
- tags: JSON.parse((row.tags as string) || '[]'),
210
- author: row.author as string,
211
- icon: (row.icon as string | null) ?? undefined,
212
- version: row.version as string,
213
- installCount: (row.install_count as number) ?? 0,
214
- syncedAt: row.synced_at as string,
215
- createdAt: row.created_at as string,
216
- updatedAt: row.updated_at as string,
217
- };
218
- }
219
-
220
- private rowToAsset(row: Record<string, unknown>): AiAsset {
221
- return {
222
- id: row.id as string,
223
- providerId: row.provider_id as string,
224
- name: row.name as string,
225
- label: (row.label as string | null) ?? undefined,
226
- description: row.description as string,
227
- type: row.type as AssetType,
228
- tools: JSON.parse((row.tools as string) || '[]'),
229
- tags: JSON.parse((row.tags as string) || '[]'),
230
- author: row.author as string,
231
- icon: (row.icon as string | null) ?? undefined,
232
- version: row.version as string,
233
- installPath: (row.install_path as string | null) ?? undefined,
234
- installPaths: row.install_paths
235
- ? JSON.parse(row.install_paths as string)
236
- : undefined,
237
- content: row.content as string,
238
- yamlRaw: row.yaml_raw as string,
239
- metadata: row.metadata
240
- ? JSON.parse(row.metadata as string)
241
- : undefined,
242
- resourcesContent: row.resources_content
243
- ? JSON.parse(row.resources_content as string)
244
- : undefined,
245
- yamlPath: row.yaml_path as string,
246
- mdPath: row.md_path as string,
247
- repoUrl: row.repo_url as string,
248
- branch: row.branch as string,
249
- commitSha: (row.commit_sha as string | null) ?? undefined,
250
- installCount: (row.install_count as number) ?? 0,
251
- syncedAt: row.synced_at as string,
252
- createdAt: row.created_at as string,
253
- updatedAt: row.updated_at as string,
254
- };
255
- }
256
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { devAiHubPlugin } from './plugin';
2
- export { devAiHubPlugin as default } from './plugin';
package/src/plugin.ts DELETED
@@ -1,84 +0,0 @@
1
- import {
2
- coreServices,
3
- createBackendPlugin,
4
- } from '@backstage/backend-plugin-api';
5
- import {
6
- devAiHubProviderExtensionPoint,
7
- type AiAssetProvider,
8
- } from '@nospt/plugin-dev-ai-hub-node';
9
- import { AiAssetStore } from './database/AiAssetStore';
10
- import { AiAssetSyncService } from './service/AiAssetSyncService';
11
- import { createRouter } from './router';
12
- import type { ProviderConfig } from './types';
13
-
14
- export const devAiHubPlugin = createBackendPlugin({
15
- pluginId: 'dev-ai-hub',
16
- register(env) {
17
- const externalProviders: AiAssetProvider[] = [];
18
-
19
- env.registerExtensionPoint(devAiHubProviderExtensionPoint, {
20
- addProvider(provider) {
21
- externalProviders.push(provider);
22
- },
23
- });
24
-
25
- env.registerInit({
26
- deps: {
27
- config: coreServices.rootConfig,
28
- logger: coreServices.logger,
29
- database: coreServices.database,
30
- scheduler: coreServices.scheduler,
31
- httpRouter: coreServices.httpRouter,
32
- urlReader: coreServices.urlReader,
33
- },
34
- async init({ config, logger, database, scheduler, httpRouter, urlReader }) {
35
- const store = await AiAssetStore.create({ database });
36
-
37
- const providers: ProviderConfig[] = (
38
- config.getOptionalConfigArray('devAiHub.providers') ?? []
39
- ).map(p => ({
40
- id: p.getString('id'),
41
- type: p.getString('type') as ProviderConfig['type'],
42
- target: p.getString('target'),
43
- branch: p.getOptionalString('branch') ?? 'main',
44
- schedule: {
45
- frequency: {
46
- minutes: p.getOptionalNumber('schedule.frequency.minutes'),
47
- hours: p.getOptionalNumber('schedule.frequency.hours'),
48
- },
49
- timeout: {
50
- minutes: p.getOptionalNumber('schedule.timeout.minutes'),
51
- hours: p.getOptionalNumber('schedule.timeout.hours'),
52
- },
53
- },
54
- filters: p.has('filters')
55
- ? {
56
- tools: p.getOptionalStringArray('filters.tools'),
57
- types: p.getOptionalStringArray('filters.types'),
58
- }
59
- : undefined,
60
- }));
61
-
62
- const syncService = new AiAssetSyncService({
63
- logger,
64
- store,
65
- scheduler,
66
- urlReader,
67
- providers,
68
- externalProviders,
69
- });
70
-
71
- await syncService.start();
72
-
73
- const router = createRouter({ logger, store, syncService, providers });
74
-
75
- httpRouter.use(router);
76
-
77
- httpRouter.addAuthPolicy({ path: '/assets', allow: 'unauthenticated' });
78
- httpRouter.addAuthPolicy({ path: '/providers', allow: 'unauthenticated' });
79
- httpRouter.addAuthPolicy({ path: '/stats', allow: 'unauthenticated' });
80
- httpRouter.addAuthPolicy({ path: '/mcp', allow: 'unauthenticated' });
81
- },
82
- });
83
- },
84
- });