@hamak/smart-data-dico 1.0.4 → 1.1.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.
- package/backend/dist/server.mjs +82213 -0
- package/bin/cli.js +28 -17
- package/package.json +28 -27
- package/backend/package.json +0 -51
- package/backend/src/__tests__/integration/api.test.ts +0 -149
- package/backend/src/__tests__/setup.ts +0 -24
- package/backend/src/__tests__/utils/testUtils.ts +0 -76
- package/backend/src/adapters/EntityFileAdapter.ts +0 -154
- package/backend/src/adapters/YamlFileInfoEnricher.ts +0 -52
- package/backend/src/controllers/authController.ts +0 -131
- package/backend/src/controllers/diagramController.ts +0 -143
- package/backend/src/controllers/dictionaryController.ts +0 -306
- package/backend/src/controllers/importExportController.ts +0 -64
- package/backend/src/controllers/perspectiveController.ts +0 -90
- package/backend/src/controllers/serviceController.ts +0 -418
- package/backend/src/controllers/stereotypeController.ts +0 -59
- package/backend/src/controllers/versionController.ts +0 -226
- package/backend/src/kernel/config.ts +0 -43
- package/backend/src/middleware/auth.ts +0 -128
- package/backend/src/middleware/jwtAuth.ts +0 -100
- package/backend/src/models/Dictionary.ts +0 -38
- package/backend/src/models/EntitySchema.ts +0 -393
- package/backend/src/models/__tests__/Dictionary.test.ts +0 -92
- package/backend/src/models/__tests__/EntitySchema.test.ts +0 -119
- package/backend/src/routes/index.ts +0 -120
- package/backend/src/scripts/migrate-to-uuid.ts +0 -24
- package/backend/src/server.ts +0 -158
- package/backend/src/services/__mocks__/entityService.ts +0 -38
- package/backend/src/services/__mocks__/serviceService.ts +0 -88
- package/backend/src/services/__mocks__/versionService.ts +0 -38
- package/backend/src/services/__tests__/dictionaryService.test.ts +0 -74
- package/backend/src/services/diagramService.ts +0 -165
- package/backend/src/services/dictionaryService.ts +0 -582
- package/backend/src/services/entityService.ts +0 -102
- package/backend/src/services/exportService.ts +0 -172
- package/backend/src/services/importService.ts +0 -208
- package/backend/src/services/perspectiveService.ts +0 -276
- package/backend/src/services/qualityService.ts +0 -121
- package/backend/src/services/serviceService.ts +0 -763
- package/backend/src/services/stereotypeService.ts +0 -98
- package/backend/src/services/versionService.ts +0 -135
- package/backend/src/setupTests.ts +0 -12
- package/backend/src/utils/__mocks__/fileOperations.ts +0 -116
- package/backend/src/utils/fileOperations.ts +0 -602
- package/backend/src/utils/logger.ts +0 -38
- package/backend/src/utils/migration.ts +0 -254
- package/backend/src/utils/swagger.ts +0 -358
- package/backend/src/utils/uuid.ts +0 -41
- package/backend/tsconfig.json +0 -20
package/bin/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { dirname, join, resolve } from 'path';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { existsSync, mkdirSync, cpSync } from 'fs';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
|
|
@@ -25,7 +25,7 @@ if (flags.help) {
|
|
|
25
25
|
|
|
26
26
|
Usage:
|
|
27
27
|
smart-data-dico [options]
|
|
28
|
-
npx smart-data-dico [options]
|
|
28
|
+
npx @hamak/smart-data-dico [options]
|
|
29
29
|
|
|
30
30
|
Options:
|
|
31
31
|
--port <number> Server port (default: 3001)
|
|
@@ -59,14 +59,33 @@ if (!existsSync(dataDir)) {
|
|
|
59
59
|
mkdirSync(join(dataDir, 'perspectives'), { recursive: true });
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
// Determine how to run the server:
|
|
63
|
+
// 1. Bundled (production/npm): single .mjs file, run with node — no deps needed
|
|
64
|
+
// 2. Source (dev): TypeScript source, run with tsx
|
|
65
|
+
const bundledServer = join(PKG_ROOT, 'backend', 'dist', 'server.mjs');
|
|
66
|
+
const sourceServer = join(PKG_ROOT, 'backend', 'src', 'server.ts');
|
|
67
|
+
|
|
68
|
+
let bin, binArgs;
|
|
69
|
+
|
|
70
|
+
if (existsSync(bundledServer)) {
|
|
71
|
+
// Production: bundled server — all deps inlined, just node
|
|
72
|
+
bin = process.execPath; // 'node'
|
|
73
|
+
binArgs = [bundledServer];
|
|
74
|
+
} else if (existsSync(sourceServer)) {
|
|
75
|
+
// Development: run TypeScript source via tsx
|
|
76
|
+
const tsxPaths = [
|
|
77
|
+
join(PKG_ROOT, 'node_modules', '.bin', 'tsx'),
|
|
78
|
+
join(PKG_ROOT, 'backend', 'node_modules', '.bin', 'tsx'),
|
|
79
|
+
];
|
|
80
|
+
bin = tsxPaths.find(p => existsSync(p)) || 'npx';
|
|
81
|
+
binArgs = bin.endsWith('npx') ? ['tsx', sourceServer] : [sourceServer];
|
|
82
|
+
} else {
|
|
83
|
+
console.error('Error: No server found (neither bundled nor source).');
|
|
67
84
|
process.exit(1);
|
|
68
85
|
}
|
|
69
86
|
|
|
87
|
+
const frontendDist = join(PKG_ROOT, 'frontend', 'dist');
|
|
88
|
+
|
|
70
89
|
console.log(`
|
|
71
90
|
Smart Data Dictionary
|
|
72
91
|
|
|
@@ -76,16 +95,8 @@ console.log(`
|
|
|
76
95
|
Frontend: ${existsSync(frontendDist) ? 'bundled' : 'dev (use frontend dev server on :3000)'}
|
|
77
96
|
`);
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
join(PKG_ROOT, 'node_modules', '.bin', 'tsx'),
|
|
82
|
-
join(PKG_ROOT, 'backend', 'node_modules', '.bin', 'tsx'),
|
|
83
|
-
];
|
|
84
|
-
let tsxBin = tsxPaths.find(p => existsSync(p)) || 'npx';
|
|
85
|
-
const tsxArgs = tsxBin === 'npx' ? ['tsx', serverTs] : [serverTs];
|
|
86
|
-
|
|
87
|
-
const child = spawn(tsxBin, tsxArgs, {
|
|
88
|
-
cwd: join(PKG_ROOT, 'backend'),
|
|
98
|
+
const child = spawn(bin, binArgs, {
|
|
99
|
+
cwd: PKG_ROOT,
|
|
89
100
|
env: {
|
|
90
101
|
...process.env,
|
|
91
102
|
PORT: port,
|
package/package.json
CHANGED
|
@@ -1,46 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamak/smart-data-dico",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Collaborative data dictionary management system — model, document, and govern your data landscape",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"smart-data-dico": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "
|
|
10
|
+
"build": "npm run build:frontend && npm run build:backend",
|
|
11
|
+
"build:frontend": "cd frontend && npm run build",
|
|
12
|
+
"build:backend": "node scripts/build-backend.mjs",
|
|
11
13
|
"start": "node bin/cli.js",
|
|
12
14
|
"prepublishOnly": "npm run build"
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
15
17
|
"bin/",
|
|
16
|
-
"backend/
|
|
17
|
-
"backend/package.json",
|
|
18
|
-
"backend/tsconfig.json",
|
|
18
|
+
"backend/dist/",
|
|
19
19
|
"frontend/dist/",
|
|
20
20
|
"data-dictionaries/stereotypes.yaml"
|
|
21
21
|
],
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"tsx": "^4.0.0",
|
|
24
|
-
"cors": "^2.8.5",
|
|
25
|
-
"dotenv": "^16.3.1",
|
|
26
|
-
"express": "^4.18.2",
|
|
27
|
-
"jsonschema": "^1.4.1",
|
|
28
|
-
"jsonwebtoken": "^9.0.2",
|
|
29
|
-
"swagger-jsdoc": "^6.2.8",
|
|
30
|
-
"swagger-ui-express": "^5.0.1",
|
|
31
|
-
"yaml": "^2.3.1",
|
|
32
|
-
"@hamak/filesystem-server-api": "^0.5.2",
|
|
33
|
-
"@hamak/filesystem-server-impl": "^0.5.2",
|
|
34
|
-
"@hamak/filesystem-server-spi": "^0.5.2",
|
|
35
|
-
"@hamak/shared-utils": "^0.5.2",
|
|
36
|
-
"@hamak/ui-remote-git-fs-backend": "^0.5.2",
|
|
37
|
-
"@types/jsonwebtoken": "^9.0.9",
|
|
38
|
-
"@types/cors": "^2.8.13",
|
|
39
|
-
"@types/express": "^4.17.17",
|
|
40
|
-
"@types/node": "^20.17.50",
|
|
41
|
-
"@types/swagger-jsdoc": "^6.0.4",
|
|
42
|
-
"@types/swagger-ui-express": "^4.1.8"
|
|
43
|
-
},
|
|
44
22
|
"keywords": [
|
|
45
23
|
"data-dictionary",
|
|
46
24
|
"data-modeling",
|
|
@@ -57,5 +35,28 @@
|
|
|
57
35
|
"repository": {
|
|
58
36
|
"type": "git",
|
|
59
37
|
"url": "https://github.com/amah/smart-data-dico.git"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@hamak/filesystem-server-api": "^0.5.2",
|
|
41
|
+
"@hamak/filesystem-server-impl": "^0.5.2",
|
|
42
|
+
"@hamak/filesystem-server-spi": "^0.5.2",
|
|
43
|
+
"@hamak/shared-utils": "^0.5.2",
|
|
44
|
+
"@hamak/ui-remote-git-fs-backend": "^0.5.2",
|
|
45
|
+
"@types/cors": "^2.8.13",
|
|
46
|
+
"@types/express": "^4.17.17",
|
|
47
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
48
|
+
"@types/node": "^20.17.50",
|
|
49
|
+
"@types/swagger-jsdoc": "^6.0.4",
|
|
50
|
+
"@types/swagger-ui-express": "^4.1.8",
|
|
51
|
+
"cors": "^2.8.5",
|
|
52
|
+
"dotenv": "^16.3.1",
|
|
53
|
+
"esbuild": "^0.27.4",
|
|
54
|
+
"express": "^4.18.2",
|
|
55
|
+
"jsonschema": "^1.4.1",
|
|
56
|
+
"jsonwebtoken": "^9.0.2",
|
|
57
|
+
"swagger-jsdoc": "^6.2.8",
|
|
58
|
+
"swagger-ui-express": "^5.0.1",
|
|
59
|
+
"tsx": "^4.0.0",
|
|
60
|
+
"yaml": "^2.3.1"
|
|
60
61
|
}
|
|
61
62
|
}
|
package/backend/package.json
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "data-dictionary-backend",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"description": "Backend for Data Dictionary Management System",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/server.js",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"start": "node dist/server.js",
|
|
9
|
-
"dev": "nodemon --exec tsx src/server.ts",
|
|
10
|
-
"build": "tsc",
|
|
11
|
-
"test": "jest",
|
|
12
|
-
"lint": "eslint . --ext .ts",
|
|
13
|
-
"test:coverage": "jest --coverage"
|
|
14
|
-
},
|
|
15
|
-
"dependencies": {
|
|
16
|
-
"@hamak/filesystem-server-api": "^0.5.2",
|
|
17
|
-
"@hamak/filesystem-server-impl": "^0.5.2",
|
|
18
|
-
"@hamak/filesystem-server-spi": "^0.5.2",
|
|
19
|
-
"@hamak/shared-utils": "^0.5.2",
|
|
20
|
-
"@hamak/ui-remote-git-fs-backend": "^0.5.2",
|
|
21
|
-
"@types/jsonwebtoken": "^9.0.9",
|
|
22
|
-
"cors": "^2.8.5",
|
|
23
|
-
"dotenv": "^16.3.1",
|
|
24
|
-
"express": "^4.18.2",
|
|
25
|
-
"jsonschema": "^1.4.1",
|
|
26
|
-
"jsonwebtoken": "^9.0.2",
|
|
27
|
-
"swagger-jsdoc": "^6.2.8",
|
|
28
|
-
"swagger-ui-express": "^5.0.1",
|
|
29
|
-
"yaml": "^2.3.1"
|
|
30
|
-
},
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"@jest/globals": "^30.3.0",
|
|
33
|
-
"@types/cors": "^2.8.13",
|
|
34
|
-
"@types/express": "^4.17.17",
|
|
35
|
-
"@types/jest": "^29.5.3",
|
|
36
|
-
"@types/node": "^20.17.50",
|
|
37
|
-
"@types/supertest": "^6.0.3",
|
|
38
|
-
"@types/swagger-jsdoc": "^6.0.4",
|
|
39
|
-
"@types/swagger-ui-express": "^4.1.8",
|
|
40
|
-
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
41
|
-
"@typescript-eslint/parser": "^6.0.0",
|
|
42
|
-
"eslint": "^8.45.0",
|
|
43
|
-
"jest": "^29.6.2",
|
|
44
|
-
"nodemon": "^3.0.1",
|
|
45
|
-
"supertest": "^7.1.1",
|
|
46
|
-
"ts-jest": "^29.1.1",
|
|
47
|
-
"ts-node": "^10.9.1",
|
|
48
|
-
"tsx": "^4.21.0",
|
|
49
|
-
"typescript": "^5.1.6"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { NextFunction, Request, Response } from 'express';
|
|
2
|
-
import request from 'supertest';
|
|
3
|
-
|
|
4
|
-
import { UserRole } from '../../middleware/auth.js';
|
|
5
|
-
import app from '../../server.js';
|
|
6
|
-
|
|
7
|
-
// Extend Request type to include user property
|
|
8
|
-
declare global {
|
|
9
|
-
namespace Express {
|
|
10
|
-
interface Request {
|
|
11
|
-
user?: {
|
|
12
|
-
id: string;
|
|
13
|
-
role: string;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Mock auth middleware to bypass authentication
|
|
20
|
-
jest.mock('../../middleware/auth', () => ({
|
|
21
|
-
UserRole: {
|
|
22
|
-
ADMIN: 'admin',
|
|
23
|
-
EDITOR: 'editor',
|
|
24
|
-
VIEWER: 'viewer',
|
|
25
|
-
},
|
|
26
|
-
authenticate: jest.fn().mockImplementation((_roles: string[]) => (req: Request, _res: Response, next: NextFunction) => {
|
|
27
|
-
req.user = { id: 'test-user', role: 'admin' };
|
|
28
|
-
next();
|
|
29
|
-
}),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
// Mock JWT auth to bypass token verification
|
|
33
|
-
jest.mock('../../middleware/jwtAuth', () => ({
|
|
34
|
-
verifyToken: jest.fn().mockImplementation((req: Request, _res: Response, next: NextFunction) => {
|
|
35
|
-
req.user = { id: 'test-user', role: 'admin' };
|
|
36
|
-
next();
|
|
37
|
-
}),
|
|
38
|
-
authorizeJwt: jest.fn().mockImplementation((_roles: string[]) => (req: Request, _res: Response, next: NextFunction) => {
|
|
39
|
-
req.user = { id: 'test-user', role: 'admin' };
|
|
40
|
-
next();
|
|
41
|
-
}),
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
// Mock services (uses __mocks__ directory auto-mocks)
|
|
45
|
-
jest.mock('../../services/dictionaryService');
|
|
46
|
-
jest.mock('../../services/entityService');
|
|
47
|
-
jest.mock('../../services/serviceService');
|
|
48
|
-
jest.mock('../../services/versionService');
|
|
49
|
-
jest.mock('../../utils/logger');
|
|
50
|
-
|
|
51
|
-
describe('API Integration Tests', () => {
|
|
52
|
-
describe('Health & Status', () => {
|
|
53
|
-
it('should return health status', async () => {
|
|
54
|
-
const response = await request(app).get('/health');
|
|
55
|
-
expect(response.status).toBe(200);
|
|
56
|
-
expect(response.body).toHaveProperty('status', 'ok');
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('Service Endpoints', () => {
|
|
61
|
-
it('GET /api/services should return service list', async () => {
|
|
62
|
-
const response = await request(app).get('/api/services');
|
|
63
|
-
expect(response.status).toBe(200);
|
|
64
|
-
expect(response.body).toHaveProperty('data');
|
|
65
|
-
expect(Array.isArray(response.body.data)).toBe(true);
|
|
66
|
-
expect(response.body.data).toContain('user-service');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('GET /api/services/:service/entities should return entities', async () => {
|
|
70
|
-
const response = await request(app).get('/api/services/user-service/entities');
|
|
71
|
-
expect(response.status).toBe(200);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('GET /api/services/:service/entities/:entity should return entity schema', async () => {
|
|
75
|
-
const response = await request(app).get('/api/services/user-service/entities/User');
|
|
76
|
-
expect(response.status).toBe(200);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('POST /api/services/:service/entities should create entity', async () => {
|
|
80
|
-
const response = await request(app)
|
|
81
|
-
.post('/api/services/user-service/entities')
|
|
82
|
-
.send({
|
|
83
|
-
uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
|
|
84
|
-
name: 'NewEntity',
|
|
85
|
-
description: 'A new entity',
|
|
86
|
-
attributes: [{ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'ID', type: 'string', required: true }],
|
|
87
|
-
});
|
|
88
|
-
expect(response.status).toBe(201);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('PUT /api/services/:service/entities/:entity should update entity', async () => {
|
|
92
|
-
const response = await request(app)
|
|
93
|
-
.put('/api/services/user-service/entities/User')
|
|
94
|
-
.send({
|
|
95
|
-
uuid: 'a38d1597-cc4f-4934-bb08-c876c023f693',
|
|
96
|
-
name: 'User',
|
|
97
|
-
description: 'Updated user',
|
|
98
|
-
attributes: [{ uuid: 'b49e2608-dd5f-4045-aa09-d464c234e694', name: 'id', description: 'ID', type: 'string', required: true }],
|
|
99
|
-
});
|
|
100
|
-
expect(response.status).toBe(200);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('DELETE /api/services/:service/entities/:entity should delete entity', async () => {
|
|
104
|
-
const response = await request(app).delete('/api/services/user-service/entities/User');
|
|
105
|
-
expect(response.status).toBe(200);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('Search & Graph', () => {
|
|
110
|
-
it('GET /api/search should return results', async () => {
|
|
111
|
-
const response = await request(app).get('/api/search?q=user');
|
|
112
|
-
expect(response.status).toBe(200);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('GET /api/graph/:service should return graph data', async () => {
|
|
116
|
-
const response = await request(app).get('/api/graph/user-service');
|
|
117
|
-
expect(response.status).toBe(200);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('Version Control', () => {
|
|
122
|
-
it('POST /api/commit should commit changes', async () => {
|
|
123
|
-
const response = await request(app)
|
|
124
|
-
.post('/api/commit')
|
|
125
|
-
.send({ message: 'Test commit' });
|
|
126
|
-
expect(response.status).toBe(200);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('GET /api/history should return commit history', async () => {
|
|
130
|
-
const response = await request(app).get('/api/history');
|
|
131
|
-
expect(response.status).toBe(200);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('POST /api/revert should revert to commit', async () => {
|
|
135
|
-
const response = await request(app)
|
|
136
|
-
.post('/api/revert')
|
|
137
|
-
.send({ commitHash: 'mock-commit-1' });
|
|
138
|
-
expect(response.status).toBe(200);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('Relationship Endpoints', () => {
|
|
143
|
-
it('GET /api/packages/:packageName/relationships should return relationships', async () => {
|
|
144
|
-
const response = await request(app).get('/api/packages/user-service/relationships');
|
|
145
|
-
expect(response.status).toBe(200);
|
|
146
|
-
expect(response.body).toHaveProperty('data');
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import dotenv from 'dotenv';
|
|
2
|
-
|
|
3
|
-
// Load environment variables for testing
|
|
4
|
-
dotenv.config({ path: '.env.test' });
|
|
5
|
-
|
|
6
|
-
// Mock logger to prevent console output during tests
|
|
7
|
-
jest.mock('../utils/logger', () => ({
|
|
8
|
-
logger: {
|
|
9
|
-
info: jest.fn(),
|
|
10
|
-
error: jest.fn(),
|
|
11
|
-
warn: jest.fn(),
|
|
12
|
-
debug: jest.fn(),
|
|
13
|
-
},
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
// Global test setup
|
|
17
|
-
beforeAll(() => {
|
|
18
|
-
// Setup any global test requirements here
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// Global test teardown
|
|
22
|
-
afterAll(() => {
|
|
23
|
-
// Clean up any global test resources here
|
|
24
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Express } from 'express';
|
|
2
|
-
import request from 'supertest';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates a test client for making HTTP requests to the Express app
|
|
8
|
-
* @param app Express application
|
|
9
|
-
* @returns Supertest instance
|
|
10
|
-
*/
|
|
11
|
-
export const createTestClient = (app: Express) => {
|
|
12
|
-
return request(app);
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Creates a temporary test directory for file operations
|
|
17
|
-
* @param dirPath Directory path to create
|
|
18
|
-
*/
|
|
19
|
-
export const createTestDirectory = (dirPath: string): void => {
|
|
20
|
-
if (!fs.existsSync(dirPath)) {
|
|
21
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Removes a test directory and all its contents
|
|
27
|
-
* @param dirPath Directory path to remove
|
|
28
|
-
*/
|
|
29
|
-
export const removeTestDirectory = (dirPath: string): void => {
|
|
30
|
-
if (fs.existsSync(dirPath)) {
|
|
31
|
-
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates a test YAML file with the given content
|
|
37
|
-
* @param filePath Path to create the file
|
|
38
|
-
* @param content Content to write to the file
|
|
39
|
-
*/
|
|
40
|
-
export const createTestYamlFile = (filePath: string, content: string): void => {
|
|
41
|
-
const dir = path.dirname(filePath);
|
|
42
|
-
if (!fs.existsSync(dir)) {
|
|
43
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
-
}
|
|
45
|
-
fs.writeFileSync(filePath, content);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Generates a random entity for testing
|
|
50
|
-
* @param overrides Properties to override in the generated entity
|
|
51
|
-
* @returns A test entity
|
|
52
|
-
*/
|
|
53
|
-
export const generateTestEntity = (overrides = {}) => {
|
|
54
|
-
return {
|
|
55
|
-
id: `test-entity-${Date.now()}`,
|
|
56
|
-
name: 'TestEntity',
|
|
57
|
-
description: 'A test entity for unit tests',
|
|
58
|
-
microservice: 'test-service',
|
|
59
|
-
version: '1.0.0',
|
|
60
|
-
attributes: [
|
|
61
|
-
{
|
|
62
|
-
name: 'id',
|
|
63
|
-
description: 'Primary identifier',
|
|
64
|
-
type: 'string',
|
|
65
|
-
required: true,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'name',
|
|
69
|
-
description: 'Entity name',
|
|
70
|
-
type: 'string',
|
|
71
|
-
required: true,
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
...overrides,
|
|
75
|
-
};
|
|
76
|
-
};
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EntityFileAdapter
|
|
3
|
-
*
|
|
4
|
-
* Wraps @hamak/filesystem-server-impl's WorkspaceManager to provide
|
|
5
|
-
* the same API as current fileOperations.ts functions.
|
|
6
|
-
* Uses dynamic imports since the framework packages are ESM-only.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import YAML from 'yaml';
|
|
11
|
-
import { logger } from '../utils/logger.js';
|
|
12
|
-
import { Entity, validateEntity } from '../models/EntitySchema.js';
|
|
13
|
-
import { generateEntityFilename } from '../utils/uuid.js';
|
|
14
|
-
import { config } from '../kernel/config.js';
|
|
15
|
-
|
|
16
|
-
// Lazy-loaded framework modules (ESM)
|
|
17
|
-
let WorkspaceManager: any;
|
|
18
|
-
let FileRouter: any;
|
|
19
|
-
let FileInfoEnricherRegistry: any;
|
|
20
|
-
|
|
21
|
-
let workspaceManager: any = null;
|
|
22
|
-
let enricherRegistry: any = null;
|
|
23
|
-
let fileRouter: any = null;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Initialize the framework filesystem components.
|
|
27
|
-
* Must be called once at startup (async because of ESM dynamic imports).
|
|
28
|
-
*/
|
|
29
|
-
export async function initializeFileSystem(): Promise<{
|
|
30
|
-
workspaceManager: any;
|
|
31
|
-
fileRouter: any;
|
|
32
|
-
enricherRegistry: any;
|
|
33
|
-
}> {
|
|
34
|
-
if (workspaceManager) {
|
|
35
|
-
return { workspaceManager, fileRouter, enricherRegistry };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const fsModule = await import('@hamak/filesystem-server-impl');
|
|
39
|
-
WorkspaceManager = fsModule.WorkspaceManager;
|
|
40
|
-
FileRouter = fsModule.FileRouter;
|
|
41
|
-
FileInfoEnricherRegistry = fsModule.FileInfoEnricherRegistry;
|
|
42
|
-
|
|
43
|
-
const baseDirectory = config.dataDir;
|
|
44
|
-
|
|
45
|
-
const workspacesConfig: Record<string, string> = {
|
|
46
|
-
dictionaries: '.',
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
workspaceManager = new WorkspaceManager(workspacesConfig, { baseDirectory });
|
|
50
|
-
enricherRegistry = new FileInfoEnricherRegistry();
|
|
51
|
-
fileRouter = new FileRouter(workspaceManager, { enricherRegistry });
|
|
52
|
-
|
|
53
|
-
logger.info('Framework filesystem initialized', { baseDirectory });
|
|
54
|
-
|
|
55
|
-
return { workspaceManager, fileRouter, enricherRegistry };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get the Express router for the /fs endpoint.
|
|
60
|
-
*/
|
|
61
|
-
export function getFileRouter(): any {
|
|
62
|
-
if (!fileRouter) {
|
|
63
|
-
throw new Error('FileRouter not initialized. Call initializeFileSystem() first.');
|
|
64
|
-
}
|
|
65
|
-
return fileRouter.router;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get the enricher registry for registering file info enrichers.
|
|
70
|
-
*/
|
|
71
|
-
export function getEnricherRegistry(): any {
|
|
72
|
-
if (!enricherRegistry) {
|
|
73
|
-
throw new Error('EnricherRegistry not initialized. Call initializeFileSystem() first.');
|
|
74
|
-
}
|
|
75
|
-
return enricherRegistry;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get the workspace manager instance.
|
|
80
|
-
*/
|
|
81
|
-
export function getWorkspaceManager(): any {
|
|
82
|
-
if (!workspaceManager) {
|
|
83
|
-
throw new Error('WorkspaceManager not initialized. Call initializeFileSystem() first.');
|
|
84
|
-
}
|
|
85
|
-
return workspaceManager;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Read an entity file using WorkspaceManager.
|
|
90
|
-
*/
|
|
91
|
-
export async function readEntityViaAdapter(
|
|
92
|
-
packageName: string,
|
|
93
|
-
entityName: string
|
|
94
|
-
): Promise<Entity | null> {
|
|
95
|
-
if (!workspaceManager) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const dirPath = `microservices/${packageName}`;
|
|
101
|
-
const files = await workspaceManager.listFiles('dictionaries', dirPath);
|
|
102
|
-
|
|
103
|
-
for (const file of files) {
|
|
104
|
-
if (!file.name.endsWith('.yaml') && !file.name.endsWith('.yml')) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (file.name === 'metadata.yaml' || file.name === 'relationships.yaml') {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const filePath = `${dirPath}/${file.name}`;
|
|
112
|
-
const content = await workspaceManager.readFile('dictionaries', filePath);
|
|
113
|
-
const entity = YAML.parse(content) as Entity;
|
|
114
|
-
|
|
115
|
-
if (entity.name === entityName) {
|
|
116
|
-
return entity;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return null;
|
|
121
|
-
} catch (error) {
|
|
122
|
-
logger.error(`EntityFileAdapter.readEntity error: ${error}`);
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Write an entity file using WorkspaceManager.
|
|
129
|
-
*/
|
|
130
|
-
export async function writeEntityViaAdapter(entity: Entity, packageName: string): Promise<boolean> {
|
|
131
|
-
if (!workspaceManager) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const validation = validateEntity(entity);
|
|
137
|
-
if (!validation.valid) {
|
|
138
|
-
logger.error(`Invalid entity: ${validation.errors.join(', ')}`);
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const filename = generateEntityFilename(entity.uuid, entity.name);
|
|
143
|
-
const filePath = `microservices/${packageName}/${filename}`;
|
|
144
|
-
const yamlContent = YAML.stringify(entity);
|
|
145
|
-
|
|
146
|
-
await workspaceManager.writeFile('dictionaries', filePath, yamlContent);
|
|
147
|
-
logger.info(`Entity written via adapter: ${filePath}`);
|
|
148
|
-
|
|
149
|
-
return true;
|
|
150
|
-
} catch (error) {
|
|
151
|
-
logger.error(`EntityFileAdapter.writeEntity error: ${error}`);
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* YamlFileInfoEnricher
|
|
3
|
-
*
|
|
4
|
-
* FileInfoEnricher that parses .yaml files and adds entity metadata
|
|
5
|
-
* (entity name, uuid) to the file info response.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import YAML from 'yaml';
|
|
9
|
-
import { logger } from '../utils/logger.js';
|
|
10
|
-
|
|
11
|
-
export interface YamlEnrichedData {
|
|
12
|
-
entityName?: string;
|
|
13
|
-
entityUuid?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Creates a YAML file info enricher compatible with
|
|
18
|
-
* @hamak/filesystem-server-impl's FileInfoEnricherRegistry.
|
|
19
|
-
*/
|
|
20
|
-
export function createYamlFileInfoEnricher() {
|
|
21
|
-
return {
|
|
22
|
-
name: 'yaml-entity',
|
|
23
|
-
extensionKey: 'entity',
|
|
24
|
-
|
|
25
|
-
canEnrich(fileInfo: any): boolean {
|
|
26
|
-
const name: string = fileInfo?.name || '';
|
|
27
|
-
return name.endsWith('.yaml') || name.endsWith('.yml');
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
async enrich(fileInfo: any, context: any): Promise<YamlEnrichedData | null> {
|
|
31
|
-
try {
|
|
32
|
-
const content = context?.content;
|
|
33
|
-
if (!content) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const parsed = YAML.parse(content);
|
|
38
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
entityName: parsed.name,
|
|
44
|
-
entityUuid: parsed.uuid,
|
|
45
|
-
};
|
|
46
|
-
} catch (error) {
|
|
47
|
-
logger.debug(`YamlFileInfoEnricher: could not parse ${fileInfo?.name}: ${error}`);
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|