@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,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatAsJSON,
|
|
3
|
+
formatAsYAML,
|
|
4
|
+
formatAsEnvExample
|
|
5
|
+
} from './schemaFormatters.js';
|
|
6
|
+
import { ExportedSchema } from './schemaExporter.js';
|
|
7
|
+
|
|
8
|
+
describe('Schema Formatters', () => {
|
|
9
|
+
const mockSchema: ExportedSchema = {
|
|
10
|
+
name: 'testApp',
|
|
11
|
+
sections: [
|
|
12
|
+
{
|
|
13
|
+
name: 'app',
|
|
14
|
+
description: 'Application settings',
|
|
15
|
+
elements: [
|
|
16
|
+
{
|
|
17
|
+
name: 'port',
|
|
18
|
+
description: 'Server port',
|
|
19
|
+
type: 'number',
|
|
20
|
+
default: 3000,
|
|
21
|
+
example: 8080,
|
|
22
|
+
required: false,
|
|
23
|
+
sensitive: false,
|
|
24
|
+
joiValidation: {
|
|
25
|
+
type: 'number',
|
|
26
|
+
rules: [
|
|
27
|
+
{ name: 'min', args: { limit: 1 } },
|
|
28
|
+
{ name: 'max', args: { limit: 65535 } }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'apiKey',
|
|
34
|
+
description: 'API key',
|
|
35
|
+
type: 'string',
|
|
36
|
+
default: undefined,
|
|
37
|
+
example: 'sk_test_123',
|
|
38
|
+
required: true,
|
|
39
|
+
sensitive: true,
|
|
40
|
+
joiValidation: { type: 'string', flags: { presence: 'required' } }
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'database',
|
|
46
|
+
description: 'Database configuration',
|
|
47
|
+
elements: [
|
|
48
|
+
{
|
|
49
|
+
name: 'host',
|
|
50
|
+
description: 'Database host',
|
|
51
|
+
type: 'string',
|
|
52
|
+
default: 'localhost',
|
|
53
|
+
example: undefined,
|
|
54
|
+
required: false,
|
|
55
|
+
sensitive: false,
|
|
56
|
+
joiValidation: { type: 'string' }
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe('formatAsJSON', () => {
|
|
64
|
+
it('should format schema as pretty JSON', () => {
|
|
65
|
+
const result = formatAsJSON(mockSchema, true);
|
|
66
|
+
|
|
67
|
+
expect(result).toContain('"name": "testApp"');
|
|
68
|
+
expect(result).toContain('"port"');
|
|
69
|
+
expect(result).toContain('"database"');
|
|
70
|
+
expect(JSON.parse(result)).toEqual(mockSchema);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should format schema as compact JSON', () => {
|
|
74
|
+
const result = formatAsJSON(mockSchema, false);
|
|
75
|
+
|
|
76
|
+
expect(result).not.toContain('\n');
|
|
77
|
+
expect(JSON.parse(result)).toEqual(mockSchema);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('formatAsYAML', () => {
|
|
82
|
+
it('should format schema as YAML', () => {
|
|
83
|
+
const result = formatAsYAML(mockSchema);
|
|
84
|
+
|
|
85
|
+
expect(result).toContain('name: testApp');
|
|
86
|
+
expect(result).toContain('- name: port');
|
|
87
|
+
expect(result).toContain('- name: database');
|
|
88
|
+
expect(result).toBeTruthy();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('formatAsEnvExample', () => {
|
|
93
|
+
it('should format schema as .env.example without prefix', () => {
|
|
94
|
+
const result = formatAsEnvExample(mockSchema);
|
|
95
|
+
|
|
96
|
+
expect(result).toContain('# testApp Configuration');
|
|
97
|
+
expect(result).toContain('# Generated from schema export');
|
|
98
|
+
expect(result).toContain('APP_PORT=');
|
|
99
|
+
expect(result).toContain('APP_APIKEY=');
|
|
100
|
+
expect(result).toContain('DATABASE_HOST=');
|
|
101
|
+
expect(result).toContain('# Server port');
|
|
102
|
+
expect(result).toContain('# API key');
|
|
103
|
+
expect(result).toContain('# Database host');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should format schema as .env.example with prefix', () => {
|
|
107
|
+
const result = formatAsEnvExample(mockSchema, 'MYAPP');
|
|
108
|
+
|
|
109
|
+
expect(result).toContain('MYAPP_APP_PORT=');
|
|
110
|
+
expect(result).toContain('MYAPP_APP_APIKEY=');
|
|
111
|
+
expect(result).toContain('MYAPP_DATABASE_HOST=');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should include example values when available', () => {
|
|
115
|
+
const result = formatAsEnvExample(mockSchema);
|
|
116
|
+
|
|
117
|
+
expect(result).toContain('APP_PORT=8080');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should include default values when example is not available', () => {
|
|
121
|
+
const result = formatAsEnvExample(mockSchema);
|
|
122
|
+
|
|
123
|
+
expect(result).toContain('DATABASE_HOST=localhost');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should use placeholder for sensitive values', () => {
|
|
127
|
+
const result = formatAsEnvExample(mockSchema);
|
|
128
|
+
|
|
129
|
+
expect(result).toContain('APP_APIKEY=your-example-value');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should mark required fields', () => {
|
|
133
|
+
const result = formatAsEnvExample(mockSchema);
|
|
134
|
+
|
|
135
|
+
expect(result).toContain('# API key (required)');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle sections without descriptions', () => {
|
|
139
|
+
const schemaWithoutDesc: ExportedSchema = {
|
|
140
|
+
name: 'test',
|
|
141
|
+
sections: [
|
|
142
|
+
{
|
|
143
|
+
name: 'app',
|
|
144
|
+
elements: [
|
|
145
|
+
{
|
|
146
|
+
name: 'port',
|
|
147
|
+
type: 'number',
|
|
148
|
+
default: 3000,
|
|
149
|
+
required: false,
|
|
150
|
+
sensitive: false,
|
|
151
|
+
joiValidation: {}
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const result = formatAsEnvExample(schemaWithoutDesc);
|
|
159
|
+
expect(result).toContain('APP_PORT=3000');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should handle elements without descriptions', () => {
|
|
163
|
+
const schemaWithoutElementDesc: ExportedSchema = {
|
|
164
|
+
name: 'test',
|
|
165
|
+
sections: [
|
|
166
|
+
{
|
|
167
|
+
name: 'app',
|
|
168
|
+
description: 'App config',
|
|
169
|
+
elements: [
|
|
170
|
+
{
|
|
171
|
+
name: 'port',
|
|
172
|
+
type: 'number',
|
|
173
|
+
default: 3000,
|
|
174
|
+
required: false,
|
|
175
|
+
sensitive: false,
|
|
176
|
+
joiValidation: {}
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const result = formatAsEnvExample(schemaWithoutElementDesc);
|
|
184
|
+
expect(result).toContain('# App config');
|
|
185
|
+
expect(result).toContain('APP_PORT=3000');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import yaml from 'js-yaml';
|
|
2
|
+
import { ExportedSchema, ExportedElement } from './schemaExporter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Formats the schema as JSON string
|
|
6
|
+
* @param schema - The schema to format
|
|
7
|
+
* @param pretty - Whether to pretty print the JSON
|
|
8
|
+
* @returns The configuration schema as a JSON string
|
|
9
|
+
*/
|
|
10
|
+
export function formatAsJSON(
|
|
11
|
+
schema: ExportedSchema,
|
|
12
|
+
pretty: boolean = true
|
|
13
|
+
): string {
|
|
14
|
+
return JSON.stringify(schema, null, pretty ? 2 : 0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Formats the schema as YAML string
|
|
19
|
+
* @param schema - The schema to format
|
|
20
|
+
* @returns The configuration schema as a YAML string
|
|
21
|
+
*/
|
|
22
|
+
export function formatAsYAML(schema: ExportedSchema): string {
|
|
23
|
+
return yaml.dump(schema, {
|
|
24
|
+
indent: 2,
|
|
25
|
+
lineWidth: 80,
|
|
26
|
+
noRefs: true
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generates an environment variable name from section and element names
|
|
32
|
+
* @param sectionName - The name of the section
|
|
33
|
+
* @param elementName - The name of the element
|
|
34
|
+
* @param prefix - Optional prefix for the environment variable name
|
|
35
|
+
* @returns The environment variable name
|
|
36
|
+
*/
|
|
37
|
+
function generateEnvVarName(
|
|
38
|
+
sectionName: string,
|
|
39
|
+
elementName: string,
|
|
40
|
+
prefix?: string
|
|
41
|
+
): string {
|
|
42
|
+
let name = `${sectionName}_${elementName}`.replace(/\./g, '_');
|
|
43
|
+
if (prefix) {
|
|
44
|
+
name = `${prefix}_${name}`;
|
|
45
|
+
}
|
|
46
|
+
return name.toUpperCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Formats a value for display in .env.example file
|
|
51
|
+
* @param element - The element to format
|
|
52
|
+
* @returns The formatted value
|
|
53
|
+
*/
|
|
54
|
+
function formatEnvValue(element: ExportedElement): string {
|
|
55
|
+
if (element.sensitive) {
|
|
56
|
+
if (element.example !== undefined) {
|
|
57
|
+
return 'your-example-value';
|
|
58
|
+
}
|
|
59
|
+
return 'your-secret-value';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (element.example !== undefined) {
|
|
63
|
+
return String(element.example);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (element.default !== undefined) {
|
|
67
|
+
return String(element.default);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch (element.type) {
|
|
71
|
+
case 'string':
|
|
72
|
+
return 'your-value';
|
|
73
|
+
case 'number':
|
|
74
|
+
return '0';
|
|
75
|
+
case 'boolean':
|
|
76
|
+
return 'true';
|
|
77
|
+
case 'array':
|
|
78
|
+
return 'value1,value2';
|
|
79
|
+
default:
|
|
80
|
+
return 'your-value';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Formats the schema as a .env.example file
|
|
86
|
+
* @param schema - The exported schema
|
|
87
|
+
* @param prefix - Optional prefix for environment variable names (e.g., 'MYAPP')
|
|
88
|
+
* @returns The .env.example file content as a string
|
|
89
|
+
*/
|
|
90
|
+
export function formatAsEnvExample(
|
|
91
|
+
schema: ExportedSchema,
|
|
92
|
+
prefix?: string
|
|
93
|
+
): string {
|
|
94
|
+
const lines: string[] = [];
|
|
95
|
+
|
|
96
|
+
lines.push(`# ${schema.name} Configuration`);
|
|
97
|
+
lines.push('# Generated from schema export');
|
|
98
|
+
lines.push('');
|
|
99
|
+
|
|
100
|
+
for (const section of schema.sections) {
|
|
101
|
+
if (section.description) {
|
|
102
|
+
lines.push(`# ${section.description}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const element of section.elements) {
|
|
106
|
+
const envVarName = generateEnvVarName(section.name, element.name, prefix);
|
|
107
|
+
const value = formatEnvValue(element);
|
|
108
|
+
const isRequired = element.required && element.default === undefined;
|
|
109
|
+
|
|
110
|
+
if (element.description) {
|
|
111
|
+
lines.push(
|
|
112
|
+
`# ${element.description}${isRequired ? ' (required)' : ''}`
|
|
113
|
+
);
|
|
114
|
+
} else if (isRequired) {
|
|
115
|
+
lines.push('# Required');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lines.push(`${envVarName}=${value}`);
|
|
119
|
+
lines.push('');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return lines.join('\n');
|
|
124
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@config-bound/typescript-config/package.tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2024",
|
|
5
|
+
"lib": ["ES2024"],
|
|
6
|
+
"module": "NodeNext",
|
|
7
|
+
"moduleResolution": "NodeNext",
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src"
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*", "src/**/*.spec.ts"]
|
|
12
|
+
}
|