@usebruno/filestore 0.9.0 → 0.11.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/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/workers/worker-script.js +1 -1
- package/dist/cjs/workers/worker-script.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/workers/worker-script.js +1 -1
- package/dist/esm/workers/worker-script.js.map +1 -1
- package/dist/formats/bru/index.d.ts.map +1 -1
- package/dist/formats/bru/utils/redact-large-text-blocks.d.ts +11 -0
- package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -0
- package/dist/formats/yml/common/app.d.ts +8 -0
- package/dist/formats/yml/common/app.d.ts.map +1 -0
- package/dist/formats/yml/common/auth.d.ts.map +1 -1
- package/dist/formats/yml/common/body.d.ts.map +1 -1
- package/dist/formats/yml/common/datatype.d.ts +14 -0
- package/dist/formats/yml/common/datatype.d.ts.map +1 -0
- package/dist/formats/yml/common/headers.d.ts.map +1 -1
- package/dist/formats/yml/common/variables.d.ts.map +1 -1
- package/dist/formats/yml/items/parseApp.d.ts +13 -0
- package/dist/formats/yml/items/parseApp.d.ts.map +1 -0
- package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/parseGrpcRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/parseWebsocketRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyApp.d.ts +4 -0
- package/dist/formats/yml/items/stringifyApp.d.ts.map +1 -0
- package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyGrpcRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyHttpRequest.d.ts.map +1 -1
- package/dist/formats/yml/items/stringifyWebsocketRequest.d.ts.map +1 -1
- package/dist/formats/yml/parseCollection.d.ts.map +1 -1
- package/dist/formats/yml/parseEnvironment.d.ts +3 -1
- package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
- package/dist/formats/yml/parseFolder.d.ts.map +1 -1
- package/dist/formats/yml/parseItem.d.ts.map +1 -1
- package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
- package/dist/formats/yml/stringifyEnvironment.d.ts +3 -1
- package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
- package/dist/formats/yml/stringifyItem.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/secretsJsonToEnvironments.d.ts +38 -0
- package/dist/utils/secretsJsonToEnvironments.d.ts.map +1 -0
- package/package.json +7 -5
- package/src/constants.ts +9 -0
- package/src/formats/bru/index.ts +52 -0
- package/src/formats/bru/tests/parseBruEnvironment.spec.js +63 -0
- package/src/formats/bru/tests/redact-large-text-blocks.spec.js +313 -0
- package/src/formats/bru/utils/redact-large-text-blocks.ts +102 -0
- package/src/formats/yml/common/actions.ts +1 -1
- package/src/formats/yml/common/app.ts +27 -0
- package/src/formats/yml/common/auth.ts +53 -1
- package/src/formats/yml/common/body.spec.ts +62 -0
- package/src/formats/yml/common/body.ts +27 -9
- package/src/formats/yml/common/datatype.spec.ts +110 -0
- package/src/formats/yml/common/datatype.ts +57 -0
- package/src/formats/yml/common/headers.ts +4 -0
- package/src/formats/yml/common/variables.spec.ts +127 -0
- package/src/formats/yml/common/variables.ts +20 -6
- package/src/formats/yml/items/parseApp.ts +37 -0
- package/src/formats/yml/items/parseGraphQLRequest.ts +8 -0
- package/src/formats/yml/items/parseGrpcRequest.ts +24 -3
- package/src/formats/yml/items/parseHttpRequest.ts +13 -0
- package/src/formats/yml/items/parseWebsocketRequest.ts +29 -9
- package/src/formats/yml/items/stringifyApp.ts +45 -0
- package/src/formats/yml/items/stringifyGraphQLRequest.ts +3 -0
- package/src/formats/yml/items/stringifyGrpcRequest.ts +9 -11
- package/src/formats/yml/items/stringifyHttpRequest.ts +10 -0
- package/src/formats/yml/items/stringifyWebsocketRequest.ts +22 -9
- package/src/formats/yml/parseCollection.spec.ts +70 -0
- package/src/formats/yml/parseCollection.ts +8 -3
- package/src/formats/yml/parseEnvironment.spec.ts +99 -0
- package/src/formats/yml/parseEnvironment.ts +69 -5
- package/src/formats/yml/parseFolder.spec.ts +52 -0
- package/src/formats/yml/parseFolder.ts +14 -21
- package/src/formats/yml/parseItem.spec.ts +147 -0
- package/src/formats/yml/parseItem.ts +4 -0
- package/src/formats/yml/stringifyCollection.spec.ts +77 -0
- package/src/formats/yml/stringifyCollection.ts +4 -0
- package/src/formats/yml/stringifyEnvironment.spec.ts +91 -0
- package/src/formats/yml/stringifyEnvironment.ts +59 -10
- package/src/formats/yml/stringifyFolder.spec.ts +47 -0
- package/src/formats/yml/stringifyItem.spec.ts +53 -0
- package/src/formats/yml/stringifyItem.ts +4 -0
- package/src/formats/yml/tests/fixtures/presets/empty-request.yml +7 -0
- package/src/formats/yml/tests/fixtures/presets/no-presets.yml +3 -0
- package/src/formats/yml/tests/fixtures/presets/no-request-key.yml +6 -0
- package/src/formats/yml/tests/fixtures/presets/type-only-realistic.yml +24 -0
- package/src/formats/yml/tests/fixtures/presets/url-only-realistic.yml +25 -0
- package/src/formats/yml/tests/fixtures/presets/with-type-and-url.yml +9 -0
- package/src/formats/yml/tests/parseCollection.spec.js +63 -0
- package/src/formats/yml/tests/parseEnvironment.spec.js +122 -0
- package/src/formats/yml/tests/parseEnvironment.spec.ts +160 -0
- package/src/formats/yml/tests/stringifyEnvironment.spec.ts +204 -0
- package/src/formats/yml/tests/variables.spec.js +34 -0
- package/src/index.ts +5 -0
- package/src/types/bruno-lang.d.ts +1 -0
- package/src/types.ts +7 -0
- package/src/utils/secretsJsonToEnvironments.spec.ts +211 -0
- package/src/utils/secretsJsonToEnvironments.ts +129 -0
- package/dist/formats/bru/tests/example-status-swap.spec.d.ts +0 -2
- package/dist/formats/bru/tests/example-status-swap.spec.d.ts.map +0 -1
- package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts +0 -57
- package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts.map +0 -1
- package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts +0 -2
- package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts.map +0 -1
- package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts +0 -2
- package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts.map +0 -1
|
@@ -10,6 +10,7 @@ import parseGraphQLRequest from './items/parseGraphQLRequest';
|
|
|
10
10
|
import parseGrpcRequest from './items/parseGrpcRequest';
|
|
11
11
|
import parseWebsocketRequest from './items/parseWebsocketRequest';
|
|
12
12
|
import parseScript from './items/parseScript';
|
|
13
|
+
import parseApp, { type AppFile } from './items/parseApp';
|
|
13
14
|
|
|
14
15
|
// Helper to get the type from an item (now in info block)
|
|
15
16
|
const getItemType = (item: Item): string | undefined => {
|
|
@@ -87,6 +88,9 @@ const parseItem = (ymlString: string): BrunoItem => {
|
|
|
87
88
|
case 'script':
|
|
88
89
|
return parseScript(ocItem as ScriptFile);
|
|
89
90
|
|
|
91
|
+
case 'app':
|
|
92
|
+
return parseApp(ocItem as unknown as AppFile);
|
|
93
|
+
|
|
90
94
|
case 'folder':
|
|
91
95
|
throw new Error('Folder items should be handled separately using parseFolder');
|
|
92
96
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import stringifyCollection from './stringifyCollection';
|
|
2
|
+
import parseCollection from './parseCollection';
|
|
3
|
+
|
|
4
|
+
// Typed collection-root vars serialize to OC's `{ type, data }` struct.
|
|
5
|
+
// `dataType: 'string'` is the implicit default and stays a raw string.
|
|
6
|
+
|
|
7
|
+
describe('stringifyCollection — typed request.variables', () => {
|
|
8
|
+
it('round-trips typed values and omits a typed struct for the implicit string default', () => {
|
|
9
|
+
const collectionRoot = {
|
|
10
|
+
meta: null,
|
|
11
|
+
request: {
|
|
12
|
+
headers: [],
|
|
13
|
+
auth: { mode: 'none' },
|
|
14
|
+
script: { req: null, res: null },
|
|
15
|
+
tests: null,
|
|
16
|
+
vars: {
|
|
17
|
+
req: [
|
|
18
|
+
{ uid: 'v1', name: 'count', value: 42, enabled: true, dataType: 'number' },
|
|
19
|
+
{ uid: 'v2', name: 'enabled', value: true, enabled: true, dataType: 'boolean' },
|
|
20
|
+
{ uid: 'v3', name: 'config', value: { a: 1 }, enabled: true, dataType: 'object' },
|
|
21
|
+
{ uid: 'v4', name: 'greeting', value: 'hi', enabled: true, dataType: 'string' },
|
|
22
|
+
{ uid: 'v5', name: 'plain', value: 'hello', enabled: true }
|
|
23
|
+
],
|
|
24
|
+
res: []
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
docs: null
|
|
28
|
+
} as any;
|
|
29
|
+
|
|
30
|
+
const brunoConfig = { name: 'c' };
|
|
31
|
+
|
|
32
|
+
const yml = stringifyCollection(collectionRoot, brunoConfig);
|
|
33
|
+
|
|
34
|
+
// `type: string` is never written out.
|
|
35
|
+
expect(yml).not.toMatch(/type:\s*string/);
|
|
36
|
+
|
|
37
|
+
const { collectionRoot: reparsed } = parseCollection(yml);
|
|
38
|
+
const reqVars = reparsed.request!.vars!.req!;
|
|
39
|
+
|
|
40
|
+
expect(reqVars).toHaveLength(5);
|
|
41
|
+
expect(reqVars[0]).toMatchObject({ name: 'count', value: 42, dataType: 'number' });
|
|
42
|
+
expect(reqVars[1]).toMatchObject({ name: 'enabled', value: true, dataType: 'boolean' });
|
|
43
|
+
expect(reqVars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
44
|
+
expect(reqVars[3]).toMatchObject({ name: 'greeting', value: 'hi' });
|
|
45
|
+
expect(reqVars[3].dataType).toBeUndefined();
|
|
46
|
+
expect(reqVars[4]).toMatchObject({ name: 'plain', value: 'hello' });
|
|
47
|
+
expect(reqVars[4].dataType).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('stringifyCollection — writing the collection version', () => {
|
|
52
|
+
const baseRoot = {
|
|
53
|
+
meta: null,
|
|
54
|
+
request: { headers: [], auth: { mode: 'none' }, script: { req: null, res: null }, tests: null, vars: { req: [], res: [] } },
|
|
55
|
+
docs: null
|
|
56
|
+
} as any;
|
|
57
|
+
|
|
58
|
+
it('writes the version as-is and reads back the same value', () => {
|
|
59
|
+
const yml = stringifyCollection(baseRoot, { name: 'c', version: '2' });
|
|
60
|
+
expect(yml).toMatch(/version:\s*['"]?2['"]?/);
|
|
61
|
+
expect(parseCollection(yml).brunoConfig.version).toBe('2');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('writes no version when the collection has none', () => {
|
|
65
|
+
const yml = stringifyCollection(baseRoot, { name: 'c' });
|
|
66
|
+
expect(parseCollection(yml).brunoConfig.version).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('accepts any version text — there is no check on the format', () => {
|
|
70
|
+
// The version is free-form: users can set whatever they like. Each of these should be
|
|
71
|
+
// saved and read back exactly the same, without changing the format or rejecting it.
|
|
72
|
+
['v1.0.0', '2', '1.2.3-beta', '2024.01.15', 'release-2024-Q3', 'anything-goes'].forEach((version) => {
|
|
73
|
+
const yml = stringifyCollection(baseRoot, { name: 'c', version });
|
|
74
|
+
expect(parseCollection(yml).brunoConfig.version).toBe(version);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -73,6 +73,10 @@ const stringifyCollection = (collectionRoot: any, brunoConfig: any): string => {
|
|
|
73
73
|
name: brunoConfig.name || 'Untitled Collection'
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
if (brunoConfig.version != null && brunoConfig.version !== '') {
|
|
77
|
+
oc.info.version = String(brunoConfig.version);
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
// collection config
|
|
77
81
|
if (hasCollectionConfig(brunoConfig)) {
|
|
78
82
|
oc.config = {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import stringifyEnvironment, { toOpenCollectionEnvironmentVariables } from './stringifyEnvironment';
|
|
2
|
+
import parseEnvironment from './parseEnvironment';
|
|
3
|
+
|
|
4
|
+
describe('toOpenCollectionEnvironmentVariables', () => {
|
|
5
|
+
it('returns undefined for null / empty input', () => {
|
|
6
|
+
expect(toOpenCollectionEnvironmentVariables([])).toBeUndefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('serializes plain string variables as raw strings', () => {
|
|
10
|
+
const out = toOpenCollectionEnvironmentVariables([
|
|
11
|
+
{ uid: 'u1', name: 'apiKey', value: 'abc', type: 'text', enabled: true, secret: false } as any
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
expect(out).toEqual([{ name: 'apiKey', value: 'abc' }]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('serializes typed variables as a {type, data} struct', () => {
|
|
18
|
+
const out = toOpenCollectionEnvironmentVariables([
|
|
19
|
+
{ uid: 'u1', name: 'port', value: 300, type: 'text', enabled: true, secret: false, dataType: 'number' } as any,
|
|
20
|
+
{ uid: 'u2', name: 'flag', value: true, type: 'text', enabled: true, secret: false, dataType: 'boolean' } as any,
|
|
21
|
+
{ uid: 'u3', name: 'config', value: { a: 1 }, type: 'text', enabled: true, secret: false, dataType: 'object' } as any
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
expect(out).toEqual([
|
|
25
|
+
{ name: 'port', value: { type: 'number', data: '300' } },
|
|
26
|
+
{ name: 'flag', value: { type: 'boolean', data: 'true' } },
|
|
27
|
+
{ name: 'config', value: { type: 'object', data: '{\n "a": 1\n}' } }
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('serializes secret variables without a value but preserves the dataType as `type`', () => {
|
|
32
|
+
const out = toOpenCollectionEnvironmentVariables([
|
|
33
|
+
{ uid: 'u1', name: 'apiKey', value: '', type: 'text', enabled: true, secret: true } as any,
|
|
34
|
+
{ uid: 'u2', name: 'flag', value: '', type: 'text', enabled: false, secret: true, dataType: 'number' } as any
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
expect(out).toEqual([
|
|
38
|
+
{ secret: true, name: 'apiKey' },
|
|
39
|
+
{ secret: true, name: 'flag', type: 'number', disabled: true }
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('marks disabled non-secret variables', () => {
|
|
44
|
+
const out = toOpenCollectionEnvironmentVariables([
|
|
45
|
+
{ uid: 'u1', name: 'apiKey', value: 'abc', type: 'text', enabled: false, secret: false } as any
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
expect(out).toEqual([{ name: 'apiKey', value: 'abc', disabled: true }]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('stringifyEnvironment', () => {
|
|
53
|
+
it('round-trips through parseEnvironment for typed and secret variables', () => {
|
|
54
|
+
const env = {
|
|
55
|
+
uid: 'env-uid',
|
|
56
|
+
name: 'test_env',
|
|
57
|
+
variables: [
|
|
58
|
+
{ uid: 'u1', name: 'env_str', value: 'hello', type: 'text', enabled: true, secret: false },
|
|
59
|
+
{ uid: 'u2', name: 'env_num', value: 300, type: 'text', enabled: true, secret: false, dataType: 'number' as const },
|
|
60
|
+
{ uid: 'u3', name: 'env_bool', value: true, type: 'text', enabled: true, secret: false, dataType: 'boolean' as const },
|
|
61
|
+
{ uid: 'u4', name: 'env_obj', value: { scope: 'env' }, type: 'text', enabled: true, secret: false, dataType: 'object' as const },
|
|
62
|
+
{ uid: 'u5', name: 'env_secret', value: '', type: 'text', enabled: true, secret: true },
|
|
63
|
+
{ uid: 'u6', name: 'env_secret_num', value: '', type: 'text', enabled: true, secret: true, dataType: 'number' as const }
|
|
64
|
+
]
|
|
65
|
+
} as any;
|
|
66
|
+
|
|
67
|
+
const yml = stringifyEnvironment(env);
|
|
68
|
+
const reparsed = parseEnvironment(yml);
|
|
69
|
+
|
|
70
|
+
expect(reparsed.name).toBe('test_env');
|
|
71
|
+
expect(reparsed.variables[0]).toMatchObject({ name: 'env_str', value: 'hello', secret: false });
|
|
72
|
+
expect(reparsed.variables[1]).toMatchObject({ name: 'env_num', value: 300, dataType: 'number', secret: false });
|
|
73
|
+
expect(reparsed.variables[2]).toMatchObject({ name: 'env_bool', value: true, dataType: 'boolean', secret: false });
|
|
74
|
+
expect(reparsed.variables[3]).toMatchObject({ name: 'env_obj', value: { scope: 'env' }, dataType: 'object', secret: false });
|
|
75
|
+
expect(reparsed.variables[4]).toMatchObject({ name: 'env_secret', secret: true, value: '' });
|
|
76
|
+
expect(reparsed.variables[4].dataType).toBeUndefined();
|
|
77
|
+
expect(reparsed.variables[5]).toMatchObject({ name: 'env_secret_num', secret: true, value: '', dataType: 'number' });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('preserves the color field when present', () => {
|
|
81
|
+
const env = {
|
|
82
|
+
uid: 'env-uid',
|
|
83
|
+
name: 'colorful',
|
|
84
|
+
color: '#ff0000',
|
|
85
|
+
variables: []
|
|
86
|
+
} as any;
|
|
87
|
+
|
|
88
|
+
const reparsed = parseEnvironment(stringifyEnvironment(env));
|
|
89
|
+
expect(reparsed.color).toBe('#ff0000');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -1,46 +1,89 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Environment as BrunoEnvironment,
|
|
3
|
+
EnvironmentVariable as BrunoEnvironmentVariable,
|
|
4
|
+
SecretEnvironmentVariable as BrunoSecretEnvironmentVariable,
|
|
5
|
+
ExternalSecretProviderType as BrunoExternalSecretProviderType
|
|
6
|
+
} from '@usebruno/schema-types/collection/environment';
|
|
2
7
|
import type { Environment } from '@opencollection/types/config/environments';
|
|
3
8
|
import type { Variable, SecretVariable } from '@opencollection/types/common/variables';
|
|
4
9
|
import { stringifyYml } from './utils';
|
|
10
|
+
import { ensureString } from '../../utils';
|
|
11
|
+
import { SECRET_PROVIDER_IDENTIFIER_FIELDS } from '../../constants';
|
|
12
|
+
import { hasTypedMetadata, toOpenCollectionTypedValue, serializeVariableValue } from './common/datatype';
|
|
5
13
|
|
|
6
|
-
const toOpenCollectionEnvironmentVariables = (variables: BrunoEnvironmentVariable[]): (Variable | SecretVariable)[] | undefined => {
|
|
14
|
+
export const toOpenCollectionEnvironmentVariables = (variables: (BrunoEnvironmentVariable | BrunoSecretEnvironmentVariable)[]): (Variable | SecretVariable)[] | undefined => {
|
|
7
15
|
if (!variables?.length) {
|
|
8
16
|
return undefined;
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
const ocVariables: (Variable | SecretVariable)[] = variables
|
|
12
|
-
.
|
|
13
|
-
|
|
14
|
-
// update this when bruno app supports non-string values
|
|
15
|
-
return typeof v.value === 'string';
|
|
16
|
-
})
|
|
17
|
-
.map((v: BrunoEnvironmentVariable): Variable | SecretVariable => {
|
|
18
|
-
if (v.secret === true) {
|
|
20
|
+
.map((v): Variable | SecretVariable => {
|
|
21
|
+
if ('secret' in v && v.secret === true) {
|
|
19
22
|
const secretVar: SecretVariable = {
|
|
20
23
|
secret: true,
|
|
21
24
|
name: v.name || ''
|
|
22
25
|
};
|
|
26
|
+
if (hasTypedMetadata(v)) {
|
|
27
|
+
secretVar.type = v.dataType;
|
|
28
|
+
}
|
|
23
29
|
if (v.enabled === false) {
|
|
24
30
|
secretVar.disabled = true;
|
|
25
31
|
}
|
|
32
|
+
if (v.description !== undefined) {
|
|
33
|
+
secretVar.description = v.description;
|
|
34
|
+
}
|
|
26
35
|
return secretVar;
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
const valueStr = serializeVariableValue(v.value);
|
|
39
|
+
|
|
29
40
|
const variable: Variable = {
|
|
30
41
|
name: v.name || '',
|
|
31
|
-
value: v
|
|
42
|
+
value: hasTypedMetadata(v) ? toOpenCollectionTypedValue(v, valueStr) : valueStr
|
|
32
43
|
};
|
|
33
44
|
|
|
34
45
|
if (v.enabled === false) {
|
|
35
46
|
variable.disabled = true;
|
|
36
47
|
}
|
|
37
48
|
|
|
49
|
+
if (v.description !== undefined) {
|
|
50
|
+
variable.description = v.description;
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
return variable;
|
|
39
54
|
});
|
|
40
55
|
|
|
41
56
|
return ocVariables.length > 0 ? ocVariables : undefined;
|
|
42
57
|
};
|
|
43
58
|
|
|
59
|
+
// Serializes the runtime externalSecrets block to yml `{ type, variables: [{ name,
|
|
60
|
+
// path|secretName|vaultName, disabled? }] }`. Runtime-only fields (uid, value,
|
|
61
|
+
// providerConfig, lastSyncedAt) are never written.
|
|
62
|
+
const toOpenCollectionExternalSecrets = (externalSecrets: BrunoEnvironment['externalSecrets']): { type: string; variables: Record<string, string | boolean>[] } | undefined => {
|
|
63
|
+
const type = externalSecrets?.type as BrunoExternalSecretProviderType | undefined;
|
|
64
|
+
const identifierField = type && SECRET_PROVIDER_IDENTIFIER_FIELDS[type];
|
|
65
|
+
if (!type || !identifierField) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Keep any named entry, even one with an empty identifier — a half-filled secret row is the user's
|
|
70
|
+
// data; dropping it here would silently delete it on the next save.
|
|
71
|
+
const variables = (externalSecrets?.variables || [])
|
|
72
|
+
.filter((v: any) => v && v.name)
|
|
73
|
+
.map((v: any) => {
|
|
74
|
+
const secret: Record<string, string | boolean> = {
|
|
75
|
+
name: v.name,
|
|
76
|
+
[identifierField]: ensureString(v[identifierField])
|
|
77
|
+
};
|
|
78
|
+
if (v.enabled === false) {
|
|
79
|
+
secret.disabled = true;
|
|
80
|
+
}
|
|
81
|
+
return secret;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return { type, variables };
|
|
85
|
+
};
|
|
86
|
+
|
|
44
87
|
const stringifyEnvironment = (environment: BrunoEnvironment): string => {
|
|
45
88
|
try {
|
|
46
89
|
const ocEnvironment: Environment = {
|
|
@@ -58,10 +101,16 @@ const stringifyEnvironment = (environment: BrunoEnvironment): string => {
|
|
|
58
101
|
}
|
|
59
102
|
}
|
|
60
103
|
|
|
104
|
+
const ocExternalSecrets = toOpenCollectionExternalSecrets(environment.externalSecrets);
|
|
105
|
+
if (ocExternalSecrets) {
|
|
106
|
+
(ocEnvironment as any).externalSecrets = ocExternalSecrets;
|
|
107
|
+
}
|
|
108
|
+
|
|
61
109
|
return stringifyYml(ocEnvironment);
|
|
62
110
|
} catch (error) {
|
|
63
111
|
console.error('Error stringifying environment:', error);
|
|
64
112
|
throw error;
|
|
65
113
|
}
|
|
66
114
|
};
|
|
115
|
+
|
|
67
116
|
export default stringifyEnvironment;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import stringifyFolder from './stringifyFolder';
|
|
2
|
+
import parseFolder from './parseFolder';
|
|
3
|
+
|
|
4
|
+
// Typed folder vars serialize to OC's `{ type, data }` struct.
|
|
5
|
+
// `dataType: 'string'` is the implicit default and stays a raw string.
|
|
6
|
+
|
|
7
|
+
describe('stringifyFolder — typed request.variables', () => {
|
|
8
|
+
it('round-trips typed values and omits a typed struct for the implicit string default', () => {
|
|
9
|
+
const folderRoot = {
|
|
10
|
+
meta: { name: 'my-folder', seq: 1 },
|
|
11
|
+
request: {
|
|
12
|
+
headers: [],
|
|
13
|
+
auth: { mode: 'none' },
|
|
14
|
+
script: { req: null, res: null },
|
|
15
|
+
tests: null,
|
|
16
|
+
vars: {
|
|
17
|
+
req: [
|
|
18
|
+
{ uid: 'v1', name: 'count', value: 42, enabled: true, dataType: 'number' },
|
|
19
|
+
{ uid: 'v2', name: 'enabled', value: true, enabled: true, dataType: 'boolean' },
|
|
20
|
+
{ uid: 'v3', name: 'config', value: { a: 1 }, enabled: true, dataType: 'object' },
|
|
21
|
+
{ uid: 'v4', name: 'greeting', value: 'hi', enabled: true, dataType: 'string' },
|
|
22
|
+
{ uid: 'v5', name: 'plain', value: 'hello', enabled: true }
|
|
23
|
+
],
|
|
24
|
+
res: []
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
docs: null
|
|
28
|
+
} as any;
|
|
29
|
+
|
|
30
|
+
const yml = stringifyFolder(folderRoot);
|
|
31
|
+
|
|
32
|
+
// `type: string` is never written out.
|
|
33
|
+
expect(yml).not.toMatch(/type:\s*string/);
|
|
34
|
+
|
|
35
|
+
const reparsed = parseFolder(yml);
|
|
36
|
+
const reqVars = reparsed.request!.vars!.req!;
|
|
37
|
+
|
|
38
|
+
expect(reqVars).toHaveLength(5);
|
|
39
|
+
expect(reqVars[0]).toMatchObject({ name: 'count', value: 42, dataType: 'number' });
|
|
40
|
+
expect(reqVars[1]).toMatchObject({ name: 'enabled', value: true, dataType: 'boolean' });
|
|
41
|
+
expect(reqVars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
42
|
+
expect(reqVars[3]).toMatchObject({ name: 'greeting', value: 'hi' });
|
|
43
|
+
expect(reqVars[3].dataType).toBeUndefined();
|
|
44
|
+
expect(reqVars[4]).toMatchObject({ name: 'plain', value: 'hello' });
|
|
45
|
+
expect(reqVars[4].dataType).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import stringifyItem from './stringifyItem';
|
|
2
|
+
import parseItem from './parseItem';
|
|
3
|
+
|
|
4
|
+
// Typed bruno vars on `request.vars.req` serialize to OC's `{ type, data }`
|
|
5
|
+
// struct. `dataType: 'string'` is the implicit default and stays a raw string.
|
|
6
|
+
|
|
7
|
+
describe('stringifyItem — typed runtime.variables', () => {
|
|
8
|
+
it('round-trips typed values and omits a typed struct for the implicit string default', () => {
|
|
9
|
+
const item = {
|
|
10
|
+
uid: 'i1',
|
|
11
|
+
type: 'http-request',
|
|
12
|
+
name: 'r',
|
|
13
|
+
seq: 1,
|
|
14
|
+
request: {
|
|
15
|
+
url: 'https://example.com',
|
|
16
|
+
method: 'GET',
|
|
17
|
+
headers: [],
|
|
18
|
+
params: [],
|
|
19
|
+
body: { mode: 'none' },
|
|
20
|
+
auth: { mode: 'none' },
|
|
21
|
+
script: { req: null, res: null },
|
|
22
|
+
tests: null,
|
|
23
|
+
vars: {
|
|
24
|
+
req: [
|
|
25
|
+
{ uid: 'v1', name: 'count', value: 42, enabled: true, dataType: 'number' },
|
|
26
|
+
{ uid: 'v2', name: 'enabled', value: true, enabled: true, dataType: 'boolean' },
|
|
27
|
+
{ uid: 'v3', name: 'config', value: { a: 1 }, enabled: true, dataType: 'object' },
|
|
28
|
+
{ uid: 'v4', name: 'greeting', value: 'hi', enabled: true, dataType: 'string' },
|
|
29
|
+
{ uid: 'v5', name: 'plain', value: 'hello', enabled: true }
|
|
30
|
+
],
|
|
31
|
+
res: []
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} as any;
|
|
35
|
+
|
|
36
|
+
const yml = stringifyItem(item);
|
|
37
|
+
|
|
38
|
+
// `type: string` is never written out.
|
|
39
|
+
expect(yml).not.toMatch(/type:\s*string/);
|
|
40
|
+
|
|
41
|
+
const reparsed = parseItem(yml);
|
|
42
|
+
const reqVars = reparsed.request!.vars!.req!;
|
|
43
|
+
|
|
44
|
+
expect(reqVars).toHaveLength(5);
|
|
45
|
+
expect(reqVars[0]).toMatchObject({ name: 'count', value: 42, dataType: 'number' });
|
|
46
|
+
expect(reqVars[1]).toMatchObject({ name: 'enabled', value: true, dataType: 'boolean' });
|
|
47
|
+
expect(reqVars[2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
48
|
+
expect(reqVars[3]).toMatchObject({ name: 'greeting', value: 'hi' });
|
|
49
|
+
expect(reqVars[3].dataType).toBeUndefined();
|
|
50
|
+
expect(reqVars[4]).toMatchObject({ name: 'plain', value: 'hello' });
|
|
51
|
+
expect(reqVars[4].dataType).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -4,6 +4,7 @@ import stringifyGraphqlRequest from './items/stringifyGraphQLRequest';
|
|
|
4
4
|
import stringifyGrpcRequest from './items/stringifyGrpcRequest';
|
|
5
5
|
import stringifyWebsocketRequest from './items/stringifyWebsocketRequest';
|
|
6
6
|
import stringifyScript from './items/stringifyScript';
|
|
7
|
+
import stringifyApp from './items/stringifyApp';
|
|
7
8
|
|
|
8
9
|
const stringifyItem = (item: BrunoItem): string => {
|
|
9
10
|
try {
|
|
@@ -23,6 +24,9 @@ const stringifyItem = (item: BrunoItem): string => {
|
|
|
23
24
|
case 'js':
|
|
24
25
|
return stringifyScript(item);
|
|
25
26
|
|
|
27
|
+
case 'app':
|
|
28
|
+
return stringifyApp(item);
|
|
29
|
+
|
|
26
30
|
case 'folder':
|
|
27
31
|
throw new Error('Folder items should be handled separately using stringifyFolder');
|
|
28
32
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
opencollection: 1.0.0
|
|
2
|
+
|
|
3
|
+
info:
|
|
4
|
+
name: md8
|
|
5
|
+
config:
|
|
6
|
+
proxy:
|
|
7
|
+
inherit: true
|
|
8
|
+
config:
|
|
9
|
+
protocol: http
|
|
10
|
+
hostname: ""
|
|
11
|
+
port: ""
|
|
12
|
+
auth:
|
|
13
|
+
username: ""
|
|
14
|
+
password: ""
|
|
15
|
+
bypassProxy: ""
|
|
16
|
+
bundled: false
|
|
17
|
+
extensions:
|
|
18
|
+
bruno:
|
|
19
|
+
ignore:
|
|
20
|
+
- node_modules
|
|
21
|
+
- .git
|
|
22
|
+
presets:
|
|
23
|
+
request:
|
|
24
|
+
type: graphql
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
opencollection: 1.0.0
|
|
2
|
+
|
|
3
|
+
info:
|
|
4
|
+
name: md8
|
|
5
|
+
config:
|
|
6
|
+
proxy:
|
|
7
|
+
inherit: true
|
|
8
|
+
config:
|
|
9
|
+
protocol: http
|
|
10
|
+
hostname: ""
|
|
11
|
+
port: ""
|
|
12
|
+
auth:
|
|
13
|
+
username: ""
|
|
14
|
+
password: ""
|
|
15
|
+
bypassProxy: ""
|
|
16
|
+
bundled: false
|
|
17
|
+
extensions:
|
|
18
|
+
bruno:
|
|
19
|
+
ignore:
|
|
20
|
+
- node_modules
|
|
21
|
+
- .git
|
|
22
|
+
presets:
|
|
23
|
+
request:
|
|
24
|
+
type: http
|
|
25
|
+
url: https://example.com/graphql
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import parseCollection from '../parseCollection';
|
|
4
|
+
|
|
5
|
+
const loadFixture = (name) =>
|
|
6
|
+
fs.readFileSync(path.join(__dirname, 'fixtures', 'presets', `${name}.yml`), 'utf8');
|
|
7
|
+
|
|
8
|
+
describe('yml parseCollection - presets', () => {
|
|
9
|
+
it('parses presets when request.type and request.url are present', () => {
|
|
10
|
+
const { brunoConfig } = parseCollection(loadFixture('with-type-and-url'));
|
|
11
|
+
|
|
12
|
+
expect(brunoConfig.presets).toEqual({
|
|
13
|
+
requestType: 'graphql',
|
|
14
|
+
requestUrl: 'https://example.com/graphql'
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('defaults requestType and requestUrl to empty strings (not arrays) when request fields are missing', () => {
|
|
19
|
+
const { brunoConfig } = parseCollection(loadFixture('empty-request'));
|
|
20
|
+
|
|
21
|
+
expect(brunoConfig.presets).toEqual({
|
|
22
|
+
requestType: '',
|
|
23
|
+
requestUrl: ''
|
|
24
|
+
});
|
|
25
|
+
expect(Array.isArray(brunoConfig.presets.requestType)).toBe(false);
|
|
26
|
+
expect(Array.isArray(brunoConfig.presets.requestUrl)).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not set presets when the extension has no presets block', () => {
|
|
30
|
+
const { brunoConfig } = parseCollection(loadFixture('no-presets'));
|
|
31
|
+
|
|
32
|
+
expect(brunoConfig.presets).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('does not set presets when presets exists but request key is absent', () => {
|
|
36
|
+
const { brunoConfig } = parseCollection(loadFixture('no-request-key'));
|
|
37
|
+
|
|
38
|
+
expect(brunoConfig.presets).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('parses a realistic collection with only request.type set (no url) — defaults url to empty string', () => {
|
|
42
|
+
const { brunoConfig } = parseCollection(loadFixture('type-only-realistic'));
|
|
43
|
+
|
|
44
|
+
expect(brunoConfig.presets).toEqual({
|
|
45
|
+
requestType: 'graphql',
|
|
46
|
+
requestUrl: ''
|
|
47
|
+
});
|
|
48
|
+
expect(Array.isArray(brunoConfig.presets.requestUrl)).toBe(false);
|
|
49
|
+
expect(brunoConfig.ignore).toEqual(['node_modules', '.git']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('parses a realistic collection with request.type http and request.url set', () => {
|
|
53
|
+
const { brunoConfig } = parseCollection(loadFixture('url-only-realistic'));
|
|
54
|
+
|
|
55
|
+
expect(brunoConfig.presets).toEqual({
|
|
56
|
+
requestType: 'http',
|
|
57
|
+
requestUrl: 'https://example.com/graphql'
|
|
58
|
+
});
|
|
59
|
+
expect(Array.isArray(brunoConfig.presets.requestType)).toBe(false);
|
|
60
|
+
expect(Array.isArray(brunoConfig.presets.requestUrl)).toBe(false);
|
|
61
|
+
expect(brunoConfig.ignore).toEqual(['node_modules', '.git']);
|
|
62
|
+
});
|
|
63
|
+
});
|