@usebruno/filestore 0.10.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 +1 -2
- 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 +14 -22
- 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 +41 -19
- 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 +55 -20
- 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 +24 -63
- 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 +7 -7
- 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
- package/dist/formats/yml/tests/parseEnvironment.spec.d.ts +0 -2
- package/dist/formats/yml/tests/parseEnvironment.spec.d.ts.map +0 -1
- package/dist/formats/yml/tests/variables.spec.d.ts +0 -2
- package/dist/formats/yml/tests/variables.spec.d.ts.map +0 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBrunoVariableDataType,
|
|
3
|
+
isTypedValue,
|
|
4
|
+
hasTypedMetadata,
|
|
5
|
+
toOpenCollectionTypedValue,
|
|
6
|
+
fromOpenCollectionTypedValue
|
|
7
|
+
} from './datatype';
|
|
8
|
+
|
|
9
|
+
describe('dataType helpers', () => {
|
|
10
|
+
describe('isBrunoVariableDataType', () => {
|
|
11
|
+
it('returns true for supported Bruno datatypes', () => {
|
|
12
|
+
expect(isBrunoVariableDataType('string')).toBe(true);
|
|
13
|
+
expect(isBrunoVariableDataType('number')).toBe(true);
|
|
14
|
+
expect(isBrunoVariableDataType('boolean')).toBe(true);
|
|
15
|
+
expect(isBrunoVariableDataType('object')).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('returns false for unsupported OpenCollection datatypes (e.g. null)', () => {
|
|
19
|
+
expect(isBrunoVariableDataType('null')).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('returns false for non-string inputs', () => {
|
|
23
|
+
expect(isBrunoVariableDataType(undefined)).toBe(false);
|
|
24
|
+
expect(isBrunoVariableDataType(null)).toBe(false);
|
|
25
|
+
expect(isBrunoVariableDataType(42)).toBe(false);
|
|
26
|
+
expect(isBrunoVariableDataType({})).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('isTypedValue', () => {
|
|
31
|
+
it('returns true for OpenCollection typed-value shapes', () => {
|
|
32
|
+
expect(isTypedValue({ type: 'number', data: '42' })).toBe(true);
|
|
33
|
+
expect(isTypedValue({ type: 'object', data: '{}' })).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns false for plain strings', () => {
|
|
37
|
+
expect(isTypedValue('hello')).toBe(false);
|
|
38
|
+
expect(isTypedValue('')).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns false for arrays, null, and objects missing type/data', () => {
|
|
42
|
+
expect(isTypedValue(null)).toBe(false);
|
|
43
|
+
expect(isTypedValue([])).toBe(false);
|
|
44
|
+
expect(isTypedValue([{ type: 'number', data: '1' }])).toBe(false);
|
|
45
|
+
expect(isTypedValue({ type: 'number' })).toBe(false);
|
|
46
|
+
expect(isTypedValue({ data: '1' })).toBe(false);
|
|
47
|
+
expect(isTypedValue({})).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('hasTypedMetadata', () => {
|
|
52
|
+
it('returns false when dataType is missing', () => {
|
|
53
|
+
expect(hasTypedMetadata({})).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns false when dataType is the string default', () => {
|
|
57
|
+
expect(hasTypedMetadata({ dataType: 'string' })).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns true for non-string datatypes', () => {
|
|
61
|
+
expect(hasTypedMetadata({ dataType: 'number' })).toBe(true);
|
|
62
|
+
expect(hasTypedMetadata({ dataType: 'boolean' })).toBe(true);
|
|
63
|
+
expect(hasTypedMetadata({ dataType: 'object' })).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('toOpenCollectionTypedValue', () => {
|
|
68
|
+
it('builds a {type, data} struct from dataType + stringified value', () => {
|
|
69
|
+
expect(toOpenCollectionTypedValue({ dataType: 'number' }, '42'))
|
|
70
|
+
.toEqual({ type: 'number', data: '42' });
|
|
71
|
+
expect(toOpenCollectionTypedValue({ dataType: 'boolean' }, 'true'))
|
|
72
|
+
.toEqual({ type: 'boolean', data: 'true' });
|
|
73
|
+
expect(toOpenCollectionTypedValue({ dataType: 'object' }, '{"a":1}'))
|
|
74
|
+
.toEqual({ type: 'object', data: '{"a":1}' });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('falls back to type "string" when dataType is missing', () => {
|
|
78
|
+
expect(toOpenCollectionTypedValue({}, 'hello'))
|
|
79
|
+
.toEqual({ type: 'string', data: 'hello' });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('fromOpenCollectionTypedValue', () => {
|
|
84
|
+
it('coerces typed values to their declared dataType', () => {
|
|
85
|
+
expect(fromOpenCollectionTypedValue({ type: 'number', data: '42' }))
|
|
86
|
+
.toEqual({ value: 42, dataType: 'number' });
|
|
87
|
+
expect(fromOpenCollectionTypedValue({ type: 'boolean', data: 'true' }))
|
|
88
|
+
.toEqual({ value: true, dataType: 'boolean' });
|
|
89
|
+
expect(fromOpenCollectionTypedValue({ type: 'object', data: '{"a":1}' }))
|
|
90
|
+
.toEqual({ value: { a: 1 }, dataType: 'object' });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('falls back to string when the declared type is unsupported (e.g. null)', () => {
|
|
94
|
+
const result = fromOpenCollectionTypedValue({ type: 'null' as any, data: '' });
|
|
95
|
+
expect(result.dataType).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('coerces non-string data to a string before coercing by dataType', () => {
|
|
99
|
+
const result = fromOpenCollectionTypedValue({ type: 'number', data: 42 as any });
|
|
100
|
+
expect(result.value).toBe(42);
|
|
101
|
+
expect(result.dataType).toBe('number');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('returns the raw string when coercion fails (e.g. non-numeric @number)', () => {
|
|
105
|
+
const result = fromOpenCollectionTypedValue({ type: 'number', data: 'not-a-number' });
|
|
106
|
+
expect(result.value).toBe('not-a-number');
|
|
107
|
+
expect(result.dataType).toBe('number');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { VariableTypedValue } from '@opencollection/types/common/variables';
|
|
2
|
+
import {
|
|
3
|
+
parseValueByDataType,
|
|
4
|
+
BrunoVariableDataType,
|
|
5
|
+
isBrunoVariableDataType,
|
|
6
|
+
valueToString
|
|
7
|
+
} from '@usebruno/common/utils';
|
|
8
|
+
|
|
9
|
+
export { BrunoVariableDataType, isBrunoVariableDataType };
|
|
10
|
+
|
|
11
|
+
export const serializeVariableValue = (value: unknown): string => {
|
|
12
|
+
if (value !== null && typeof value === 'object') {
|
|
13
|
+
return JSON.stringify(value, null, 2);
|
|
14
|
+
}
|
|
15
|
+
return valueToString(value);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const isTypedValue = (value: unknown): value is VariableTypedValue => {
|
|
19
|
+
return typeof value === 'object'
|
|
20
|
+
&& value !== null
|
|
21
|
+
&& !Array.isArray(value)
|
|
22
|
+
&& 'type' in value
|
|
23
|
+
&& 'data' in value;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface TypedVariableFields {
|
|
27
|
+
dataType?: BrunoVariableDataType;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const hasTypedMetadata = (v: TypedVariableFields): boolean => {
|
|
31
|
+
return !!v.dataType && v.dataType !== 'string';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const toOpenCollectionTypedValue = (
|
|
35
|
+
v: TypedVariableFields,
|
|
36
|
+
dataStr: string
|
|
37
|
+
): VariableTypedValue => {
|
|
38
|
+
return {
|
|
39
|
+
type: (v.dataType || 'string') as VariableTypedValue['type'],
|
|
40
|
+
data: dataStr
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// `'string'` is the implicit default — omitted from the result.
|
|
45
|
+
export const fromOpenCollectionTypedValue = (
|
|
46
|
+
typed: VariableTypedValue
|
|
47
|
+
): { value: any } & TypedVariableFields => {
|
|
48
|
+
const dataStr = typeof typed.data === 'string' ? typed.data : String(typed.data ?? '');
|
|
49
|
+
const dataType: BrunoVariableDataType = isBrunoVariableDataType(typed.type) ? typed.type : 'string';
|
|
50
|
+
const result: { value: any } & TypedVariableFields = {
|
|
51
|
+
value: parseValueByDataType(dataStr, dataType)
|
|
52
|
+
};
|
|
53
|
+
if (dataType !== 'string') {
|
|
54
|
+
result.dataType = dataType;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
@@ -51,6 +51,10 @@ export const toBrunoHttpHeaders = (headers: HttpRequestHeader[] | HttpResponseHe
|
|
|
51
51
|
enabled: ('disabled' in header) ? header.disabled !== true : true
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
if ('description' in header && header.description) {
|
|
55
|
+
brunoHeader.description = typeof header.description === 'string' ? header.description : (header.description as any)?.content || '';
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
return brunoHeader;
|
|
55
59
|
});
|
|
56
60
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { toOpenCollectionVariables, toBrunoVariables } from './variables';
|
|
2
|
+
|
|
3
|
+
describe('toOpenCollectionVariables', () => {
|
|
4
|
+
it('returns undefined for null / empty input', () => {
|
|
5
|
+
expect(toOpenCollectionVariables(null)).toBeUndefined();
|
|
6
|
+
expect(toOpenCollectionVariables(undefined)).toBeUndefined();
|
|
7
|
+
expect(toOpenCollectionVariables([])).toBeUndefined();
|
|
8
|
+
expect(toOpenCollectionVariables({ req: [], res: [] })).toBeUndefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('serializes plain string variables as a raw string value', () => {
|
|
12
|
+
const out = toOpenCollectionVariables([
|
|
13
|
+
{ uid: 'u1', name: 'apiKey', value: 'abc', enabled: true } as any
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
expect(out).toEqual([{ name: 'apiKey', value: 'abc' }]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('serializes typed variables as a {type, data} struct', () => {
|
|
20
|
+
const out = toOpenCollectionVariables([
|
|
21
|
+
{ uid: 'u1', name: 'port', value: 3000, enabled: true, dataType: 'number' } as any,
|
|
22
|
+
{ uid: 'u2', name: 'flag', value: true, enabled: true, dataType: 'boolean' } as any,
|
|
23
|
+
{ uid: 'u3', name: 'config', value: { a: 1 }, enabled: true, dataType: 'object' } as any
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
expect(out).toEqual([
|
|
27
|
+
{ name: 'port', value: { type: 'number', data: '3000' } },
|
|
28
|
+
{ name: 'flag', value: { type: 'boolean', data: 'true' } },
|
|
29
|
+
{ name: 'config', value: { type: 'object', data: '{\n "a": 1\n}' } }
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('does not emit a typed struct for the string default dataType', () => {
|
|
34
|
+
const out = toOpenCollectionVariables([
|
|
35
|
+
{ uid: 'u1', name: 'apiKey', value: 'abc', enabled: true, dataType: 'string' } as any
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
expect(out).toEqual([{ name: 'apiKey', value: 'abc' }]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('marks disabled variables and preserves description', () => {
|
|
42
|
+
const out = toOpenCollectionVariables([
|
|
43
|
+
{ uid: 'u1', name: 'apiKey', value: 'abc', enabled: false, description: 'auth key' } as any
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
expect(out).toEqual([
|
|
47
|
+
{ name: 'apiKey', value: 'abc', description: 'auth key', disabled: true }
|
|
48
|
+
]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('accepts the folder { req, res } shape and only emits req vars', () => {
|
|
52
|
+
const out = toOpenCollectionVariables({
|
|
53
|
+
req: [{ uid: 'u1', name: 'a', value: '1', enabled: true } as any],
|
|
54
|
+
res: [{ uid: 'u2', name: 'b', value: '2', enabled: true } as any]
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(out).toEqual([{ name: 'a', value: '1' }]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('toBrunoVariables', () => {
|
|
62
|
+
it('returns empty req/res for null / empty input', () => {
|
|
63
|
+
expect(toBrunoVariables(null)).toEqual({ req: [], res: [] });
|
|
64
|
+
expect(toBrunoVariables(undefined)).toEqual({ req: [], res: [] });
|
|
65
|
+
expect(toBrunoVariables([])).toEqual({ req: [], res: [] });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('parses plain string variables into Bruno req vars', () => {
|
|
69
|
+
const { req } = toBrunoVariables([
|
|
70
|
+
{ name: 'apiKey', value: 'abc' } as any
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
expect(req).toHaveLength(1);
|
|
74
|
+
const v = req![0];
|
|
75
|
+
expect(v).toMatchObject({
|
|
76
|
+
name: 'apiKey',
|
|
77
|
+
value: 'abc',
|
|
78
|
+
enabled: true,
|
|
79
|
+
local: false
|
|
80
|
+
});
|
|
81
|
+
expect(v.uid).toEqual(expect.any(String));
|
|
82
|
+
expect(v.dataType).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('parses typed-value variables into typed Bruno variables', () => {
|
|
86
|
+
const { req } = toBrunoVariables([
|
|
87
|
+
{ name: 'port', value: { type: 'number', data: '3000' } } as any,
|
|
88
|
+
{ name: 'flag', value: { type: 'boolean', data: 'true' } } as any,
|
|
89
|
+
{ name: 'config', value: { type: 'object', data: '{"a":1}' } } as any
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
expect(req![0]).toMatchObject({ name: 'port', value: 3000, dataType: 'number' });
|
|
93
|
+
expect(req![1]).toMatchObject({ name: 'flag', value: true, dataType: 'boolean' });
|
|
94
|
+
expect(req![2]).toMatchObject({ name: 'config', value: { a: 1 }, dataType: 'object' });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('falls back to the raw string when typed data fails to coerce', () => {
|
|
98
|
+
const { req } = toBrunoVariables([
|
|
99
|
+
{ name: 'port', value: { type: 'number', data: 'not-a-number' } } as any,
|
|
100
|
+
{ name: 'flag', value: { type: 'boolean', data: 'not-a-boolean' } } as any,
|
|
101
|
+
{ name: 'config', value: { type: 'object', data: 'not-a-object' } } as any
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
expect(req![0]).toMatchObject({ name: 'port', value: 'not-a-number', dataType: 'number' });
|
|
105
|
+
expect(typeof req![0].value).toBe('string');
|
|
106
|
+
expect(req![1]).toMatchObject({ name: 'flag', value: 'not-a-boolean', dataType: 'boolean' });
|
|
107
|
+
expect(typeof req![1].value).toBe('string');
|
|
108
|
+
expect(req![2]).toMatchObject({ name: 'config', value: 'not-a-object', dataType: 'object' });
|
|
109
|
+
expect(typeof req![2].value).toBe('string');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('respects the disabled flag', () => {
|
|
113
|
+
const { req } = toBrunoVariables([
|
|
114
|
+
{ name: 'a', value: '1', disabled: true } as any
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
expect(req![0].enabled).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('extracts description.content when description is a structured object', () => {
|
|
121
|
+
const { req } = toBrunoVariables([
|
|
122
|
+
{ name: 'a', value: '1', description: { content: 'note' } } as any
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
expect(req![0].description).toBe('note');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -2,16 +2,13 @@ import { Variable, VariableTypedValue } from '@opencollection/types/common/varia
|
|
|
2
2
|
import { FolderRequest as BrunoFolderRequest } from '@usebruno/schema-types/collection/folder';
|
|
3
3
|
import { Variable as BrunoVariable, Variables as BrunoVariables } from '@usebruno/schema-types/common/variables';
|
|
4
4
|
import { uuid, ensureString } from '../../../utils';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
&& 'data' in value
|
|
13
|
-
);
|
|
14
|
-
};
|
|
5
|
+
import {
|
|
6
|
+
isTypedValue,
|
|
7
|
+
hasTypedMetadata,
|
|
8
|
+
toOpenCollectionTypedValue,
|
|
9
|
+
fromOpenCollectionTypedValue,
|
|
10
|
+
serializeVariableValue
|
|
11
|
+
} from './datatype';
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
14
|
* Convert Bruno pre-request variables to OpenCollection variables format.
|
|
@@ -29,12 +26,10 @@ export const toOpenCollectionVariables = (variables: BrunoFolderRequest['vars']
|
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
const ocVariables: Variable[] = reqVarsArray.map((v: BrunoVariable): Variable => {
|
|
29
|
+
const valueStr = serializeVariableValue(v.value);
|
|
32
30
|
const variable: Variable = {
|
|
33
31
|
name: v.name || '',
|
|
34
|
-
value:
|
|
35
|
-
v.datatype && v.datatype !== 'string'
|
|
36
|
-
? { type: v.datatype, data: ensureString(v.value) }
|
|
37
|
-
: v.value || ''
|
|
32
|
+
value: hasTypedMetadata(v) ? toOpenCollectionTypedValue(v, valueStr) : valueStr
|
|
38
33
|
};
|
|
39
34
|
|
|
40
35
|
if (v?.description?.trim().length) {
|
|
@@ -62,7 +57,7 @@ export const toBrunoVariables = (variables: Variable[] | null | undefined): { re
|
|
|
62
57
|
const reqVars: BrunoVariables = [];
|
|
63
58
|
|
|
64
59
|
variables.forEach((v: Variable) => {
|
|
65
|
-
const
|
|
60
|
+
const base: BrunoVariable = {
|
|
66
61
|
uid: uuid(),
|
|
67
62
|
name: ensureString(v.name),
|
|
68
63
|
value: '',
|
|
@@ -71,19 +66,16 @@ export const toBrunoVariables = (variables: Variable[] | null | undefined): { re
|
|
|
71
66
|
};
|
|
72
67
|
|
|
73
68
|
if (isTypedValue(v.value)) {
|
|
74
|
-
|
|
75
|
-
if (v.value.type !== 'string' && v.value.type !== 'null') {
|
|
76
|
-
variable.datatype = v.value.type;
|
|
77
|
-
}
|
|
69
|
+
Object.assign(base, fromOpenCollectionTypedValue(v.value));
|
|
78
70
|
} else {
|
|
79
|
-
|
|
71
|
+
base.value = ensureString(v.value);
|
|
80
72
|
}
|
|
81
73
|
|
|
82
74
|
if (v.description) {
|
|
83
|
-
|
|
75
|
+
base.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || '';
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
reqVars.push(
|
|
78
|
+
reqVars.push(base);
|
|
87
79
|
});
|
|
88
80
|
|
|
89
81
|
return { req: reqVars, res: [] };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Item as BrunoItem } from '@usebruno/schema-types/collection/item';
|
|
2
|
+
import { uuid } from '../../../utils';
|
|
3
|
+
|
|
4
|
+
export interface AppFile {
|
|
5
|
+
info: {
|
|
6
|
+
name?: string;
|
|
7
|
+
type: 'app';
|
|
8
|
+
seq?: number;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
};
|
|
11
|
+
code?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const parseApp = (ocApp: AppFile): BrunoItem => {
|
|
15
|
+
const info = ocApp.info || ({} as AppFile['info']);
|
|
16
|
+
|
|
17
|
+
const brunoItem: BrunoItem = {
|
|
18
|
+
uid: uuid(),
|
|
19
|
+
type: 'app',
|
|
20
|
+
seq: typeof info.seq === 'number' ? info.seq : 1,
|
|
21
|
+
name: info.name || 'App',
|
|
22
|
+
tags: Array.isArray(info.tags) ? info.tags : [],
|
|
23
|
+
request: null,
|
|
24
|
+
settings: null,
|
|
25
|
+
app: { code: ocApp.code || '' },
|
|
26
|
+
fileContent: null,
|
|
27
|
+
root: null,
|
|
28
|
+
items: [],
|
|
29
|
+
examples: [],
|
|
30
|
+
filename: null,
|
|
31
|
+
pathname: null
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return brunoItem;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default parseApp;
|
|
@@ -98,6 +98,14 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
|
|
|
98
98
|
pathname: null
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
+
// description
|
|
102
|
+
if (info?.description) {
|
|
103
|
+
const desc = typeof info.description === 'string' ? info.description : (info.description as any)?.content || '';
|
|
104
|
+
if (desc.trim().length) {
|
|
105
|
+
brunoItem.description = desc;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
// settings
|
|
102
110
|
if (ocRequest.settings) {
|
|
103
111
|
const settings: BrunoHttpItemSettings = {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Item as BrunoItem } from '@usebruno/schema-types/collection/item';
|
|
2
2
|
import type { GrpcRequest as BrunoGrpcRequest } from '@usebruno/schema-types/requests/grpc';
|
|
3
|
-
import type { GrpcRequest, GrpcMetadata } from '@opencollection/types/requests/grpc';
|
|
3
|
+
import type { GrpcRequest, GrpcMetadata, GrpcMessageVariant } from '@opencollection/types/requests/grpc';
|
|
4
4
|
import type { KeyValue as BrunoKeyValue } from '@usebruno/schema-types/common/key-value';
|
|
5
5
|
import { toBrunoAuth } from '../common/auth';
|
|
6
6
|
import { toBrunoVariables } from '../common/variables';
|
|
@@ -21,6 +21,13 @@ const toBrunoGrpcMetadata = (metadata: GrpcMetadata[] | null | undefined): Bruno
|
|
|
21
21
|
enabled: meta.disabled !== true
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
if (meta.description) {
|
|
25
|
+
const desc = typeof meta.description === 'string' ? meta.description : (meta.description as any)?.content || '';
|
|
26
|
+
if (desc.trim().length) {
|
|
27
|
+
brunoMeta.description = desc;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
return brunoMeta;
|
|
25
32
|
});
|
|
26
33
|
|
|
@@ -57,10 +64,16 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
|
|
|
57
64
|
};
|
|
58
65
|
|
|
59
66
|
// message
|
|
60
|
-
|
|
67
|
+
const rawMessage = grpc?.message;
|
|
68
|
+
if (Array.isArray(rawMessage)) {
|
|
69
|
+
brunoRequest.body.grpc = (rawMessage as GrpcMessageVariant[]).map(({ title, message }, index) => ({
|
|
70
|
+
name: title || `message ${index + 1}`,
|
|
71
|
+
content: ensureString(message)
|
|
72
|
+
}));
|
|
73
|
+
} else if (isNonEmptyString(rawMessage)) {
|
|
61
74
|
brunoRequest.body.grpc = [{
|
|
62
75
|
name: '',
|
|
63
|
-
content:
|
|
76
|
+
content: rawMessage as string
|
|
64
77
|
}];
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -110,6 +123,14 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
|
|
|
110
123
|
pathname: null
|
|
111
124
|
};
|
|
112
125
|
|
|
126
|
+
// description
|
|
127
|
+
if (info?.description) {
|
|
128
|
+
const desc = typeof info.description === 'string' ? info.description : (info.description as any)?.content || '';
|
|
129
|
+
if (desc.trim().length) {
|
|
130
|
+
brunoItem.description = desc;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
return brunoItem;
|
|
114
135
|
};
|
|
115
136
|
|
|
@@ -9,6 +9,7 @@ import { toBrunoVariables } from '../common/variables';
|
|
|
9
9
|
import { toBrunoPostResponseVariables } from '../common/actions';
|
|
10
10
|
import { toBrunoScripts } from '../common/scripts';
|
|
11
11
|
import { toBrunoAssertions } from '../common/assertions';
|
|
12
|
+
import { toBrunoApp } from '../common/app';
|
|
12
13
|
import { uuid, ensureString } from '../../../utils';
|
|
13
14
|
|
|
14
15
|
const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
@@ -79,6 +80,9 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
|
79
80
|
brunoRequest.docs = ocRequest.docs;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
// app
|
|
84
|
+
const app = toBrunoApp((ocRequest as any).app);
|
|
85
|
+
|
|
82
86
|
// bruno item
|
|
83
87
|
const brunoItem: BrunoItem = {
|
|
84
88
|
uid: uuid(),
|
|
@@ -88,6 +92,7 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
|
88
92
|
tags: info?.tags || [],
|
|
89
93
|
request: brunoRequest,
|
|
90
94
|
settings: null,
|
|
95
|
+
app,
|
|
91
96
|
fileContent: null,
|
|
92
97
|
root: null,
|
|
93
98
|
items: [],
|
|
@@ -96,6 +101,14 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
|
|
|
96
101
|
pathname: null
|
|
97
102
|
};
|
|
98
103
|
|
|
104
|
+
// description
|
|
105
|
+
if (info?.description) {
|
|
106
|
+
const desc = typeof info.description === 'string' ? info.description : (info.description as any)?.content || '';
|
|
107
|
+
if (desc.trim().length) {
|
|
108
|
+
brunoItem.description = desc;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
99
112
|
// settings
|
|
100
113
|
if (ocRequest.settings) {
|
|
101
114
|
const settings: BrunoHttpItemSettings = {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Item as BrunoItem } from '@usebruno/schema-types/collection/item';
|
|
2
2
|
import type { WebSocketRequest as BrunoWebSocketRequest } from '@usebruno/schema-types/requests/websocket';
|
|
3
|
-
import type { WebSocketRequest, WebSocketMessage } from '@opencollection/types/requests/websocket';
|
|
3
|
+
import type { WebSocketRequest, WebSocketMessage, WebSocketMessageVariant } from '@opencollection/types/requests/websocket';
|
|
4
4
|
import { toBrunoAuth } from '../common/auth';
|
|
5
5
|
import { toBrunoHttpHeaders } from '../common/headers';
|
|
6
6
|
import { toBrunoVariables } from '../common/variables';
|
|
@@ -35,14 +35,26 @@ const parseWebsocketRequest = (ocRequest: WebSocketRequest): BrunoItem => {
|
|
|
35
35
|
|
|
36
36
|
// message
|
|
37
37
|
if (websocket?.message) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
brunoRequest.body.ws =
|
|
42
|
-
name:
|
|
43
|
-
type: message
|
|
44
|
-
content:
|
|
45
|
-
|
|
38
|
+
if (Array.isArray(websocket.message)) {
|
|
39
|
+
// multiple messages: WebSocketMessageVariant[]
|
|
40
|
+
const variants = websocket.message as WebSocketMessageVariant[];
|
|
41
|
+
brunoRequest.body.ws = variants.map((variant, index) => ({
|
|
42
|
+
name: variant.title || `message ${index + 1}`,
|
|
43
|
+
type: variant.message?.type || 'text',
|
|
44
|
+
content: ensureString(variant.message?.data),
|
|
45
|
+
selected: variant.selected || false
|
|
46
|
+
}));
|
|
47
|
+
} else {
|
|
48
|
+
// single message uses flat WebSocketMessage
|
|
49
|
+
const message = websocket.message as WebSocketMessage;
|
|
50
|
+
const messageData = ensureString(message.data);
|
|
51
|
+
if (messageData.trim().length) {
|
|
52
|
+
brunoRequest.body.ws = [{
|
|
53
|
+
name: '',
|
|
54
|
+
type: message.type || 'text',
|
|
55
|
+
content: messageData
|
|
56
|
+
}];
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
}
|
|
48
60
|
|
|
@@ -101,6 +113,14 @@ const parseWebsocketRequest = (ocRequest: WebSocketRequest): BrunoItem => {
|
|
|
101
113
|
pathname: null
|
|
102
114
|
};
|
|
103
115
|
|
|
116
|
+
// description
|
|
117
|
+
if (info?.description) {
|
|
118
|
+
const desc = typeof info.description === 'string' ? info.description : (info.description as any)?.content || '';
|
|
119
|
+
if (desc.trim().length) {
|
|
120
|
+
brunoItem.description = desc;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
104
124
|
return brunoItem;
|
|
105
125
|
};
|
|
106
126
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Item as BrunoItem } from '@usebruno/schema-types/collection/item';
|
|
2
|
+
import { stringifyYml } from '../utils';
|
|
3
|
+
|
|
4
|
+
interface AppFileInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
type: 'app';
|
|
7
|
+
seq?: number;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface AppFile {
|
|
12
|
+
info: AppFileInfo;
|
|
13
|
+
code?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Mirror the request shape (info.name/type/seq/tags + body) so apps round-trip
|
|
17
|
+
// the same fields and can be reordered via seq like every other item.
|
|
18
|
+
const stringifyApp = (item: BrunoItem): string => {
|
|
19
|
+
try {
|
|
20
|
+
const info: AppFileInfo = {
|
|
21
|
+
name: item.name && item.name.trim().length ? item.name : 'App',
|
|
22
|
+
type: 'app'
|
|
23
|
+
};
|
|
24
|
+
if (typeof item.seq === 'number') {
|
|
25
|
+
info.seq = item.seq;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(item.tags) && item.tags.length) {
|
|
28
|
+
info.tags = item.tags;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ocApp: AppFile = { info };
|
|
32
|
+
|
|
33
|
+
const code = item.app?.code;
|
|
34
|
+
if (code && code.trim().length) {
|
|
35
|
+
ocApp.code = code;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return stringifyYml(ocApp);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error stringifying app:', error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default stringifyApp;
|
|
@@ -33,6 +33,9 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
|
|
|
33
33
|
if (item.tags?.length) {
|
|
34
34
|
info.tags = item.tags;
|
|
35
35
|
}
|
|
36
|
+
if (isNonEmptyString(item.description)) {
|
|
37
|
+
info.description = item.description;
|
|
38
|
+
}
|
|
36
39
|
ocRequest.info = info;
|
|
37
40
|
|
|
38
41
|
// graphql block
|