@vxnus/siduri 0.1.8 → 0.1.11
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 +3 -4
- package/dist/builtin-manifests.d.ts +2 -0
- package/dist/builtin-manifests.js +492 -0
- package/dist/clean-machine-e2e.test.d.ts +1 -0
- package/dist/clean-machine-e2e.test.js +236 -0
- package/dist/configurators/behavior.d.ts +2 -0
- package/dist/configurators/behavior.js +36 -0
- package/dist/configurators/body.d.ts +2 -0
- package/dist/configurators/body.js +60 -0
- package/dist/configurators/brain.d.ts +6 -0
- package/dist/configurators/brain.js +80 -0
- package/dist/configurators/ear.d.ts +2 -0
- package/dist/configurators/ear.js +28 -0
- package/dist/configurators/hands.d.ts +2 -0
- package/dist/configurators/hands.js +27 -0
- package/dist/configurators/index.d.ts +24 -0
- package/dist/configurators/index.js +78 -0
- package/dist/configurators/knowledge.d.ts +6 -0
- package/dist/configurators/knowledge.js +100 -0
- package/dist/configurators/memory.d.ts +2 -0
- package/dist/configurators/memory.js +44 -0
- package/dist/configurators/mouth.d.ts +2 -0
- package/dist/configurators/mouth.js +15 -0
- package/dist/configurators/observation.d.ts +2 -0
- package/dist/configurators/observation.js +12 -0
- package/dist/configurators/types.d.ts +12 -0
- package/dist/configurators/types.js +2 -0
- package/dist/configurators/vision.d.ts +2 -0
- package/dist/configurators/vision.js +39 -0
- package/dist/configurators/voice.d.ts +2 -0
- package/dist/configurators/voice.js +477 -0
- package/dist/configurators.test.d.ts +1 -0
- package/dist/configurators.test.js +331 -0
- package/dist/db.d.ts +25 -0
- package/dist/db.js +152 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.js +87 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +69 -0
- package/dist/doctor-db.test.d.ts +1 -0
- package/dist/doctor-db.test.js +141 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +249 -0
- package/dist/generator.d.ts +20 -0
- package/dist/generator.js +615 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +197 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +395 -0
- package/dist/manifest.d.ts +33 -0
- package/dist/manifest.js +40 -0
- package/dist/providers/knowledge-hub.d.ts +37 -0
- package/dist/providers/knowledge-hub.js +141 -0
- package/dist/providers/openrouter.d.ts +32 -0
- package/dist/providers/openrouter.js +201 -0
- package/dist/release-check.d.ts +9 -0
- package/dist/release-check.js +133 -0
- package/dist/runtime-parity.test.d.ts +1 -0
- package/dist/runtime-parity.test.js +141 -0
- package/dist/schema-validator.d.ts +9 -0
- package/dist/schema-validator.js +107 -0
- package/dist/web-dist/404/index.html +1 -0
- package/dist/web-dist/404.html +1 -0
- package/dist/web-dist/__next.__PAGE__.txt +35 -0
- package/dist/web-dist/__next._full.txt +32 -0
- package/dist/web-dist/__next._tree.txt +4 -0
- package/dist/web-dist/_next/static/chunks/09y7khbmco_v4.js +1 -0
- package/dist/web-dist/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +1 -0
- package/dist/web-dist/_next/static/chunks/0xhe1hasjan-9.css +1 -0
- package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +1 -0
- package/dist/web-dist/_next/static/chunks/2_duynll0m51m.js +30 -0
- package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +31 -0
- package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +22 -0
- package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +1 -0
- package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +1 -0
- package/dist/web-dist/_next/static/media/apple-icon.2g7-yefdphkmy.png +0 -0
- package/dist/web-dist/_next/static/media/favicon.3rlors1ui1gw9.ico +0 -0
- package/dist/web-dist/_next/static/media/icon.0580nb9h-ugue.svg +4 -0
- package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_buildManifest.js +11 -0
- package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_clientMiddlewareManifest.js +1 -0
- package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_ssgManifest.js +1 -0
- package/dist/web-dist/_not-found/__next._full.txt +17 -0
- package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +23 -0
- package/dist/web-dist/_not-found/__next._tree.txt +3 -0
- package/dist/web-dist/_not-found/index.html +1 -0
- package/dist/web-dist/_not-found/index.txt +17 -0
- package/dist/web-dist/apple-icon.png +0 -0
- package/dist/web-dist/apple-touch-icon.png +0 -0
- package/dist/web-dist/chat/__next._full.txt +18 -0
- package/dist/web-dist/chat/__next._tree.txt +3 -0
- package/dist/web-dist/chat/__next.chat.__PAGE__.txt +24 -0
- package/dist/web-dist/chat/index.html +1 -0
- package/dist/web-dist/chat/index.txt +18 -0
- package/dist/web-dist/favicon.ico +0 -0
- package/dist/web-dist/favicon.svg +4 -0
- package/dist/web-dist/icon.svg +4 -0
- package/dist/web-dist/index.html +1 -0
- package/dist/web-dist/index.txt +32 -0
- package/dist/web-dist/live2d/live2dcubismcore.min.js +9 -0
- package/dist/web-dist/logo-circle.png +0 -0
- package/dist/web-dist/logo-circle.svg +4 -0
- package/dist/web-dist/logo-white.svg +3 -0
- package/dist/web-dist/logo.png +0 -0
- package/dist/web-dist/logo.svg +3 -0
- package/dist/web-dist/operator/__next._full.txt +18 -0
- package/dist/web-dist/operator/__next._tree.txt +3 -0
- package/dist/web-dist/operator/__next.operator.__PAGE__.txt +24 -0
- package/dist/web-dist/operator/index.html +1 -0
- package/dist/web-dist/operator/index.txt +18 -0
- package/dist/web-template.d.ts +2 -0
- package/dist/web-template.js +557 -0
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
7
|
+
const discovery_1 = require("./discovery");
|
|
8
|
+
const generator_1 = require("./generator");
|
|
9
|
+
describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
|
|
10
|
+
const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
|
|
11
|
+
test('Registry successfully discovers all 11 organ packages from workspace', () => {
|
|
12
|
+
const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
|
|
13
|
+
const manifests = registry.getAll();
|
|
14
|
+
expect(manifests.length).toBe(11);
|
|
15
|
+
const organTypes = registry.getAvailableOrganTypes().sort();
|
|
16
|
+
expect(organTypes).toEqual([
|
|
17
|
+
'behavior',
|
|
18
|
+
'body',
|
|
19
|
+
'brain',
|
|
20
|
+
'ear',
|
|
21
|
+
'hands',
|
|
22
|
+
'knowledge',
|
|
23
|
+
'memory',
|
|
24
|
+
'mouth',
|
|
25
|
+
'observation',
|
|
26
|
+
'vision',
|
|
27
|
+
'voice',
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
test('Generates valid instances from discovered manifests', () => {
|
|
31
|
+
const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
|
|
32
|
+
// Test Brain + Hands + Vision
|
|
33
|
+
const brain = registry.get('brain');
|
|
34
|
+
const hands = registry.get('hands');
|
|
35
|
+
const vision = registry.get('vision');
|
|
36
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
37
|
+
name: 'robotics-agent',
|
|
38
|
+
selectedManifests: [brain, hands, vision],
|
|
39
|
+
});
|
|
40
|
+
const pkg = JSON.parse(files['package.json']);
|
|
41
|
+
expect(Object.keys(pkg.dependencies).sort()).toEqual([
|
|
42
|
+
'@siduri-x/brain',
|
|
43
|
+
'@siduri-x/core',
|
|
44
|
+
'@siduri-x/hands',
|
|
45
|
+
'@siduri-x/vision',
|
|
46
|
+
]);
|
|
47
|
+
const schema = JSON.parse(files['siduri.schema.json']);
|
|
48
|
+
expect(Object.keys(schema.properties.organs.properties).sort()).toEqual(['brain', 'hands', 'vision']);
|
|
49
|
+
const config = JSON.parse(files['siduri.config.json']);
|
|
50
|
+
expect(Object.keys(config.organs).sort()).toEqual(['brain', 'hands', 'vision']);
|
|
51
|
+
expect(files['.env.example']).toContain('OPENROUTER_API_KEY');
|
|
52
|
+
expect(files['.env.example']).toContain('ACTION_POLICY_SECRET');
|
|
53
|
+
expect(files['.env.example']).not.toContain('DATABASE_URL');
|
|
54
|
+
});
|
|
55
|
+
test('Validates manifest contract integrity during discovery', () => {
|
|
56
|
+
expect(() => {
|
|
57
|
+
const invalidRegistry = new discovery_1.OrganRegistry();
|
|
58
|
+
invalidRegistry.register({});
|
|
59
|
+
}).toThrow(/Invalid manifest/);
|
|
60
|
+
});
|
|
61
|
+
test('Fallback to builtin organ manifests when scanning non-existent search roots', () => {
|
|
62
|
+
const registry = discovery_1.OrganRegistry.discover(['/non-existent-directory/empty']);
|
|
63
|
+
const manifests = registry.getAll();
|
|
64
|
+
expect(manifests.length).toBe(11);
|
|
65
|
+
expect(registry.get('brain')).toBeDefined();
|
|
66
|
+
expect(registry.get('memory')).toBeDefined();
|
|
67
|
+
expect(registry.get('mouth')).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const doctor_1 = require("./doctor");
|
|
9
|
+
const db_1 = require("./db");
|
|
10
|
+
const generator_1 = require("./generator");
|
|
11
|
+
const discovery_1 = require("./discovery");
|
|
12
|
+
describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
13
|
+
const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
|
|
14
|
+
const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
|
|
15
|
+
const brain = registry.get('brain');
|
|
16
|
+
const hands = registry.get('hands');
|
|
17
|
+
const memory = registry.get('memory');
|
|
18
|
+
const testTempRoot = node_path_1.default.resolve(__dirname, '../temp-phase4-test');
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
if (node_fs_1.default.existsSync(testTempRoot)) {
|
|
21
|
+
node_fs_1.default.rmSync(testTempRoot, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
node_fs_1.default.mkdirSync(testTempRoot, { recursive: true });
|
|
24
|
+
});
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
if (node_fs_1.default.existsSync(testTempRoot)) {
|
|
27
|
+
node_fs_1.default.rmSync(testTempRoot, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
test('redactDatabaseUrl redacts password and sensitive credentials', () => {
|
|
31
|
+
const raw = 'postgresql://myuser:supersecretpassword@db.example.com:5432/siduri_db';
|
|
32
|
+
const redacted = (0, db_1.redactDatabaseUrl)(raw);
|
|
33
|
+
expect(redacted).not.toContain('supersecretpassword');
|
|
34
|
+
expect(redacted).toContain('***');
|
|
35
|
+
expect(redacted).toContain('myuser');
|
|
36
|
+
expect(redacted).toContain('db.example.com:5432/siduri_db');
|
|
37
|
+
});
|
|
38
|
+
test('Test Matrix A: Brain only instance doctor and db push', async () => {
|
|
39
|
+
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-only');
|
|
40
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
41
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
42
|
+
name: 'BrainOnlyInstance',
|
|
43
|
+
selectedManifests: [brain],
|
|
44
|
+
});
|
|
45
|
+
for (const [name, content] of Object.entries(files)) {
|
|
46
|
+
if (typeof content === 'string') {
|
|
47
|
+
const fullPath = node_path_1.default.join(instanceDir, name);
|
|
48
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
49
|
+
node_fs_1.default.writeFileSync(fullPath, content);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 1. Doctor without OPENROUTER_API_KEY -> FAIL (Brain Health Probe fails)
|
|
53
|
+
const doctorFail = await (0, doctor_1.runDoctor)({ projectDir: instanceDir, env: { OPENROUTER_API_KEY: '' } });
|
|
54
|
+
expect(doctorFail.passed).toBe(false);
|
|
55
|
+
expect(doctorFail.results.some((r) => r.category === 'Health Probe' && r.status === 'FAIL')).toBe(true);
|
|
56
|
+
// Database check must be SKIPPED
|
|
57
|
+
expect(doctorFail.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
|
|
58
|
+
// 2. Doctor with OPENROUTER_API_KEY -> PASS
|
|
59
|
+
const doctorPass = await (0, doctor_1.runDoctor)({
|
|
60
|
+
projectDir: instanceDir,
|
|
61
|
+
env: { OPENROUTER_API_KEY: 'test-key-123' },
|
|
62
|
+
});
|
|
63
|
+
expect(doctorPass.passed).toBe(true);
|
|
64
|
+
// 3. db push -> NOOP ("No database migrations are required...")
|
|
65
|
+
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
66
|
+
expect(dbResult.status).toBe('NOOP');
|
|
67
|
+
expect(dbResult.message).toContain('No database migrations are required');
|
|
68
|
+
});
|
|
69
|
+
test('Test Matrix B: Brain + Hands instance doctor and db push', async () => {
|
|
70
|
+
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-hands');
|
|
71
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
72
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
73
|
+
name: 'BrainHandsInstance',
|
|
74
|
+
selectedManifests: [brain, hands],
|
|
75
|
+
});
|
|
76
|
+
for (const [name, content] of Object.entries(files)) {
|
|
77
|
+
if (typeof content === 'string') {
|
|
78
|
+
const fullPath = node_path_1.default.join(instanceDir, name);
|
|
79
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
80
|
+
node_fs_1.default.writeFileSync(fullPath, content);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Doctor checks both keys
|
|
84
|
+
const doctorRes = await (0, doctor_1.runDoctor)({
|
|
85
|
+
projectDir: instanceDir,
|
|
86
|
+
env: {
|
|
87
|
+
OPENROUTER_API_KEY: 'test-openrouter-key',
|
|
88
|
+
ACTION_POLICY_SECRET: 'test-policy-secret',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
expect(doctorRes.passed).toBe(true);
|
|
92
|
+
expect(doctorRes.results.some((r) => r.name === 'OPENROUTER_API_KEY' && r.status === 'PASS')).toBe(true);
|
|
93
|
+
expect(doctorRes.results.some((r) => r.name === 'ACTION_POLICY_SECRET' && r.status === 'PASS')).toBe(true);
|
|
94
|
+
// Database check must be SKIPPED
|
|
95
|
+
expect(doctorRes.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
|
|
96
|
+
// db push returns NOOP
|
|
97
|
+
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
98
|
+
expect(dbResult.status).toBe('NOOP');
|
|
99
|
+
});
|
|
100
|
+
test('Test Matrix C: Brain + Memory instance requires DATABASE_URL', async () => {
|
|
101
|
+
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-memory');
|
|
102
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
103
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
104
|
+
name: 'BrainMemoryInstance',
|
|
105
|
+
selectedManifests: [brain, memory],
|
|
106
|
+
});
|
|
107
|
+
for (const [name, content] of Object.entries(files)) {
|
|
108
|
+
if (typeof content === 'string') {
|
|
109
|
+
const fullPath = node_path_1.default.join(instanceDir, name);
|
|
110
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
111
|
+
node_fs_1.default.writeFileSync(fullPath, content);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 1. Doctor fails without DATABASE_URL
|
|
115
|
+
const doctorMissingDb = await (0, doctor_1.runDoctor)({
|
|
116
|
+
projectDir: instanceDir,
|
|
117
|
+
env: { OPENROUTER_API_KEY: 'test-key' },
|
|
118
|
+
});
|
|
119
|
+
expect(doctorMissingDb.passed).toBe(false);
|
|
120
|
+
expect(doctorMissingDb.results.some((r) => r.name === 'DATABASE_URL' && r.status === 'FAIL')).toBe(true);
|
|
121
|
+
// 2. db push fails clearly without DATABASE_URL
|
|
122
|
+
await expect((0, db_1.runDbPush)({ projectDir: instanceDir, env: {} })).rejects.toThrow(/DATABASE_URL is required/);
|
|
123
|
+
});
|
|
124
|
+
test('Test Matrix D: Diagnostics and Error Handling for invalid or missing configs', async () => {
|
|
125
|
+
// 1. Missing siduri.config.json
|
|
126
|
+
const nonExistentDir = node_path_1.default.join(testTempRoot, 'non-existent-dir');
|
|
127
|
+
await expect((0, doctor_1.runDoctor)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
|
|
128
|
+
await expect((0, db_1.runDbPush)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
|
|
129
|
+
// 2. Unreachable PostgreSQL host reported gracefully by doctor
|
|
130
|
+
const instDir = node_path_1.default.join(testTempRoot, 'brain-memory');
|
|
131
|
+
const doctorUnreachable = await (0, doctor_1.runDoctor)({
|
|
132
|
+
projectDir: instDir,
|
|
133
|
+
env: {
|
|
134
|
+
OPENROUTER_API_KEY: 'test-key',
|
|
135
|
+
DATABASE_URL: 'postgresql://postgres:postgres@127.0.0.1:59999/non_existent_db',
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
expect(doctorUnreachable.passed).toBe(false);
|
|
139
|
+
expect(doctorUnreachable.results.some((r) => r.category === 'Database' && r.status === 'FAIL')).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DoctorCheckResult {
|
|
2
|
+
category: 'Environment' | 'Services' | 'Database' | 'Health Probe' | 'Configuration';
|
|
3
|
+
name: string;
|
|
4
|
+
status: 'PASS' | 'FAIL' | 'OPTIONAL_MISSING' | 'SKIPPED';
|
|
5
|
+
organName?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
remediation?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DoctorReport {
|
|
10
|
+
instanceName: string;
|
|
11
|
+
configuredOrgans: string[];
|
|
12
|
+
results: DoctorCheckResult[];
|
|
13
|
+
passed: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface DoctorOptions {
|
|
16
|
+
projectDir?: string;
|
|
17
|
+
env?: Record<string, string | undefined>;
|
|
18
|
+
}
|
|
19
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDoctor = runDoctor;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const discovery_1 = require("./discovery");
|
|
10
|
+
const pg_1 = require("pg");
|
|
11
|
+
const schema_validator_1 = require("./schema-validator");
|
|
12
|
+
async function runDoctor(options = {}) {
|
|
13
|
+
const projectDir = options.projectDir ? node_path_1.default.resolve(options.projectDir) : process.cwd();
|
|
14
|
+
const configPath = node_path_1.default.join(projectDir, 'siduri.config.json');
|
|
15
|
+
if (!node_fs_1.default.existsSync(configPath)) {
|
|
16
|
+
throw new Error(`siduri.config.json not found at ${projectDir}. Make sure you are in a Siduri instance directory.`);
|
|
17
|
+
}
|
|
18
|
+
// Load .env if present in projectDir
|
|
19
|
+
const envFile = node_path_1.default.join(projectDir, '.env');
|
|
20
|
+
const fileEnv = {};
|
|
21
|
+
if (node_fs_1.default.existsSync(envFile)) {
|
|
22
|
+
const lines = node_fs_1.default.readFileSync(envFile, 'utf8').split('\n');
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
26
|
+
continue;
|
|
27
|
+
const eqIdx = trimmed.indexOf('=');
|
|
28
|
+
if (eqIdx !== -1) {
|
|
29
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
30
|
+
const val = trimmed.slice(eqIdx + 1).trim();
|
|
31
|
+
fileEnv[key] = val;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const effectiveEnv = {
|
|
36
|
+
...process.env,
|
|
37
|
+
...fileEnv,
|
|
38
|
+
...(options.env || {}),
|
|
39
|
+
};
|
|
40
|
+
const config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf8'));
|
|
41
|
+
const configuredOrgansMap = config.organs || {};
|
|
42
|
+
const organKeys = Object.keys(configuredOrgansMap);
|
|
43
|
+
const registry = discovery_1.OrganRegistry.discover([
|
|
44
|
+
node_path_1.default.join(projectDir, 'node_modules/@siduri-x'),
|
|
45
|
+
node_path_1.default.resolve(__dirname, '../../packages/organs'),
|
|
46
|
+
node_path_1.default.resolve(process.cwd(), 'packages/organs'),
|
|
47
|
+
]);
|
|
48
|
+
const selectedManifests = [];
|
|
49
|
+
const results = [];
|
|
50
|
+
// 0. Schema Validation Check
|
|
51
|
+
const schemaPath = node_path_1.default.join(projectDir, 'siduri.schema.json');
|
|
52
|
+
if (node_fs_1.default.existsSync(schemaPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const schema = JSON.parse(node_fs_1.default.readFileSync(schemaPath, 'utf8'));
|
|
55
|
+
(0, schema_validator_1.validateCompanionConfig)(config, schema);
|
|
56
|
+
results.push({
|
|
57
|
+
category: 'Configuration',
|
|
58
|
+
name: 'Schema Conformance',
|
|
59
|
+
status: 'PASS',
|
|
60
|
+
message: 'siduri.config.json conforms to siduri.schema.json',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
results.push({
|
|
65
|
+
category: 'Configuration',
|
|
66
|
+
name: 'Schema Conformance',
|
|
67
|
+
status: 'FAIL',
|
|
68
|
+
message: err.message,
|
|
69
|
+
remediation: 'Update siduri.config.json to conform to the generated schema.',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 1. Resolve manifests for selected organs
|
|
74
|
+
for (const organKey of organKeys) {
|
|
75
|
+
const m = registry.get(organKey) || registry.getAll().find((item) => item.configKey === organKey || item.organType === organKey);
|
|
76
|
+
if (!m) {
|
|
77
|
+
results.push({
|
|
78
|
+
category: 'Environment',
|
|
79
|
+
name: `Manifest resolution: ${organKey}`,
|
|
80
|
+
status: 'FAIL',
|
|
81
|
+
message: `Could not resolve package manifest for configured organ '${organKey}'.`,
|
|
82
|
+
remediation: `Ensure @siduri-x/${organKey} is installed in package.json and npm install has been run.`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
selectedManifests.push(m);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 2. Check Environment Variables (deduplicated)
|
|
90
|
+
const checkedEnvVars = new Set();
|
|
91
|
+
for (const m of selectedManifests) {
|
|
92
|
+
for (const envVar of m.environment || []) {
|
|
93
|
+
if (checkedEnvVars.has(envVar.name))
|
|
94
|
+
continue;
|
|
95
|
+
checkedEnvVars.add(envVar.name);
|
|
96
|
+
const val = effectiveEnv[envVar.name];
|
|
97
|
+
const isMissing = !val || val.trim() === '';
|
|
98
|
+
if (isMissing) {
|
|
99
|
+
if (envVar.required) {
|
|
100
|
+
results.push({
|
|
101
|
+
category: 'Environment',
|
|
102
|
+
name: envVar.name,
|
|
103
|
+
status: 'FAIL',
|
|
104
|
+
organName: m.name,
|
|
105
|
+
message: `Missing required environment variable '${envVar.name}'.`,
|
|
106
|
+
remediation: `Set ${envVar.name} in .env or your shell environment. ${envVar.description || ''}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
results.push({
|
|
111
|
+
category: 'Environment',
|
|
112
|
+
name: envVar.name,
|
|
113
|
+
status: 'OPTIONAL_MISSING',
|
|
114
|
+
organName: m.name,
|
|
115
|
+
message: `Optional variable '${envVar.name}' not set.`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
results.push({
|
|
121
|
+
category: 'Environment',
|
|
122
|
+
name: envVar.name,
|
|
123
|
+
status: 'PASS',
|
|
124
|
+
organName: m.name,
|
|
125
|
+
message: 'Configured',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// 3. Check External Services
|
|
131
|
+
for (const m of selectedManifests) {
|
|
132
|
+
for (const service of m.services || []) {
|
|
133
|
+
results.push({
|
|
134
|
+
category: 'Services',
|
|
135
|
+
name: `${service.name} (${m.organType})`,
|
|
136
|
+
status: 'PASS',
|
|
137
|
+
organName: m.name,
|
|
138
|
+
message: 'Service requirement declared',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// 4. Database Check (only if an organ declares database requirement)
|
|
143
|
+
const dbOrgan = selectedManifests.find((m) => m.database !== null && m.database !== undefined);
|
|
144
|
+
if (dbOrgan) {
|
|
145
|
+
const dbUrl = effectiveEnv.DATABASE_URL;
|
|
146
|
+
if (!dbUrl) {
|
|
147
|
+
results.push({
|
|
148
|
+
category: 'Database',
|
|
149
|
+
name: 'PostgreSQL Connection',
|
|
150
|
+
status: 'FAIL',
|
|
151
|
+
organName: dbOrgan.name,
|
|
152
|
+
message: 'DATABASE_URL is not configured.',
|
|
153
|
+
remediation: 'Provide DATABASE_URL in .env (e.g. postgresql://postgres:postgres@localhost:5432/siduri)',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const pool = new pg_1.Pool({ connectionString: dbUrl, connectionTimeoutMillis: 3000 });
|
|
158
|
+
try {
|
|
159
|
+
const client = await pool.connect();
|
|
160
|
+
await client.query('SELECT 1');
|
|
161
|
+
client.release();
|
|
162
|
+
await pool.end();
|
|
163
|
+
results.push({
|
|
164
|
+
category: 'Database',
|
|
165
|
+
name: 'PostgreSQL Connection',
|
|
166
|
+
status: 'PASS',
|
|
167
|
+
organName: dbOrgan.name,
|
|
168
|
+
message: 'Connection successful',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
await pool.end().catch(() => { });
|
|
173
|
+
results.push({
|
|
174
|
+
category: 'Database',
|
|
175
|
+
name: 'PostgreSQL Connection',
|
|
176
|
+
status: 'FAIL',
|
|
177
|
+
organName: dbOrgan.name,
|
|
178
|
+
message: `Connection failed: ${err.message}`,
|
|
179
|
+
remediation: 'Check database host availability, port, credentials, and network accessibility.',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
results.push({
|
|
186
|
+
category: 'Database',
|
|
187
|
+
name: 'PostgreSQL Database',
|
|
188
|
+
status: 'SKIPPED',
|
|
189
|
+
message: 'Not required by current composition',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// 5. Organ Health Probes
|
|
193
|
+
for (const m of selectedManifests) {
|
|
194
|
+
if (m.healthCheck) {
|
|
195
|
+
try {
|
|
196
|
+
// Resolve package entrypoint
|
|
197
|
+
const candidateEntrypoints = [
|
|
198
|
+
node_path_1.default.resolve(projectDir, 'node_modules', m.name, m.entrypoint),
|
|
199
|
+
node_path_1.default.resolve(__dirname, '../../packages/organs', m.organType, m.entrypoint),
|
|
200
|
+
node_path_1.default.resolve(process.cwd(), 'packages/organs', m.organType, m.entrypoint),
|
|
201
|
+
];
|
|
202
|
+
const entryPath = candidateEntrypoints.find((p) => node_fs_1.default.existsSync(p));
|
|
203
|
+
if (entryPath) {
|
|
204
|
+
const mod = require(entryPath);
|
|
205
|
+
const probeFn = mod[m.healthCheck];
|
|
206
|
+
if (typeof probeFn === 'function') {
|
|
207
|
+
const organConf = configuredOrgansMap[m.configKey] || configuredOrgansMap[m.organType];
|
|
208
|
+
const probeRes = await probeFn({ config: organConf, env: effectiveEnv });
|
|
209
|
+
if (probeRes && probeRes.ok === false) {
|
|
210
|
+
results.push({
|
|
211
|
+
category: 'Health Probe',
|
|
212
|
+
name: `${m.displayName} Probe`,
|
|
213
|
+
status: 'FAIL',
|
|
214
|
+
organName: m.name,
|
|
215
|
+
message: probeRes.message || 'Health probe check failed',
|
|
216
|
+
remediation: `Check ${m.displayName} configuration in siduri.config.json.`,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
results.push({
|
|
221
|
+
category: 'Health Probe',
|
|
222
|
+
name: `${m.displayName} Probe`,
|
|
223
|
+
status: 'PASS',
|
|
224
|
+
organName: m.name,
|
|
225
|
+
message: probeRes?.message || 'Operational',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
results.push({
|
|
233
|
+
category: 'Health Probe',
|
|
234
|
+
name: `${m.displayName} Probe`,
|
|
235
|
+
status: 'FAIL',
|
|
236
|
+
organName: m.name,
|
|
237
|
+
message: `Probe invocation failed: ${err.message}`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const passed = results.every((r) => r.status !== 'FAIL');
|
|
243
|
+
return {
|
|
244
|
+
instanceName: config.name || 'Siduri Instance',
|
|
245
|
+
configuredOrgans: organKeys,
|
|
246
|
+
results,
|
|
247
|
+
passed,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OrganManifest } from './manifest';
|
|
2
|
+
export interface GeneratedInstanceFiles {
|
|
3
|
+
'package.json': string;
|
|
4
|
+
'siduri.config.json': string;
|
|
5
|
+
'siduri.schema.json': string;
|
|
6
|
+
'.env.example': string;
|
|
7
|
+
'README.md': string;
|
|
8
|
+
'src/index.js': string;
|
|
9
|
+
'public/index.html': string;
|
|
10
|
+
createAssetsBodyDir?: boolean;
|
|
11
|
+
createAssetsDirs?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface InstanceGeneratorOptions {
|
|
14
|
+
name: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
selectedManifests: OrganManifest[];
|
|
17
|
+
organConfigs?: Record<string, any>;
|
|
18
|
+
coreVersion?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function generateInstanceFiles(options: InstanceGeneratorOptions): GeneratedInstanceFiles;
|