@contractkit/plugin-bruno 1.0.0 → 1.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$colon$ci.log +7 -7
- package/.turbo/turbo-test$colon$ci.log +8 -8
- package/CHANGELOG.md +21 -0
- package/README.md +30 -0
- package/coverage/clover.xml +286 -278
- package/coverage/coverage-final.json +2 -2
- package/coverage/index.html +19 -19
- package/coverage/src/codegen-bruno.ts.html +208 -109
- package/coverage/src/index.html +19 -19
- package/coverage/tests/helpers.ts.html +9 -9
- package/coverage/tests/index.html +1 -1
- package/dist/codegen-bruno.d.ts +11 -0
- package/dist/codegen-bruno.d.ts.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen-bruno.ts +40 -7
- package/src/index.ts +11 -0
- package/tests/codegen-bruno.test.ts +55 -0
package/src/codegen-bruno.ts
CHANGED
|
@@ -55,6 +55,17 @@ export interface OpenCollectionOptions {
|
|
|
55
55
|
* benefit from full coverage. Set to `false` to omit internal ops.
|
|
56
56
|
*/
|
|
57
57
|
includeInternal?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Map of environment name → variables. Each entry produces a
|
|
60
|
+
* `environments/<name>.yml` file with the variables in declaration order.
|
|
61
|
+
* Values are coerced to strings.
|
|
62
|
+
*
|
|
63
|
+
* When omitted, a default `environments/local.yml` is emitted with
|
|
64
|
+
* `baseUrl=http://localhost:3000` plus any auth env-var placeholders the
|
|
65
|
+
* default scheme requires. When provided, the default is replaced entirely
|
|
66
|
+
* — auth vars are not auto-injected, so include them explicitly if needed.
|
|
67
|
+
*/
|
|
68
|
+
environments?: Record<string, Record<string, unknown>>;
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
/**
|
|
@@ -72,7 +83,9 @@ export function generateOpenCollection(roots: OpRootNode[], options: OpenCollect
|
|
|
72
83
|
const includeInternal = options.includeInternal ?? true;
|
|
73
84
|
|
|
74
85
|
files.push({ relativePath: 'opencollection.yml', content: generateCollectionRoot(options.collectionName, defaultScheme) });
|
|
75
|
-
|
|
86
|
+
for (const envFile of generateEnvFiles(options.environments, defaultScheme)) {
|
|
87
|
+
files.push(envFile);
|
|
88
|
+
}
|
|
76
89
|
// Manifest is appended at the end so it lists every generated path including itself.
|
|
77
90
|
|
|
78
91
|
for (let rootIdx = 0; rootIdx < roots.length; rootIdx++) {
|
|
@@ -183,18 +196,38 @@ function generateCollectionRoot(name: string, scheme?: BrunoSecurityScheme): str
|
|
|
183
196
|
return lines.join('\n');
|
|
184
197
|
}
|
|
185
198
|
|
|
186
|
-
function
|
|
187
|
-
|
|
199
|
+
function generateEnvFiles(
|
|
200
|
+
environments: Record<string, Record<string, unknown>> | undefined,
|
|
201
|
+
scheme: BrunoSecurityScheme | undefined,
|
|
202
|
+
): OpenCollectionFile[] {
|
|
203
|
+
if (environments && Object.keys(environments).length > 0) {
|
|
204
|
+
return Object.entries(environments).map(([name, variables]) => ({
|
|
205
|
+
relativePath: `environments/${name}.yml`,
|
|
206
|
+
content: renderEnvFile(displayNameFor(name), variables),
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
const defaultVars: Record<string, string> = { baseUrl: 'http://localhost:3000' };
|
|
188
210
|
if (scheme) {
|
|
189
|
-
for (const varName of authEnvVarNames(scheme))
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
211
|
+
for (const varName of authEnvVarNames(scheme)) defaultVars[varName] = '';
|
|
212
|
+
}
|
|
213
|
+
return [{ relativePath: 'environments/local.yml', content: renderEnvFile('Local', defaultVars) }];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function renderEnvFile(name: string, variables: Record<string, unknown>): string {
|
|
217
|
+
const lines = [`name: ${name}`, `variables:`];
|
|
218
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
219
|
+
const escaped = String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
220
|
+
lines.push(` - name: ${key}`);
|
|
221
|
+
lines.push(` value: "${escaped}"`);
|
|
193
222
|
}
|
|
194
223
|
lines.push(``);
|
|
195
224
|
return lines.join('\n');
|
|
196
225
|
}
|
|
197
226
|
|
|
227
|
+
function displayNameFor(envKey: string): string {
|
|
228
|
+
return envKey.charAt(0).toUpperCase() + envKey.slice(1);
|
|
229
|
+
}
|
|
230
|
+
|
|
198
231
|
function generateFolderFile(name: string, seq: number): string {
|
|
199
232
|
return [`info:`, ` name: ${yamlString(name)}`, ` type: folder`, ` seq: ${seq}`, ``].join('\n');
|
|
200
233
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,14 @@ export interface BrunoPluginConfig {
|
|
|
21
21
|
* benefit from full coverage. Set to `false` to omit internal ops.
|
|
22
22
|
*/
|
|
23
23
|
includeInternal?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Map of environment name → variables. Each entry produces a
|
|
26
|
+
* `environments/<name>.yml` file. When omitted, a default `local.yml` is
|
|
27
|
+
* emitted with `baseUrl=http://localhost:3000` and any auth env-var
|
|
28
|
+
* placeholders. When provided, the default is replaced entirely; include
|
|
29
|
+
* auth variables (e.g. `token`) explicitly if you need them.
|
|
30
|
+
*/
|
|
31
|
+
environments?: Record<string, Record<string, unknown>>;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
/** Full plugin options shape read from `ctx.options` — extends {@link BrunoPluginConfig} with the `auth` block. */
|
|
@@ -47,6 +55,7 @@ const plugin: ContractKitPlugin = {
|
|
|
47
55
|
auth,
|
|
48
56
|
randomExamples: config.randomExamples ?? true,
|
|
49
57
|
includeInternal: config.includeInternal,
|
|
58
|
+
environments: config.environments,
|
|
50
59
|
});
|
|
51
60
|
for (const { relativePath, content } of files) {
|
|
52
61
|
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
@@ -88,6 +97,8 @@ export function createBrunoPlugin(
|
|
|
88
97
|
contractRoots,
|
|
89
98
|
auth,
|
|
90
99
|
randomExamples: config.randomExamples ?? true,
|
|
100
|
+
includeInternal: config.includeInternal,
|
|
101
|
+
environments: config.environments,
|
|
91
102
|
});
|
|
92
103
|
for (const { relativePath, content } of files) {
|
|
93
104
|
ctx.emitFile(resolve(outDir, relativePath), content);
|
|
@@ -671,6 +671,61 @@ describe('generateOpenCollection', () => {
|
|
|
671
671
|
expect(env!.content).toContain('- name: token');
|
|
672
672
|
});
|
|
673
673
|
|
|
674
|
+
describe('environments config', () => {
|
|
675
|
+
const root = opRoot([opRoute('/users', [opOperation('get')])], 'users.op');
|
|
676
|
+
|
|
677
|
+
it('emits one file per entry, replacing the default local.yml', () => {
|
|
678
|
+
const files = generateOpenCollection([root], {
|
|
679
|
+
collectionName: 'API',
|
|
680
|
+
environments: {
|
|
681
|
+
local: { baseUrl: 'http://localhost:3000', token: '' },
|
|
682
|
+
staging: { baseUrl: 'https://staging.example.com', token: 'secret' },
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
const local = files.find(f => f.relativePath === 'environments/local.yml');
|
|
686
|
+
const staging = files.find(f => f.relativePath === 'environments/staging.yml');
|
|
687
|
+
expect(local).toBeDefined();
|
|
688
|
+
expect(staging).toBeDefined();
|
|
689
|
+
expect(local!.content).toContain('name: Local');
|
|
690
|
+
expect(local!.content).toContain('- name: baseUrl');
|
|
691
|
+
expect(local!.content).toContain('value: "http://localhost:3000"');
|
|
692
|
+
expect(local!.content).toContain('- name: token');
|
|
693
|
+
expect(staging!.content).toContain('name: Staging');
|
|
694
|
+
expect(staging!.content).toContain('value: "https://staging.example.com"');
|
|
695
|
+
expect(staging!.content).toContain('value: "secret"');
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it('does not auto-inject auth env vars when environments is provided', () => {
|
|
699
|
+
const files = generateOpenCollection([root], {
|
|
700
|
+
collectionName: 'API',
|
|
701
|
+
auth: bearerAuth,
|
|
702
|
+
environments: {
|
|
703
|
+
local: { baseUrl: 'http://localhost:3000' },
|
|
704
|
+
},
|
|
705
|
+
});
|
|
706
|
+
const local = files.find(f => f.relativePath === 'environments/local.yml');
|
|
707
|
+
expect(local!.content).not.toContain('- name: token');
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it('falls back to the default local.yml when environments is empty', () => {
|
|
711
|
+
const files = generateOpenCollection([root], { collectionName: 'API', environments: {} });
|
|
712
|
+
const local = files.find(f => f.relativePath === 'environments/local.yml');
|
|
713
|
+
expect(local).toBeDefined();
|
|
714
|
+
expect(local!.content).toContain('name: Local');
|
|
715
|
+
expect(local!.content).toContain('value: "http://localhost:3000"');
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('coerces non-string values to strings', () => {
|
|
719
|
+
const files = generateOpenCollection([root], {
|
|
720
|
+
collectionName: 'API',
|
|
721
|
+
environments: { local: { port: 3000, debug: true } as unknown as Record<string, unknown> },
|
|
722
|
+
});
|
|
723
|
+
const local = files.find(f => f.relativePath === 'environments/local.yml');
|
|
724
|
+
expect(local!.content).toContain('value: "3000"');
|
|
725
|
+
expect(local!.content).toContain('value: "true"');
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
674
729
|
it('does not add request or auth to opencollection.yml when no security config', () => {
|
|
675
730
|
const root = opRoot([opRoute('/users', [opOperation('get')])], 'users.op');
|
|
676
731
|
const files = generateOpenCollection([root], { collectionName: 'API' });
|