@config-bound/schema-export 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-format$colon$ci.log +6 -0
- package/.turbo/turbo-lint$colon$ci.log +4 -0
- package/.turbo/turbo-test.log +12 -0
- package/CHANGELOG.md +14 -0
- package/README.md +59 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/schemaExporter.d.ts +48 -0
- package/dist/schemaExporter.d.ts.map +1 -0
- package/dist/schemaExporter.js +66 -0
- package/dist/schemaExporter.js.map +1 -0
- package/dist/schemaExporter.spec.d.ts +2 -0
- package/dist/schemaExporter.spec.d.ts.map +1 -0
- package/dist/schemaExporter.spec.js +124 -0
- package/dist/schemaExporter.spec.js.map +1 -0
- package/dist/schemaFormatters.d.ts +22 -0
- package/dist/schemaFormatters.d.ts.map +1 -0
- package/dist/schemaFormatters.js +99 -0
- package/dist/schemaFormatters.js.map +1 -0
- package/dist/schemaFormatters.spec.d.ts +2 -0
- package/dist/schemaFormatters.spec.d.ts.map +1 -0
- package/dist/schemaFormatters.spec.js +161 -0
- package/dist/schemaFormatters.spec.js.map +1 -0
- package/eslint.config.mjs +3 -0
- package/jest.config.js +26 -0
- package/package.json +58 -0
- package/src/index.ts +14 -0
- package/src/schemaExporter.spec.ts +286 -0
- package/src/schemaExporter.ts +119 -0
- package/src/schemaFormatters.spec.ts +188 -0
- package/src/schemaFormatters.ts +124 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { formatAsJSON, formatAsYAML, formatAsEnvExample } from './schemaFormatters.js';
|
|
2
|
+
describe('Schema Formatters', () => {
|
|
3
|
+
const mockSchema = {
|
|
4
|
+
name: 'testApp',
|
|
5
|
+
sections: [
|
|
6
|
+
{
|
|
7
|
+
name: 'app',
|
|
8
|
+
description: 'Application settings',
|
|
9
|
+
elements: [
|
|
10
|
+
{
|
|
11
|
+
name: 'port',
|
|
12
|
+
description: 'Server port',
|
|
13
|
+
type: 'number',
|
|
14
|
+
default: 3000,
|
|
15
|
+
example: 8080,
|
|
16
|
+
required: false,
|
|
17
|
+
sensitive: false,
|
|
18
|
+
joiValidation: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
rules: [
|
|
21
|
+
{ name: 'min', args: { limit: 1 } },
|
|
22
|
+
{ name: 'max', args: { limit: 65535 } }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'apiKey',
|
|
28
|
+
description: 'API key',
|
|
29
|
+
type: 'string',
|
|
30
|
+
default: undefined,
|
|
31
|
+
example: 'sk_test_123',
|
|
32
|
+
required: true,
|
|
33
|
+
sensitive: true,
|
|
34
|
+
joiValidation: { type: 'string', flags: { presence: 'required' } }
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'database',
|
|
40
|
+
description: 'Database configuration',
|
|
41
|
+
elements: [
|
|
42
|
+
{
|
|
43
|
+
name: 'host',
|
|
44
|
+
description: 'Database host',
|
|
45
|
+
type: 'string',
|
|
46
|
+
default: 'localhost',
|
|
47
|
+
example: undefined,
|
|
48
|
+
required: false,
|
|
49
|
+
sensitive: false,
|
|
50
|
+
joiValidation: { type: 'string' }
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
describe('formatAsJSON', () => {
|
|
57
|
+
it('should format schema as pretty JSON', () => {
|
|
58
|
+
const result = formatAsJSON(mockSchema, true);
|
|
59
|
+
expect(result).toContain('"name": "testApp"');
|
|
60
|
+
expect(result).toContain('"port"');
|
|
61
|
+
expect(result).toContain('"database"');
|
|
62
|
+
expect(JSON.parse(result)).toEqual(mockSchema);
|
|
63
|
+
});
|
|
64
|
+
it('should format schema as compact JSON', () => {
|
|
65
|
+
const result = formatAsJSON(mockSchema, false);
|
|
66
|
+
expect(result).not.toContain('\n');
|
|
67
|
+
expect(JSON.parse(result)).toEqual(mockSchema);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('formatAsYAML', () => {
|
|
71
|
+
it('should format schema as YAML', () => {
|
|
72
|
+
const result = formatAsYAML(mockSchema);
|
|
73
|
+
expect(result).toContain('name: testApp');
|
|
74
|
+
expect(result).toContain('- name: port');
|
|
75
|
+
expect(result).toContain('- name: database');
|
|
76
|
+
expect(result).toBeTruthy();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('formatAsEnvExample', () => {
|
|
80
|
+
it('should format schema as .env.example without prefix', () => {
|
|
81
|
+
const result = formatAsEnvExample(mockSchema);
|
|
82
|
+
expect(result).toContain('# testApp Configuration');
|
|
83
|
+
expect(result).toContain('# Generated from schema export');
|
|
84
|
+
expect(result).toContain('APP_PORT=');
|
|
85
|
+
expect(result).toContain('APP_APIKEY=');
|
|
86
|
+
expect(result).toContain('DATABASE_HOST=');
|
|
87
|
+
expect(result).toContain('# Server port');
|
|
88
|
+
expect(result).toContain('# API key');
|
|
89
|
+
expect(result).toContain('# Database host');
|
|
90
|
+
});
|
|
91
|
+
it('should format schema as .env.example with prefix', () => {
|
|
92
|
+
const result = formatAsEnvExample(mockSchema, 'MYAPP');
|
|
93
|
+
expect(result).toContain('MYAPP_APP_PORT=');
|
|
94
|
+
expect(result).toContain('MYAPP_APP_APIKEY=');
|
|
95
|
+
expect(result).toContain('MYAPP_DATABASE_HOST=');
|
|
96
|
+
});
|
|
97
|
+
it('should include example values when available', () => {
|
|
98
|
+
const result = formatAsEnvExample(mockSchema);
|
|
99
|
+
expect(result).toContain('APP_PORT=8080');
|
|
100
|
+
});
|
|
101
|
+
it('should include default values when example is not available', () => {
|
|
102
|
+
const result = formatAsEnvExample(mockSchema);
|
|
103
|
+
expect(result).toContain('DATABASE_HOST=localhost');
|
|
104
|
+
});
|
|
105
|
+
it('should use placeholder for sensitive values', () => {
|
|
106
|
+
const result = formatAsEnvExample(mockSchema);
|
|
107
|
+
expect(result).toContain('APP_APIKEY=your-example-value');
|
|
108
|
+
});
|
|
109
|
+
it('should mark required fields', () => {
|
|
110
|
+
const result = formatAsEnvExample(mockSchema);
|
|
111
|
+
expect(result).toContain('# API key (required)');
|
|
112
|
+
});
|
|
113
|
+
it('should handle sections without descriptions', () => {
|
|
114
|
+
const schemaWithoutDesc = {
|
|
115
|
+
name: 'test',
|
|
116
|
+
sections: [
|
|
117
|
+
{
|
|
118
|
+
name: 'app',
|
|
119
|
+
elements: [
|
|
120
|
+
{
|
|
121
|
+
name: 'port',
|
|
122
|
+
type: 'number',
|
|
123
|
+
default: 3000,
|
|
124
|
+
required: false,
|
|
125
|
+
sensitive: false,
|
|
126
|
+
joiValidation: {}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
const result = formatAsEnvExample(schemaWithoutDesc);
|
|
133
|
+
expect(result).toContain('APP_PORT=3000');
|
|
134
|
+
});
|
|
135
|
+
it('should handle elements without descriptions', () => {
|
|
136
|
+
const schemaWithoutElementDesc = {
|
|
137
|
+
name: 'test',
|
|
138
|
+
sections: [
|
|
139
|
+
{
|
|
140
|
+
name: 'app',
|
|
141
|
+
description: 'App config',
|
|
142
|
+
elements: [
|
|
143
|
+
{
|
|
144
|
+
name: 'port',
|
|
145
|
+
type: 'number',
|
|
146
|
+
default: 3000,
|
|
147
|
+
required: false,
|
|
148
|
+
sensitive: false,
|
|
149
|
+
joiValidation: {}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
const result = formatAsEnvExample(schemaWithoutElementDesc);
|
|
156
|
+
expect(result).toContain('# App config');
|
|
157
|
+
expect(result).toContain('APP_PORT=3000');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=schemaFormatters.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaFormatters.spec.js","sourceRoot":"","sources":["../src/schemaFormatters.spec.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
2
|
+
|
|
3
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} **/
|
|
4
|
+
export default {
|
|
5
|
+
testEnvironment: 'node',
|
|
6
|
+
rootDir: './src',
|
|
7
|
+
testMatch: ['**/*.spec.ts'],
|
|
8
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
9
|
+
moduleNameMapper: {
|
|
10
|
+
'^(\\.{1,2}/.*)\\.js$': '$1'
|
|
11
|
+
},
|
|
12
|
+
transform: {
|
|
13
|
+
'^.+\\.tsx?$': [
|
|
14
|
+
'ts-jest',
|
|
15
|
+
{
|
|
16
|
+
useESM: true,
|
|
17
|
+
tsconfig: {
|
|
18
|
+
isolatedModules: false
|
|
19
|
+
},
|
|
20
|
+
diagnostics: {
|
|
21
|
+
ignoreCodes: [151002]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@config-bound/schema-export",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Schema export utilities for ConfigBound configuration library",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"clean": "rimraf dist",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"lint": "eslint src/**/*.ts --fix",
|
|
20
|
+
"lint:ci": "eslint src/**/*.ts",
|
|
21
|
+
"format": "prettier --write --config ../../.config/.prettierrc --ignore-path ../../.config/.prettierignore src/**/*.ts",
|
|
22
|
+
"format:ci": "prettier --check --config ../../.config/.prettierrc --ignore-path ../../.config/.prettierignore src/**/*.ts"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"config",
|
|
26
|
+
"configuration",
|
|
27
|
+
"schema",
|
|
28
|
+
"export",
|
|
29
|
+
"json",
|
|
30
|
+
"yaml",
|
|
31
|
+
"documentation"
|
|
32
|
+
],
|
|
33
|
+
"author": "Robert Keyser",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@config-bound/config-bound": "0.1.0",
|
|
37
|
+
"js-yaml": "^4.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@config-bound/eslint-config": "*",
|
|
41
|
+
"@types/jest": "^30.0.0",
|
|
42
|
+
"@types/js-yaml": "^4.0.9",
|
|
43
|
+
"@types/node": "^24.10.0",
|
|
44
|
+
"eslint": "^9.39.1",
|
|
45
|
+
"jest": "^30.2.0",
|
|
46
|
+
"rimraf": "^6.1.0",
|
|
47
|
+
"ts-jest": "^29.4.5",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public",
|
|
52
|
+
"registry": "https://registry.npmjs.org"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/notr-ai/ConfigBound"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
exportElement,
|
|
3
|
+
exportSection,
|
|
4
|
+
exportSchema,
|
|
5
|
+
type ExportedElement,
|
|
6
|
+
type ExportedSection,
|
|
7
|
+
type ExportedSchema
|
|
8
|
+
} from './schemaExporter.js';
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
formatAsJSON,
|
|
12
|
+
formatAsYAML,
|
|
13
|
+
formatAsEnvExample
|
|
14
|
+
} from './schemaFormatters.js';
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { Section } from '@config-bound/config-bound/section';
|
|
2
|
+
import { Element } from '@config-bound/config-bound/element';
|
|
3
|
+
import Joi from 'joi';
|
|
4
|
+
import {
|
|
5
|
+
exportElement,
|
|
6
|
+
exportSection,
|
|
7
|
+
exportSchema
|
|
8
|
+
} from './schemaExporter.js';
|
|
9
|
+
|
|
10
|
+
describe('Schema Exporter', () => {
|
|
11
|
+
describe('exportElement', () => {
|
|
12
|
+
it('should export a basic element', () => {
|
|
13
|
+
const element = new Element<string>(
|
|
14
|
+
'testElement',
|
|
15
|
+
'A test element',
|
|
16
|
+
'default-value',
|
|
17
|
+
'example-value',
|
|
18
|
+
false,
|
|
19
|
+
false,
|
|
20
|
+
Joi.string()
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const exported = exportElement(element);
|
|
24
|
+
|
|
25
|
+
expect(exported.name).toBe('testElement');
|
|
26
|
+
expect(exported.description).toBe('A test element');
|
|
27
|
+
expect(exported.type).toBe('string');
|
|
28
|
+
expect(exported.default).toBe('default-value');
|
|
29
|
+
expect(exported.example).toBe('example-value');
|
|
30
|
+
expect(exported.required).toBe(false);
|
|
31
|
+
expect(exported.sensitive).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle required elements', () => {
|
|
35
|
+
const element = new Element<number>(
|
|
36
|
+
'requiredElement',
|
|
37
|
+
'A required element',
|
|
38
|
+
undefined,
|
|
39
|
+
42,
|
|
40
|
+
false,
|
|
41
|
+
false,
|
|
42
|
+
Joi.number().required()
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const exported = exportElement(element);
|
|
46
|
+
|
|
47
|
+
expect(exported.required).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle sensitive elements', () => {
|
|
51
|
+
const element = new Element<string>(
|
|
52
|
+
'secretKey',
|
|
53
|
+
'API secret key',
|
|
54
|
+
undefined,
|
|
55
|
+
'sk_test_123',
|
|
56
|
+
true,
|
|
57
|
+
false,
|
|
58
|
+
Joi.string()
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const exported = exportElement(element);
|
|
62
|
+
|
|
63
|
+
expect(exported.sensitive).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should handle elements with omitFromSchema', () => {
|
|
67
|
+
const element = new Element<string>(
|
|
68
|
+
'privateKey',
|
|
69
|
+
'Private configuration key',
|
|
70
|
+
'default',
|
|
71
|
+
'example',
|
|
72
|
+
false,
|
|
73
|
+
true,
|
|
74
|
+
Joi.string()
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(element.omitFromSchema).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should extract joi validation object', () => {
|
|
81
|
+
const element = new Element<number>(
|
|
82
|
+
'port',
|
|
83
|
+
'Server port',
|
|
84
|
+
3000,
|
|
85
|
+
undefined,
|
|
86
|
+
false,
|
|
87
|
+
false,
|
|
88
|
+
Joi.number().min(1).max(65535).required()
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const exported = exportElement(element);
|
|
92
|
+
|
|
93
|
+
expect(exported.joiValidation).toBeDefined();
|
|
94
|
+
expect((exported.joiValidation as any).type).toBe('number');
|
|
95
|
+
expect((exported.joiValidation as any).rules).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle enum types', () => {
|
|
99
|
+
const element = new Element<string>(
|
|
100
|
+
'environment',
|
|
101
|
+
'Runtime environment',
|
|
102
|
+
'development',
|
|
103
|
+
undefined,
|
|
104
|
+
false,
|
|
105
|
+
false,
|
|
106
|
+
Joi.string().valid('development', 'staging', 'production')
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const exported = exportElement(element);
|
|
110
|
+
|
|
111
|
+
expect(exported.type).toContain('development');
|
|
112
|
+
expect(exported.type).toContain('staging');
|
|
113
|
+
expect(exported.type).toContain('production');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('exportSection', () => {
|
|
118
|
+
it('should export a section with elements', () => {
|
|
119
|
+
const elements = [
|
|
120
|
+
new Element<string>('host', 'Database host', 'localhost'),
|
|
121
|
+
new Element<number>('port', 'Database port', 5432)
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const section = new Section(
|
|
125
|
+
'database',
|
|
126
|
+
elements,
|
|
127
|
+
'Database configuration'
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const exported = exportSection(section);
|
|
131
|
+
|
|
132
|
+
expect(exported.name).toBe('database');
|
|
133
|
+
expect(exported.description).toBe('Database configuration');
|
|
134
|
+
expect(exported.elements).toHaveLength(2);
|
|
135
|
+
expect(exported.elements[0].name).toBe('host');
|
|
136
|
+
expect(exported.elements[1].name).toBe('port');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle empty sections', () => {
|
|
140
|
+
const section = new Section('empty', [], 'Empty section');
|
|
141
|
+
|
|
142
|
+
const exported = exportSection(section);
|
|
143
|
+
|
|
144
|
+
expect(exported.name).toBe('empty');
|
|
145
|
+
expect(exported.elements).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should filter out elements with omitFromSchema: true', () => {
|
|
149
|
+
const publicElement = new Element<string>(
|
|
150
|
+
'appName',
|
|
151
|
+
'Application name',
|
|
152
|
+
'myApp',
|
|
153
|
+
'exampleApp',
|
|
154
|
+
false,
|
|
155
|
+
false,
|
|
156
|
+
Joi.string()
|
|
157
|
+
);
|
|
158
|
+
const privateElement = new Element<string>(
|
|
159
|
+
'internalKey',
|
|
160
|
+
'Internal configuration key',
|
|
161
|
+
'secret',
|
|
162
|
+
'example',
|
|
163
|
+
false,
|
|
164
|
+
true,
|
|
165
|
+
Joi.string()
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const section = new Section(
|
|
169
|
+
'app',
|
|
170
|
+
[publicElement, privateElement],
|
|
171
|
+
'Application settings'
|
|
172
|
+
);
|
|
173
|
+
const exported = exportSection(section);
|
|
174
|
+
|
|
175
|
+
expect(exported.name).toBe('app');
|
|
176
|
+
expect(exported.description).toBe('Application settings');
|
|
177
|
+
expect(exported.elements).toHaveLength(1);
|
|
178
|
+
expect(exported.elements[0].name).toBe('appName');
|
|
179
|
+
expect(
|
|
180
|
+
exported.elements.find(
|
|
181
|
+
(e: { name: string }) => e.name === 'internalKey'
|
|
182
|
+
)
|
|
183
|
+
).toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should include omitted elements when includeOmitted is true', () => {
|
|
187
|
+
const publicElement = new Element<string>(
|
|
188
|
+
'appName',
|
|
189
|
+
'Application name',
|
|
190
|
+
'myApp',
|
|
191
|
+
'exampleApp',
|
|
192
|
+
false,
|
|
193
|
+
false,
|
|
194
|
+
Joi.string()
|
|
195
|
+
);
|
|
196
|
+
const privateElement = new Element<string>(
|
|
197
|
+
'internalKey',
|
|
198
|
+
'Internal configuration key',
|
|
199
|
+
'secret',
|
|
200
|
+
'example',
|
|
201
|
+
false,
|
|
202
|
+
true,
|
|
203
|
+
Joi.string()
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const section = new Section(
|
|
207
|
+
'app',
|
|
208
|
+
[publicElement, privateElement],
|
|
209
|
+
'Application settings'
|
|
210
|
+
);
|
|
211
|
+
const exported = exportSection(section, true);
|
|
212
|
+
|
|
213
|
+
expect(exported.name).toBe('app');
|
|
214
|
+
expect(exported.description).toBe('Application settings');
|
|
215
|
+
expect(exported.elements).toHaveLength(2);
|
|
216
|
+
expect(exported.elements[0].name).toBe('appName');
|
|
217
|
+
expect(exported.elements[1].name).toBe('internalKey');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('exportSchema', () => {
|
|
222
|
+
it('should export a complete schema', () => {
|
|
223
|
+
const appElements = [
|
|
224
|
+
new Element<number>('port', 'Application port', 3000),
|
|
225
|
+
new Element<string>('host', 'Application host', 'localhost')
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const dbElements = [
|
|
229
|
+
new Element<string>(
|
|
230
|
+
'connectionString',
|
|
231
|
+
'Database connection',
|
|
232
|
+
undefined
|
|
233
|
+
)
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
const sections = [
|
|
237
|
+
new Section('app', appElements, 'Application settings'),
|
|
238
|
+
new Section('database', dbElements, 'Database settings')
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
const schema = exportSchema('myApp', sections);
|
|
242
|
+
|
|
243
|
+
expect(schema.name).toBe('myApp');
|
|
244
|
+
expect(schema.sections).toHaveLength(2);
|
|
245
|
+
expect(schema.sections[0].name).toBe('app');
|
|
246
|
+
expect(schema.sections[1].name).toBe('database');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should include omitted elements when includeOmitted is true', () => {
|
|
250
|
+
const publicElement = new Element<string>(
|
|
251
|
+
'appName',
|
|
252
|
+
'Application name',
|
|
253
|
+
'myApp',
|
|
254
|
+
'exampleApp',
|
|
255
|
+
false,
|
|
256
|
+
false,
|
|
257
|
+
Joi.string()
|
|
258
|
+
);
|
|
259
|
+
const privateElement = new Element<string>(
|
|
260
|
+
'internalKey',
|
|
261
|
+
'Internal configuration key',
|
|
262
|
+
'secret',
|
|
263
|
+
'example',
|
|
264
|
+
false,
|
|
265
|
+
true,
|
|
266
|
+
Joi.string()
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const sections = [
|
|
270
|
+
new Section(
|
|
271
|
+
'app',
|
|
272
|
+
[publicElement, privateElement],
|
|
273
|
+
'Application settings'
|
|
274
|
+
)
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const schema = exportSchema('myApp', sections, true);
|
|
278
|
+
|
|
279
|
+
expect(schema.name).toBe('myApp');
|
|
280
|
+
expect(schema.sections).toHaveLength(1);
|
|
281
|
+
expect(schema.sections[0].elements).toHaveLength(2);
|
|
282
|
+
expect(schema.sections[0].elements[0].name).toBe('appName');
|
|
283
|
+
expect(schema.sections[0].elements[1].name).toBe('internalKey');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Section } from '@config-bound/config-bound/section';
|
|
2
|
+
import { Element } from '@config-bound/config-bound/element';
|
|
3
|
+
import Joi from 'joi';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a single configuration element in the exported schema
|
|
7
|
+
*/
|
|
8
|
+
export interface ExportedElement {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
type: string;
|
|
12
|
+
default?: unknown;
|
|
13
|
+
example?: unknown;
|
|
14
|
+
required: boolean;
|
|
15
|
+
sensitive: boolean;
|
|
16
|
+
joiValidation: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Represents a configuration section in the exported schema
|
|
21
|
+
*/
|
|
22
|
+
export interface ExportedSection {
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
elements: ExportedElement[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The complete exported configuration schema
|
|
30
|
+
*/
|
|
31
|
+
export interface ExportedSchema {
|
|
32
|
+
name: string;
|
|
33
|
+
sections: ExportedSection[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extracts type information from a Joi schema
|
|
38
|
+
*/
|
|
39
|
+
function extractJoiType(validator: Joi.AnySchema): string {
|
|
40
|
+
const describe = validator.describe();
|
|
41
|
+
|
|
42
|
+
if (describe.type === 'alternatives') {
|
|
43
|
+
const types =
|
|
44
|
+
describe.matches?.map(
|
|
45
|
+
(m: Joi.Description) => m.schema?.type || 'unknown'
|
|
46
|
+
) || [];
|
|
47
|
+
return types.join(' | ') || 'any';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (describe.type === 'string' && describe.allow) {
|
|
51
|
+
const allowed = describe.allow;
|
|
52
|
+
if (Array.isArray(allowed) && allowed.length > 0) {
|
|
53
|
+
return allowed.map((v) => `'${v}'`).join(' | ');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return describe.type || 'unknown';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extracts the raw Joi object from a schema
|
|
62
|
+
*/
|
|
63
|
+
function extractJoiValidation(validator: Joi.AnySchema): unknown {
|
|
64
|
+
return validator.describe();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Exports an Element to the external schema format
|
|
69
|
+
*/
|
|
70
|
+
export function exportElement(element: Element<unknown>): ExportedElement {
|
|
71
|
+
return {
|
|
72
|
+
name: element.name,
|
|
73
|
+
description: element.description,
|
|
74
|
+
type: extractJoiType(element.validator),
|
|
75
|
+
default: element.default,
|
|
76
|
+
example: element.example,
|
|
77
|
+
required: element.isRequired(),
|
|
78
|
+
sensitive: element.sensitive,
|
|
79
|
+
joiValidation: extractJoiValidation(element.validator)
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Exports a Section to the external schema format
|
|
85
|
+
* @param section - The section to export
|
|
86
|
+
* @param includeOmitted - Whether to include elements marked with omitFromSchema (default: false)
|
|
87
|
+
*/
|
|
88
|
+
export function exportSection(
|
|
89
|
+
section: Section,
|
|
90
|
+
includeOmitted: boolean = false
|
|
91
|
+
): ExportedSection {
|
|
92
|
+
return {
|
|
93
|
+
name: section.name,
|
|
94
|
+
description: section.description,
|
|
95
|
+
elements: section
|
|
96
|
+
.getElements()
|
|
97
|
+
.filter(
|
|
98
|
+
(element: Element<unknown>) => includeOmitted || !element.omitFromSchema
|
|
99
|
+
)
|
|
100
|
+
.map(exportElement)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Exports the complete schema to a structured format
|
|
106
|
+
* @param name - The name of the configuration
|
|
107
|
+
* @param sections - Array of sections to export
|
|
108
|
+
* @param includeOmitted - Whether to include elements marked with omitFromSchema (default: false)
|
|
109
|
+
*/
|
|
110
|
+
export function exportSchema(
|
|
111
|
+
name: string,
|
|
112
|
+
sections: Section[],
|
|
113
|
+
includeOmitted: boolean = false
|
|
114
|
+
): ExportedSchema {
|
|
115
|
+
return {
|
|
116
|
+
name,
|
|
117
|
+
sections: sections.map((section) => exportSection(section, includeOmitted))
|
|
118
|
+
};
|
|
119
|
+
}
|