@usebruno/filestore 0.10.0 → 0.12.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.
Files changed (143) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/workers/worker-script.js +1 -1
  4. package/dist/cjs/workers/worker-script.js.map +1 -1
  5. package/dist/constants.d.ts +2 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/esm/index.js +1 -1
  8. package/dist/esm/index.js.map +1 -1
  9. package/dist/esm/workers/worker-script.js +1 -1
  10. package/dist/esm/workers/worker-script.js.map +1 -1
  11. package/dist/formats/bru/index.d.ts.map +1 -1
  12. package/dist/formats/bru/utils/redact-large-text-blocks.d.ts +11 -0
  13. package/dist/formats/bru/utils/redact-large-text-blocks.d.ts.map +1 -0
  14. package/dist/formats/yml/common/app.d.ts +8 -0
  15. package/dist/formats/yml/common/app.d.ts.map +1 -0
  16. package/dist/formats/yml/common/auth.d.ts.map +1 -1
  17. package/dist/formats/yml/common/body.d.ts.map +1 -1
  18. package/dist/formats/yml/common/datatype.d.ts +14 -0
  19. package/dist/formats/yml/common/datatype.d.ts.map +1 -0
  20. package/dist/formats/yml/common/headers.d.ts.map +1 -1
  21. package/dist/formats/yml/common/variables.d.ts +1 -2
  22. package/dist/formats/yml/common/variables.d.ts.map +1 -1
  23. package/dist/formats/yml/index.d.ts +3 -1
  24. package/dist/formats/yml/index.d.ts.map +1 -1
  25. package/dist/formats/yml/items/parseApp.d.ts +13 -0
  26. package/dist/formats/yml/items/parseApp.d.ts.map +1 -0
  27. package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
  28. package/dist/formats/yml/items/parseGrpcRequest.d.ts.map +1 -1
  29. package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
  30. package/dist/formats/yml/items/parseWebsocketRequest.d.ts.map +1 -1
  31. package/dist/formats/yml/items/stringifyApp.d.ts +4 -0
  32. package/dist/formats/yml/items/stringifyApp.d.ts.map +1 -0
  33. package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
  34. package/dist/formats/yml/items/stringifyGrpcRequest.d.ts.map +1 -1
  35. package/dist/formats/yml/items/stringifyHttpRequest.d.ts.map +1 -1
  36. package/dist/formats/yml/items/stringifyWebsocketRequest.d.ts.map +1 -1
  37. package/dist/formats/yml/mockServerTypes.d.ts +95 -0
  38. package/dist/formats/yml/mockServerTypes.d.ts.map +1 -0
  39. package/dist/formats/yml/parseCollection.d.ts.map +1 -1
  40. package/dist/formats/yml/parseEnvironment.d.ts +3 -1
  41. package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
  42. package/dist/formats/yml/parseFolder.d.ts.map +1 -1
  43. package/dist/formats/yml/parseItem.d.ts.map +1 -1
  44. package/dist/formats/yml/parseMockServer.d.ts +4 -0
  45. package/dist/formats/yml/parseMockServer.d.ts.map +1 -0
  46. package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
  47. package/dist/formats/yml/stringifyEnvironment.d.ts +3 -1
  48. package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
  49. package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
  50. package/dist/formats/yml/stringifyItem.d.ts.map +1 -1
  51. package/dist/formats/yml/stringifyMockServer.d.ts +4 -0
  52. package/dist/formats/yml/stringifyMockServer.d.ts.map +1 -0
  53. package/dist/index.d.ts +18 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/types.d.ts +7 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/utils/secretsJsonToEnvironments.d.ts +38 -0
  58. package/dist/utils/secretsJsonToEnvironments.d.ts.map +1 -0
  59. package/dist/workers/WorkerQueue/index.d.ts +3 -2
  60. package/dist/workers/WorkerQueue/index.d.ts.map +1 -1
  61. package/dist/workers/index.d.ts +4 -0
  62. package/dist/workers/index.d.ts.map +1 -1
  63. package/package.json +7 -5
  64. package/src/constants.ts +13 -0
  65. package/src/formats/bru/index.ts +52 -0
  66. package/src/formats/bru/tests/parseBruEnvironment.spec.js +63 -0
  67. package/src/formats/bru/tests/redact-large-text-blocks.spec.js +472 -0
  68. package/src/formats/bru/utils/redact-large-text-blocks.ts +143 -0
  69. package/src/formats/yml/common/actions.ts +1 -1
  70. package/src/formats/yml/common/app.ts +27 -0
  71. package/src/formats/yml/common/auth.ts +53 -1
  72. package/src/formats/yml/common/body.spec.ts +62 -0
  73. package/src/formats/yml/common/body.ts +27 -9
  74. package/src/formats/yml/common/datatype.spec.ts +110 -0
  75. package/src/formats/yml/common/datatype.ts +57 -0
  76. package/src/formats/yml/common/headers.ts +4 -0
  77. package/src/formats/yml/common/variables.spec.ts +127 -0
  78. package/src/formats/yml/common/variables.ts +14 -22
  79. package/src/formats/yml/index.ts +5 -1
  80. package/src/formats/yml/items/parseApp.ts +37 -0
  81. package/src/formats/yml/items/parseGraphQLRequest.ts +14 -16
  82. package/src/formats/yml/items/parseGrpcRequest.ts +24 -3
  83. package/src/formats/yml/items/parseHttpRequest.ts +19 -16
  84. package/src/formats/yml/items/parseWebsocketRequest.ts +29 -9
  85. package/src/formats/yml/items/stringifyApp.ts +45 -0
  86. package/src/formats/yml/items/stringifyGraphQLRequest.ts +7 -6
  87. package/src/formats/yml/items/stringifyGrpcRequest.ts +9 -11
  88. package/src/formats/yml/items/stringifyHttpRequest.ts +14 -6
  89. package/src/formats/yml/items/stringifyWebsocketRequest.ts +22 -9
  90. package/src/formats/yml/mockServerTypes.ts +95 -0
  91. package/src/formats/yml/parseCollection.spec.ts +193 -0
  92. package/src/formats/yml/parseCollection.ts +24 -6
  93. package/src/formats/yml/parseEnvironment.spec.ts +99 -0
  94. package/src/formats/yml/parseEnvironment.ts +41 -19
  95. package/src/formats/yml/parseFolder.spec.ts +70 -0
  96. package/src/formats/yml/parseFolder.ts +19 -22
  97. package/src/formats/yml/parseItem.spec.ts +147 -0
  98. package/src/formats/yml/parseItem.ts +4 -0
  99. package/src/formats/yml/parseMockServer.spec.ts +180 -0
  100. package/src/formats/yml/parseMockServer.ts +113 -0
  101. package/src/formats/yml/stringifyCollection.spec.ts +223 -0
  102. package/src/formats/yml/stringifyCollection.ts +26 -9
  103. package/src/formats/yml/stringifyEnvironment.spec.ts +91 -0
  104. package/src/formats/yml/stringifyEnvironment.ts +55 -20
  105. package/src/formats/yml/stringifyFolder.spec.ts +70 -0
  106. package/src/formats/yml/stringifyFolder.ts +7 -2
  107. package/src/formats/yml/stringifyItem.spec.ts +53 -0
  108. package/src/formats/yml/stringifyItem.ts +4 -0
  109. package/src/formats/yml/stringifyMockServer.spec.ts +177 -0
  110. package/src/formats/yml/stringifyMockServer.ts +129 -0
  111. package/src/formats/yml/tests/defaultEnvironment.spec.js +50 -0
  112. package/src/formats/yml/tests/fixtures/presets/empty-request.yml +7 -0
  113. package/src/formats/yml/tests/fixtures/presets/no-presets.yml +3 -0
  114. package/src/formats/yml/tests/fixtures/presets/no-request-key.yml +6 -0
  115. package/src/formats/yml/tests/fixtures/presets/type-only-realistic.yml +24 -0
  116. package/src/formats/yml/tests/fixtures/presets/url-only-realistic.yml +25 -0
  117. package/src/formats/yml/tests/fixtures/presets/with-type-and-url.yml +9 -0
  118. package/src/formats/yml/tests/parseCollection.spec.js +63 -0
  119. package/src/formats/yml/tests/parseEnvironment.spec.js +24 -63
  120. package/src/formats/yml/tests/parseEnvironment.spec.ts +160 -0
  121. package/src/formats/yml/tests/stringifyEnvironment.spec.ts +230 -0
  122. package/src/formats/yml/tests/variables.spec.js +7 -7
  123. package/src/formats/yml/utils.ts +1 -1
  124. package/src/index.ts +39 -1
  125. package/src/types/bruno-lang.d.ts +1 -0
  126. package/src/types.ts +8 -0
  127. package/src/utils/secretsJsonToEnvironments.spec.ts +211 -0
  128. package/src/utils/secretsJsonToEnvironments.ts +129 -0
  129. package/src/workers/WorkerQueue/index.ts +10 -2
  130. package/src/workers/index.ts +25 -1
  131. package/src/workers/worker-script.ts +45 -21
  132. package/dist/formats/bru/tests/example-status-swap.spec.d.ts +0 -2
  133. package/dist/formats/bru/tests/example-status-swap.spec.d.ts.map +0 -1
  134. package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts +0 -57
  135. package/dist/formats/bru/tests/fixtures/oauth2-additional-params.d.ts.map +0 -1
  136. package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts +0 -2
  137. package/dist/formats/bru/tests/oauth2-additional-params.spec.d.ts.map +0 -1
  138. package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts +0 -2
  139. package/dist/formats/bru/tests/request-parse-and-redact-body-data.spec.d.ts.map +0 -1
  140. package/dist/formats/yml/tests/parseEnvironment.spec.d.ts +0 -2
  141. package/dist/formats/yml/tests/parseEnvironment.spec.d.ts.map +0 -1
  142. package/dist/formats/yml/tests/variables.spec.d.ts +0 -2
  143. package/dist/formats/yml/tests/variables.spec.d.ts.map +0 -1
@@ -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
- export const isTypedValue = (value: unknown): value is VariableTypedValue => {
7
- return (
8
- typeof value === 'object'
9
- && value !== null
10
- && !Array.isArray(value)
11
- && 'type' in value
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 variable: BrunoVariable = {
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
- variable.value = ensureString(v.value.data);
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
- variable.value = ensureString(v.value);
71
+ base.value = ensureString(v.value);
80
72
  }
81
73
 
82
74
  if (v.description) {
83
- variable.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || '';
75
+ base.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || '';
84
76
  }
85
77
 
86
- reqVars.push(variable);
78
+ reqVars.push(base);
87
79
  });
88
80
 
89
81
  return { req: reqVars, res: [] };
@@ -2,19 +2,23 @@ import parseYmlItem from './parseItem';
2
2
  import parseYmlFolder from './parseFolder';
3
3
  import parseYmlCollection from './parseCollection';
4
4
  import parseYmlEnvironment from './parseEnvironment';
5
+ import parseYmlMockServer from './parseMockServer';
5
6
 
6
7
  import stringifyYmlItem from './stringifyItem';
7
8
  import stringifyYmlFolder from './stringifyFolder';
8
9
  import stringifyYmlCollection from './stringifyCollection';
9
10
  import stringifyYmlEnvironment from './stringifyEnvironment';
11
+ import stringifyYmlMockServer from './stringifyMockServer';
10
12
 
11
13
  export {
12
14
  parseYmlItem,
13
15
  parseYmlFolder,
14
16
  parseYmlCollection,
15
17
  parseYmlEnvironment,
18
+ parseYmlMockServer,
16
19
  stringifyYmlItem,
17
20
  stringifyYmlFolder,
18
21
  stringifyYmlCollection,
19
- stringifyYmlEnvironment
22
+ stringifyYmlEnvironment,
23
+ stringifyYmlMockServer
20
24
  };
@@ -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;
@@ -9,6 +9,8 @@ import { toBrunoPostResponseVariables } from '../common/actions';
9
9
  import { toBrunoScripts } from '../common/scripts';
10
10
  import { toBrunoAssertions } from '../common/assertions';
11
11
  import { uuid, ensureString } from '../../../utils';
12
+ import { utils } from '@usebruno/common';
13
+ const { toBool, toNumber } = utils;
12
14
 
13
15
  const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
14
16
  const info = ocRequest.info;
@@ -98,15 +100,19 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
98
100
  pathname: null
99
101
  };
100
102
 
103
+ // description
104
+ if (info?.description) {
105
+ const desc = typeof info.description === 'string' ? info.description : (info.description as any)?.content || '';
106
+ if (desc.trim().length) {
107
+ brunoItem.description = desc;
108
+ }
109
+ }
110
+
101
111
  // settings
102
112
  if (ocRequest.settings) {
103
113
  const settings: BrunoHttpItemSettings = {};
104
114
 
105
- if (typeof ocRequest.settings.encodeUrl === 'boolean') {
106
- settings.encodeUrl = ocRequest.settings.encodeUrl;
107
- } else {
108
- settings.encodeUrl = true;
109
- }
115
+ settings.encodeUrl = toBool(ocRequest.settings.encodeUrl, true);
110
116
 
111
117
  if (typeof ocRequest.settings.timeout === 'number') {
112
118
  settings.timeout = ocRequest.settings.timeout;
@@ -116,17 +122,9 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
116
122
  settings.timeout = 0;
117
123
  }
118
124
 
119
- if (typeof ocRequest.settings.followRedirects === 'boolean') {
120
- settings.followRedirects = ocRequest.settings.followRedirects;
121
- } else {
122
- settings.followRedirects = true;
123
- }
124
-
125
- if (typeof ocRequest.settings.maxRedirects === 'number') {
126
- settings.maxRedirects = ocRequest.settings.maxRedirects;
127
- } else {
128
- settings.maxRedirects = 5;
129
- }
125
+ settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
126
+ settings.maxRedirects = toNumber(ocRequest.settings.maxRedirects, 5);
127
+ settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
130
128
 
131
129
  brunoItem.settings = settings;
132
130
  }
@@ -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
- if (isNonEmptyString(grpc?.message)) {
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: grpc?.message as string
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,7 +9,10 @@ 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';
14
+ import { utils } from '@usebruno/common';
15
+ const { toBool, toNumber } = utils;
13
16
 
14
17
  const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
15
18
  const info = ocRequest.info;
@@ -79,6 +82,9 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
79
82
  brunoRequest.docs = ocRequest.docs;
80
83
  }
81
84
 
85
+ // app
86
+ const app = toBrunoApp((ocRequest as any).app);
87
+
82
88
  // bruno item
83
89
  const brunoItem: BrunoItem = {
84
90
  uid: uuid(),
@@ -88,6 +94,7 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
88
94
  tags: info?.tags || [],
89
95
  request: brunoRequest,
90
96
  settings: null,
97
+ app,
91
98
  fileContent: null,
92
99
  root: null,
93
100
  items: [],
@@ -96,15 +103,19 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
96
103
  pathname: null
97
104
  };
98
105
 
106
+ // description
107
+ if (info?.description) {
108
+ const desc = typeof info.description === 'string' ? info.description : (info.description as any)?.content || '';
109
+ if (desc.trim().length) {
110
+ brunoItem.description = desc;
111
+ }
112
+ }
113
+
99
114
  // settings
100
115
  if (ocRequest.settings) {
101
116
  const settings: BrunoHttpItemSettings = {};
102
117
 
103
- if (typeof ocRequest.settings.encodeUrl === 'boolean') {
104
- settings.encodeUrl = ocRequest.settings.encodeUrl;
105
- } else {
106
- settings.encodeUrl = true;
107
- }
118
+ settings.encodeUrl = toBool(ocRequest.settings.encodeUrl, true);
108
119
 
109
120
  if (typeof ocRequest.settings.timeout === 'number') {
110
121
  settings.timeout = ocRequest.settings.timeout;
@@ -114,17 +125,9 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
114
125
  settings.timeout = 0;
115
126
  }
116
127
 
117
- if (typeof ocRequest.settings.followRedirects === 'boolean') {
118
- settings.followRedirects = ocRequest.settings.followRedirects;
119
- } else {
120
- settings.followRedirects = true;
121
- }
122
-
123
- if (typeof ocRequest.settings.maxRedirects === 'number') {
124
- settings.maxRedirects = ocRequest.settings.maxRedirects;
125
- } else {
126
- settings.maxRedirects = 5;
127
- }
128
+ settings.followRedirects = toBool(ocRequest.settings.followRedirects, true);
129
+ settings.maxRedirects = toNumber(ocRequest.settings.maxRedirects, 5);
130
+ settings.forwardAuthorizationHeader = toBool(ocRequest.settings.forwardAuthorizationHeader, true);
128
131
 
129
132
  brunoItem.settings = settings;
130
133
  }
@@ -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
- const message = websocket.message as WebSocketMessage;
39
- const messageData = ensureString(message.data);
40
- if (messageData.trim().length) {
41
- brunoRequest.body.ws = [{
42
- name: '',
43
- type: message.type || 'text',
44
- content: messageData
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;
@@ -16,6 +16,7 @@ import { toOpenCollectionVariables } from '../common/variables';
16
16
  import { toOpenCollectionActions } from '../common/actions';
17
17
  import { toOpenCollectionScripts } from '../common/scripts';
18
18
  import { toOpenCollectionAssertions } from '../common/assertions';
19
+ import { resolveTimeoutSetting } from '@usebruno/common/utils';
19
20
 
20
21
  const stringifyGraphQLRequest = (item: BrunoItem): string => {
21
22
  try {
@@ -33,6 +34,9 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
33
34
  if (item.tags?.length) {
34
35
  info.tags = item.tags;
35
36
  }
37
+ if (isNonEmptyString(item.description)) {
38
+ info.description = item.description;
39
+ }
36
40
  ocRequest.info = info;
37
41
 
38
42
  // graphql block
@@ -129,12 +133,7 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
129
133
  settings.encodeUrl = true;
130
134
  }
131
135
 
132
- const timeout = httpSettings?.timeout;
133
- if (isNumber(timeout)) {
134
- settings.timeout = timeout;
135
- } else {
136
- settings.timeout = 0;
137
- }
136
+ settings.timeout = resolveTimeoutSetting(httpSettings?.timeout);
138
137
 
139
138
  if (httpSettings?.followRedirects === true) {
140
139
  settings.followRedirects = true;
@@ -151,6 +150,8 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => {
151
150
  settings.maxRedirects = 5;
152
151
  }
153
152
 
153
+ settings.forwardAuthorizationHeader = httpSettings?.forwardAuthorizationHeader ?? true;
154
+
154
155
  ocRequest.settings = settings;
155
156
 
156
157
  // docs
@@ -1,7 +1,7 @@
1
1
  import type { Item as BrunoItem } from '@usebruno/schema-types/collection/item';
2
2
  import type { KeyValue as BrunoKeyValue } from '@usebruno/schema-types/common/key-value';
3
3
  import type { GrpcRequest as BrunoGrpcRequest } from '@usebruno/schema-types/requests/grpc';
4
- import type { GrpcRequest, GrpcMetadata, GrpcMessage, GrpcRequestInfo, GrpcRequestDetails, GrpcRequestRuntime } from '@opencollection/types/requests/grpc';
4
+ import type { GrpcRequest, GrpcMetadata, GrpcMessageVariant, GrpcMessage, GrpcRequestInfo, GrpcRequestDetails, GrpcRequestRuntime } from '@opencollection/types/requests/grpc';
5
5
  import type { Auth } from '@opencollection/types/common/auth';
6
6
  import type { Scripts } from '@opencollection/types/common/scripts';
7
7
  import type { Variable } from '@opencollection/types/common/variables';
@@ -29,6 +29,9 @@ const stringifyGrpcRequest = (item: BrunoItem): string => {
29
29
  if (item.tags?.length) {
30
30
  info.tags = item.tags;
31
31
  }
32
+ if (isNonEmptyString(item.description)) {
33
+ info.description = item.description;
34
+ }
32
35
  ocRequest.info = info;
33
36
 
34
37
  // grpc block
@@ -73,16 +76,11 @@ const stringifyGrpcRequest = (item: BrunoItem): string => {
73
76
 
74
77
  // message
75
78
  if (brunoRequest.body?.mode === 'grpc' && brunoRequest.body.grpc?.length) {
76
- const messages = brunoRequest.body.grpc;
77
-
78
- // todo: bruno app supports only one message for now
79
- // update this when bruno app supports multiple messages
80
- if (messages.length) {
81
- const message: GrpcMessage = messages[0].content || '';
82
- if (message.trim().length) {
83
- grpc.message = message;
84
- }
85
- }
79
+ const messages: GrpcMessageVariant[] = brunoRequest.body.grpc.map(({ name, content }, index) => ({
80
+ title: name || `message ${index + 1}`,
81
+ message: content || ''
82
+ }));
83
+ grpc.message = messages;
86
84
  }
87
85
 
88
86
  // auth