@usebruno/filestore 0.11.0 → 0.13.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 (88) 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 +1 -1
  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/utils/redact-large-text-blocks.d.ts.map +1 -1
  12. package/dist/formats/yml/common/omit-headers.d.ts +3 -0
  13. package/dist/formats/yml/common/omit-headers.d.ts.map +1 -0
  14. package/dist/formats/yml/common/scripts.d.ts +3 -5
  15. package/dist/formats/yml/common/scripts.d.ts.map +1 -1
  16. package/dist/formats/yml/index.d.ts +3 -1
  17. package/dist/formats/yml/index.d.ts.map +1 -1
  18. package/dist/formats/yml/items/parseGraphQLRequest.d.ts.map +1 -1
  19. package/dist/formats/yml/items/parseGrpcRequest.d.ts.map +1 -1
  20. package/dist/formats/yml/items/parseHttpRequest.d.ts.map +1 -1
  21. package/dist/formats/yml/items/stringifyGraphQLRequest.d.ts.map +1 -1
  22. package/dist/formats/yml/items/stringifyGrpcRequest.d.ts.map +1 -1
  23. package/dist/formats/yml/items/stringifyHttpRequest.d.ts.map +1 -1
  24. package/dist/formats/yml/items/stringifyWebsocketRequest.d.ts.map +1 -1
  25. package/dist/formats/yml/mockServerTypes.d.ts +95 -0
  26. package/dist/formats/yml/mockServerTypes.d.ts.map +1 -0
  27. package/dist/formats/yml/parseCollection.d.ts.map +1 -1
  28. package/dist/formats/yml/parseEnvironment.d.ts.map +1 -1
  29. package/dist/formats/yml/parseFolder.d.ts.map +1 -1
  30. package/dist/formats/yml/parseMockServer.d.ts +4 -0
  31. package/dist/formats/yml/parseMockServer.d.ts.map +1 -0
  32. package/dist/formats/yml/stringifyCollection.d.ts.map +1 -1
  33. package/dist/formats/yml/stringifyEnvironment.d.ts.map +1 -1
  34. package/dist/formats/yml/stringifyFolder.d.ts.map +1 -1
  35. package/dist/formats/yml/stringifyMockServer.d.ts +4 -0
  36. package/dist/formats/yml/stringifyMockServer.d.ts.map +1 -0
  37. package/dist/index.d.ts +14 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/types.d.ts +1 -0
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/workers/WorkerQueue/index.d.ts +3 -2
  42. package/dist/workers/WorkerQueue/index.d.ts.map +1 -1
  43. package/dist/workers/index.d.ts +4 -0
  44. package/dist/workers/index.d.ts.map +1 -1
  45. package/package.json +8 -8
  46. package/src/constants.ts +5 -1
  47. package/src/formats/bru/tests/redact-large-text-blocks.spec.js +220 -1
  48. package/src/formats/bru/utils/redact-large-text-blocks.ts +46 -1
  49. package/src/formats/yml/common/omit-headers.spec.ts +16 -0
  50. package/src/formats/yml/common/omit-headers.ts +12 -0
  51. package/src/formats/yml/common/scripts.spec.ts +155 -0
  52. package/src/formats/yml/common/scripts.ts +55 -29
  53. package/src/formats/yml/grpc-round-trip.spec.ts +110 -0
  54. package/src/formats/yml/index.ts +5 -1
  55. package/src/formats/yml/items/parseGraphQLRequest.ts +6 -16
  56. package/src/formats/yml/items/parseGrpcRequest.ts +14 -6
  57. package/src/formats/yml/items/parseHttpRequest.ts +10 -14
  58. package/src/formats/yml/items/stringifyGraphQLRequest.ts +8 -14
  59. package/src/formats/yml/items/stringifyGrpcRequest.ts +2 -1
  60. package/src/formats/yml/items/stringifyHttpRequest.ts +13 -13
  61. package/src/formats/yml/items/stringifyWebsocketRequest.ts +3 -1
  62. package/src/formats/yml/mockServerTypes.ts +95 -0
  63. package/src/formats/yml/parseCollection.spec.ts +123 -0
  64. package/src/formats/yml/parseCollection.ts +23 -14
  65. package/src/formats/yml/parseEnvironment.spec.ts +49 -0
  66. package/src/formats/yml/parseEnvironment.ts +6 -0
  67. package/src/formats/yml/parseFolder.spec.ts +18 -0
  68. package/src/formats/yml/parseFolder.ts +5 -1
  69. package/src/formats/yml/parseMockServer.spec.ts +180 -0
  70. package/src/formats/yml/parseMockServer.ts +113 -0
  71. package/src/formats/yml/script.spec.ts +159 -0
  72. package/src/formats/yml/stringifyCollection.spec.ts +146 -0
  73. package/src/formats/yml/stringifyCollection.ts +29 -19
  74. package/src/formats/yml/stringifyEnvironment.spec.ts +58 -0
  75. package/src/formats/yml/stringifyEnvironment.ts +6 -0
  76. package/src/formats/yml/stringifyFolder.spec.ts +23 -0
  77. package/src/formats/yml/stringifyFolder.ts +10 -3
  78. package/src/formats/yml/stringifyMockServer.spec.ts +177 -0
  79. package/src/formats/yml/stringifyMockServer.ts +129 -0
  80. package/src/formats/yml/tests/defaultEnvironment.spec.js +50 -0
  81. package/src/formats/yml/tests/stringifyEnvironment.spec.ts +26 -0
  82. package/src/formats/yml/utils.ts +1 -1
  83. package/src/index.ts +34 -1
  84. package/src/maxRedirects.spec.ts +144 -0
  85. package/src/types.ts +1 -0
  86. package/src/workers/WorkerQueue/index.ts +10 -2
  87. package/src/workers/index.ts +25 -1
  88. package/src/workers/worker-script.ts +45 -21
@@ -0,0 +1,129 @@
1
+ import { stringifyYml } from './utils';
2
+ import { isNonEmptyString } from '../../utils';
3
+ import { toOpenCollectionHttpHeaders, toOpenCollectionResponseHeaders } from './common/headers';
4
+ import { toOpenCollectionParams } from './common/params';
5
+ import { toOpenCollectionBody } from './common/body';
6
+ import type {
7
+ MockServerFile,
8
+ MockRouteEntry,
9
+ BrunoMockServer,
10
+ BrunoMockRoute
11
+ } from './mockServerTypes';
12
+
13
+ const toOcRuleConditions = (conditions: BrunoMockRoute['rules']['conditions']) => (
14
+ conditions.map((condition) => ({
15
+ target: condition.target || '',
16
+ key: condition.key || '',
17
+ operator: condition.operator || 'equals',
18
+ value: condition.value || ''
19
+ }))
20
+ );
21
+
22
+ const toOcMockRoute = (route: BrunoMockRoute): MockRouteEntry => {
23
+ const ocRoute: MockRouteEntry = {
24
+ name: route.name || 'Untitled Mock Response'
25
+ };
26
+
27
+ if (isNonEmptyString(route.description)) {
28
+ ocRoute.description = route.description;
29
+ }
30
+
31
+ ocRoute.request = {
32
+ method: route.request?.method || 'GET',
33
+ url: route.request?.url || '/'
34
+ };
35
+
36
+ const requestHeaders = toOpenCollectionHttpHeaders(route.request?.headers);
37
+ if (requestHeaders) {
38
+ ocRoute.request.headers = requestHeaders;
39
+ }
40
+
41
+ const requestParams = toOpenCollectionParams(route.request?.params);
42
+ if (requestParams) {
43
+ ocRoute.request.params = requestParams;
44
+ }
45
+
46
+ const requestBody = toOpenCollectionBody(route.request?.body);
47
+ if (requestBody !== undefined && !Array.isArray(requestBody)) {
48
+ ocRoute.request.body = requestBody;
49
+ }
50
+
51
+ ocRoute.response = {
52
+ status: Number(route.response?.status) || 200
53
+ };
54
+
55
+ if (isNonEmptyString(route.response?.statusText)) {
56
+ ocRoute.response.statusText = route.response.statusText;
57
+ }
58
+
59
+ const responseHeaders = toOpenCollectionResponseHeaders(route.response?.headers);
60
+ if (responseHeaders) {
61
+ ocRoute.response.headers = responseHeaders;
62
+ }
63
+
64
+ const responseBody = route.response?.body;
65
+ if (responseBody && responseBody.type) {
66
+ const content = responseBody.content;
67
+ ocRoute.response.body = {
68
+ type: responseBody.type,
69
+ data: typeof content === 'string' ? content : JSON.stringify(content, null, 2)
70
+ };
71
+ }
72
+
73
+ if (route.rules?.conditions?.length) {
74
+ ocRoute.rules = {
75
+ operator: route.rules.operator === 'OR' ? 'OR' : 'AND',
76
+ conditions: toOcRuleConditions(route.rules.conditions)
77
+ };
78
+ }
79
+
80
+ if (route.copiedFrom && (route.copiedFrom.exampleName || route.copiedFrom.requestPathname)) {
81
+ ocRoute.copiedFrom = {};
82
+ if (route.copiedFrom.exampleName) {
83
+ ocRoute.copiedFrom.example = route.copiedFrom.exampleName;
84
+ }
85
+ if (route.copiedFrom.requestPathname) {
86
+ ocRoute.copiedFrom.requestPath = route.copiedFrom.requestPathname;
87
+ }
88
+ }
89
+
90
+ return ocRoute;
91
+ };
92
+
93
+ const stringifyMockServer = (mockServer: BrunoMockServer): string => {
94
+ try {
95
+ const ocMockServer: MockServerFile = {
96
+ info: {
97
+ name: mockServer.name && mockServer.name.trim().length ? mockServer.name : 'Mock Server',
98
+ type: 'mock'
99
+ },
100
+ mock: {
101
+ port: Number(mockServer.port) || 4000
102
+ }
103
+ };
104
+
105
+ const delay = Number(mockServer.delay);
106
+ if (delay > 0) {
107
+ ocMockServer.mock!.delay = delay;
108
+ }
109
+
110
+ const source = mockServer.source;
111
+ if (source && (source.type === 'collection' || source.type === 'spec') && source.path) {
112
+ ocMockServer.mock!.source = {
113
+ type: source.type,
114
+ path: source.path
115
+ };
116
+ }
117
+
118
+ if (mockServer.routes?.length) {
119
+ ocMockServer.routes = mockServer.routes.map(toOcMockRoute);
120
+ }
121
+
122
+ return stringifyYml(ocMockServer);
123
+ } catch (error) {
124
+ console.error('Error stringifying mock server:', error);
125
+ throw error;
126
+ }
127
+ };
128
+
129
+ export default stringifyMockServer;
@@ -0,0 +1,50 @@
1
+ import parseCollection from '../parseCollection';
2
+ import stringifyCollection from '../stringifyCollection';
3
+
4
+ const collectionRoot = { meta: null, request: null, docs: null };
5
+
6
+ describe('yml collection - presets.defaultEnvironment round-trip', () => {
7
+ it('writes and reads back defaultEnvironment via extensions.bruno.presets', () => {
8
+ const brunoConfig = {
9
+ name: 'My API',
10
+ type: 'collection',
11
+ ignore: ['node_modules'],
12
+ presets: { requestType: 'http', requestUrl: 'https://example.com', defaultEnvironment: 'prod' }
13
+ };
14
+
15
+ const yml = stringifyCollection(collectionRoot, brunoConfig);
16
+ expect(yml).toContain('defaultEnvironment: prod');
17
+
18
+ const { brunoConfig: parsed } = parseCollection(yml);
19
+ expect(parsed.presets).toEqual({
20
+ requestType: 'http',
21
+ requestUrl: 'https://example.com',
22
+ defaultEnvironment: 'prod'
23
+ });
24
+ });
25
+
26
+ it('persists defaultEnvironment even when no request presets are set', () => {
27
+ const brunoConfig = {
28
+ name: 'My API',
29
+ type: 'collection',
30
+ ignore: [],
31
+ presets: { defaultEnvironment: 'staging' }
32
+ };
33
+
34
+ const yml = stringifyCollection(collectionRoot, brunoConfig);
35
+ expect(yml).toContain('defaultEnvironment: staging');
36
+
37
+ const { brunoConfig: parsed } = parseCollection(yml);
38
+ expect(parsed.presets.defaultEnvironment).toBe('staging');
39
+ });
40
+
41
+ it('does not emit defaultEnvironment when it is not configured', () => {
42
+ const brunoConfig = { name: 'My API', type: 'collection', ignore: [] };
43
+
44
+ const yml = stringifyCollection(collectionRoot, brunoConfig);
45
+ expect(yml).not.toContain('defaultEnvironment');
46
+
47
+ const { brunoConfig: parsed } = parseCollection(yml);
48
+ expect(parsed.presets).toBeUndefined();
49
+ });
50
+ });
@@ -128,6 +128,32 @@ externalSecrets:
128
128
  `);
129
129
  });
130
130
 
131
+ it('should stringify a gcp-secrets-manager provider block, one row per project', () => {
132
+ const output = stringifyEnvironment({
133
+ uid: 'env1',
134
+ name: 'test',
135
+ variables: [],
136
+ externalSecrets: {
137
+ type: 'gcp-secrets-manager',
138
+ variables: [
139
+ { uid: 'e1', name: 'staging', projectId: 'my-staging-project', enabled: true },
140
+ { uid: 'e2', name: 'prod', projectId: 'my-prod-project', enabled: true }
141
+ ]
142
+ },
143
+ color: null
144
+ } as any);
145
+
146
+ expect(output).toBe(`name: test
147
+ externalSecrets:
148
+ type: gcp-secrets-manager
149
+ variables:
150
+ - name: staging
151
+ projectId: my-staging-project
152
+ - name: prod
153
+ projectId: my-prod-project
154
+ `);
155
+ });
156
+
131
157
  it('should keep a half-filled row (a name with an empty identifier)', () => {
132
158
  const output = stringifyEnvironment({
133
159
  uid: 'env1',
@@ -1,7 +1,7 @@
1
1
  import * as YAML from 'yaml';
2
2
 
3
3
  // Top-level keys that should have a blank line before them
4
- const BLOCK_KEYS = ['info', 'http', 'graphql', 'grpc', 'websocket', 'runtime', 'settings', 'examples', 'docs', 'items', 'request'];
4
+ const BLOCK_KEYS = ['info', 'http', 'graphql', 'grpc', 'websocket', 'mock', 'routes', 'runtime', 'settings', 'examples', 'docs', 'items', 'request'];
5
5
 
6
6
  export const stringifyYml = (obj: any): string => {
7
7
  const yamlStr = YAML.stringify(obj, {
package/src/index.ts CHANGED
@@ -13,10 +13,12 @@ import {
13
13
  parseYmlCollection,
14
14
  parseYmlFolder,
15
15
  parseYmlEnvironment,
16
+ parseYmlMockServer,
16
17
  stringifyYmlItem,
17
18
  stringifyYmlFolder,
18
19
  stringifyYmlCollection,
19
- stringifyYmlEnvironment
20
+ stringifyYmlEnvironment,
21
+ stringifyYmlMockServer
20
22
  } from './formats/yml';
21
23
  import { dotenvToJson } from '@usebruno/lang';
22
24
  import BruParserWorker from './workers';
@@ -75,6 +77,22 @@ export const stringifyRequestViaWorker = async (requestObj: any, options: { form
75
77
  return await fileParserWorker.stringifyRequest(requestObj, options.format);
76
78
  };
77
79
 
80
+ export const parseFolderViaWorker = async (content: string, options: { format: CollectionFormat }): Promise<any> => {
81
+ return await getWorkerInstance().parseFolder(content, options.format);
82
+ };
83
+
84
+ export const stringifyFolderViaWorker = async (folderObj: any, options: { format: CollectionFormat }): Promise<string> => {
85
+ return await getWorkerInstance().stringifyFolder(folderObj, options.format);
86
+ };
87
+
88
+ export const parseEnvironmentViaWorker = async (content: string, options: { format: CollectionFormat }): Promise<any> => {
89
+ return await getWorkerInstance().parseEnvironment(content, options.format);
90
+ };
91
+
92
+ export const stringifyEnvironmentViaWorker = async (envObj: any, options: { format: CollectionFormat }): Promise<string> => {
93
+ return await getWorkerInstance().stringifyEnvironment(envObj, options.format);
94
+ };
95
+
78
96
  // collection
79
97
  export const parseCollection = (content: string, options: ParseOptions = { format: DEFAULT_COLLECTION_FORMAT }): any => {
80
98
  if (options.format === 'bru') {
@@ -132,6 +150,21 @@ export const stringifyEnvironment = (envObj: BrunoEnvironment, options: Stringif
132
150
  throw new Error(`Unsupported format: ${options.format}`);
133
151
  };
134
152
 
153
+ // mock server — workspace-level entity, opencollection yml only
154
+ export const parseMockServer = (content: string, options: ParseOptions = { format: 'yml' }): any => {
155
+ if (options.format === 'yml') {
156
+ return parseYmlMockServer(content);
157
+ }
158
+ throw new Error(`Unsupported format: ${options.format}`);
159
+ };
160
+
161
+ export const stringifyMockServer = (mockServerObj: any, options: StringifyOptions = { format: 'yml' }): string => {
162
+ if (options.format === 'yml') {
163
+ return stringifyYmlMockServer(mockServerObj);
164
+ }
165
+ throw new Error(`Unsupported format: ${options.format}`);
166
+ };
167
+
135
168
  export const parseDotEnv = (content: string): Record<string, string> => {
136
169
  return dotenvToJson(content);
137
170
  };
@@ -0,0 +1,144 @@
1
+ import { parseRequest, stringifyRequest } from './index';
2
+ import { DEFAULT_MAX_REDIRECTS } from '@usebruno/common/utils';
3
+ import type { CollectionFormat } from './types';
4
+
5
+ const FORMATS: CollectionFormat[] = ['bru', 'yml'];
6
+
7
+ const requestWithMaxRedirects = (maxRedirects: number) => ({
8
+ uid: 'req-uid',
9
+ name: 'Get Users',
10
+ type: 'http-request',
11
+ seq: 1,
12
+ request: {
13
+ url: 'https://restcountries.com/v2/alpha/in',
14
+ method: 'GET',
15
+ headers: [],
16
+ params: [],
17
+ body: { mode: 'none' },
18
+ auth: { mode: 'none' },
19
+ script: {},
20
+ vars: {},
21
+ assertions: [],
22
+ tests: ''
23
+ },
24
+ settings: { maxRedirects }
25
+ });
26
+
27
+ // Serializes with Bruno's own writer and reads back, covering the write side too.
28
+ const roundTripRequest = (maxRedirects: number, format: CollectionFormat) =>
29
+ parseRequest(stringifyRequest(requestWithMaxRedirects(maxRedirects) as any, { format }), { format })
30
+ .settings.maxRedirects;
31
+
32
+ const HAND_EDITED_DOCUMENTS: Record<CollectionFormat, (rawValue: string) => string> = {
33
+ bru: (rawValue) => `meta {
34
+ name: Get Users
35
+ type: http
36
+ seq: 1
37
+ }
38
+
39
+ get {
40
+ url: https://restcountries.com/v2/alpha/in
41
+ body: none
42
+ auth: none
43
+ }
44
+
45
+ settings {
46
+ maxRedirects: ${rawValue}
47
+ }
48
+ `,
49
+ yml: (rawValue) => `info:
50
+ name: Get Users
51
+ type: http
52
+ seq: 1
53
+
54
+ http:
55
+ method: GET
56
+ url: https://restcountries.com/v2/alpha/in
57
+
58
+ settings:
59
+ maxRedirects: ${rawValue}
60
+ `
61
+ };
62
+
63
+ // Parses a document stated byte-for-byte, for values Bruno's writer would never produce
64
+ // (.inf, blank, quoted). Returns what the parser stored, so assertions can distinguish a
65
+ // dropped value from one the parser replaced with the default.
66
+ const parseHandEditedValue = (rawValue: string, format: CollectionFormat) =>
67
+ parseRequest(HAND_EDITED_DOCUMENTS[format](rawValue), { format }).settings.maxRedirects;
68
+
69
+ describe('maxRedirects on-disk round trip', () => {
70
+ describe.each(FORMATS)('%s format', (format) => {
71
+ // The formats agree on the enforced ceiling but not on what they store for an unusable value:
72
+ // bru omits the setting, yml writes the default in its place.
73
+ const storedWhenUnusable = format === 'bru' ? undefined : DEFAULT_MAX_REDIRECTS;
74
+
75
+ it.each([0, 5, 50, 51, 1000, Number.MAX_SAFE_INTEGER])(
76
+ 'preserves a maxRedirects of %p',
77
+ (maxRedirects) => {
78
+ expect(roundTripRequest(maxRedirects, format)).toBe(maxRedirects);
79
+ }
80
+ );
81
+
82
+ // parseInt stops at the exponent, so a ceiling Bruno writes as 1e+31 reads back as 1.
83
+ it.each([1e21, 1e31, 9.999999999998865e21])(
84
+ 'preserves an exponentially serialized maxRedirects of %p',
85
+ (maxRedirects) => {
86
+ expect(roundTripRequest(maxRedirects, format)).toBe(maxRedirects);
87
+ }
88
+ );
89
+
90
+ // Files written before any change to the writer's number formatting keep these exact bytes,
91
+ // so the literal exponent form must parse regardless of what the writer emits today.
92
+ it.each([
93
+ ['1e+21', 1e21],
94
+ ['9.999999999998865e+21', 9.999999999998865e21]
95
+ ])('reads an on-disk exponent literal %s in full', (rawValue, expected) => {
96
+ expect(parseHandEditedValue(rawValue, format)).toBe(expected);
97
+ });
98
+
99
+ it.each(['1e309', '-1e309', '1' + '0'.repeat(400)])(
100
+ 'does not store an on-disk %s that overflows to a non-finite number',
101
+ (rawValue) => {
102
+ expect(parseHandEditedValue(rawValue, format)).toBe(storedWhenUnusable);
103
+ }
104
+ );
105
+
106
+ it('clamps an on-disk value beyond MAX_SAFE_INTEGER to the nearest representable integer', () => {
107
+ expect(parseHandEditedValue('9007199254740993', format)).toBe(9007199254740992);
108
+ });
109
+
110
+ it.each(['', ' ', ' '])('does not store a blank on-disk value %p', (rawValue) => {
111
+ expect(parseHandEditedValue(rawValue, format)).toBe(storedWhenUnusable);
112
+ });
113
+
114
+ it.each(['-1', '-3', '-0.5'])('does not store a negative on-disk value of %s', (rawValue) => {
115
+ expect(parseHandEditedValue(rawValue, format)).toBe(storedWhenUnusable);
116
+ });
117
+ });
118
+
119
+ // Copying a request from a bru collection into a yml one adds a maxRedirects the author never
120
+ // set, since yml materializes the default where bru left the setting out.
121
+ it('stores an unusable value differently per format', () => {
122
+ expect(parseHandEditedValue('abc', 'bru')).toBeUndefined();
123
+ expect(parseHandEditedValue('abc', 'yml')).toBe(DEFAULT_MAX_REDIRECTS);
124
+ });
125
+
126
+ it('truncates a fractional maxRedirects identically in both formats', () => {
127
+ expect(roundTripRequest(3.5, 'bru')).toBe(3);
128
+ expect(roundTripRequest(3.5, 'yml')).toBe(3);
129
+ });
130
+
131
+ it.each(['Infinity', '-Infinity', 'NaN'])('does not store a hand-edited bru maxRedirects of %s', (rawValue) => {
132
+ expect(parseHandEditedValue(rawValue, 'bru')).toBeUndefined();
133
+ });
134
+
135
+ it.each(['.inf', '-.inf', '.nan'])('replaces a hand-edited yml maxRedirects of %s with the default', (rawValue) => {
136
+ expect(parseHandEditedValue(rawValue, 'yml')).toBe(DEFAULT_MAX_REDIRECTS);
137
+ });
138
+
139
+ it('does not honour a quoted yml count, but reads a plain count in both formats', () => {
140
+ expect(parseHandEditedValue('"10"', 'yml')).toBe(DEFAULT_MAX_REDIRECTS);
141
+ expect(parseHandEditedValue('10', 'yml')).toBe(10);
142
+ expect(parseHandEditedValue('10', 'bru')).toBe(10);
143
+ });
144
+ });
package/src/types.ts CHANGED
@@ -26,4 +26,5 @@ export interface BrunoPresetsExtension {
26
26
  type?: string;
27
27
  url?: string;
28
28
  };
29
+ defaultEnvironment?: string;
29
30
  }
@@ -1,10 +1,18 @@
1
1
  import { Worker } from 'node:worker_threads';
2
2
 
3
+ type WorkerTaskType
4
+ = | 'parse'
5
+ | 'stringify'
6
+ | 'parseFolder'
7
+ | 'stringifyFolder'
8
+ | 'parseEnvironment'
9
+ | 'stringifyEnvironment';
10
+
3
11
  interface QueuedTask {
4
12
  priority: number;
5
13
  scriptPath: string;
6
14
  data: any;
7
- taskType: 'parse' | 'stringify';
15
+ taskType: WorkerTaskType;
8
16
  resolve?: (value: any) => void;
9
17
  reject?: (reason?: any) => void;
10
18
  }
@@ -58,7 +66,7 @@ class WorkerQueue {
58
66
  }
59
67
  }
60
68
 
61
- async runWorker({ scriptPath, data, taskType }: { scriptPath: string; data: any; taskType: 'parse' | 'stringify' }) {
69
+ async runWorker({ scriptPath, data, taskType }: { scriptPath: string; data: any; taskType: WorkerTaskType }) {
62
70
  return new Promise(async (resolve, reject) => {
63
71
  let worker = await this.getWorkerForScriptPath(scriptPath);
64
72
 
@@ -55,7 +55,15 @@ class BruParserWorker {
55
55
  return queueForSize?.workerQueue ?? this.workerQueues[this.workerQueues.length - 1].workerQueue;
56
56
  }
57
57
 
58
- private async enqueueTask({ data, taskType, format = DEFAULT_COLLECTION_FORMAT }: { data: any; taskType: 'parse' | 'stringify'; format?: CollectionFormat }): Promise<any> {
58
+ private async enqueueTask({
59
+ data,
60
+ taskType,
61
+ format = DEFAULT_COLLECTION_FORMAT
62
+ }: {
63
+ data: any;
64
+ taskType: 'parse' | 'stringify' | 'parseFolder' | 'stringifyFolder' | 'parseEnvironment' | 'stringifyEnvironment';
65
+ format?: CollectionFormat;
66
+ }): Promise<any> {
59
67
  const size = getSize(data);
60
68
  const workerQueue = this.getWorkerQueue(size);
61
69
  const workerScriptPath = path.join(__dirname, './workers/worker-script.js');
@@ -76,6 +84,22 @@ class BruParserWorker {
76
84
  return this.enqueueTask({ data, taskType: 'stringify', format });
77
85
  }
78
86
 
87
+ async parseFolder(data: any, format: CollectionFormat = DEFAULT_COLLECTION_FORMAT): Promise<any> {
88
+ return this.enqueueTask({ data, taskType: 'parseFolder', format });
89
+ }
90
+
91
+ async stringifyFolder(data: any, format: CollectionFormat = DEFAULT_COLLECTION_FORMAT): Promise<any> {
92
+ return this.enqueueTask({ data, taskType: 'stringifyFolder', format });
93
+ }
94
+
95
+ async parseEnvironment(data: any, format: CollectionFormat = DEFAULT_COLLECTION_FORMAT): Promise<any> {
96
+ return this.enqueueTask({ data, taskType: 'parseEnvironment', format });
97
+ }
98
+
99
+ async stringifyEnvironment(data: any, format: CollectionFormat = DEFAULT_COLLECTION_FORMAT): Promise<any> {
100
+ return this.enqueueTask({ data, taskType: 'stringifyEnvironment', format });
101
+ }
102
+
79
103
  async cleanup(): Promise<void> {
80
104
  const cleanupPromises = this.workerQueues.map(({ workerQueue }) =>
81
105
  workerQueue.cleanup()
@@ -1,39 +1,63 @@
1
1
  import { parentPort } from 'node:worker_threads';
2
- import { parseBruRequest, stringifyBruRequest } from '../formats/bru';
3
- import { parseYmlItem, stringifyYmlItem } from '../formats/yml';
2
+ import {
3
+ parseBruRequest,
4
+ parseBruCollection,
5
+ parseBruEnvironment,
6
+ stringifyBruRequest,
7
+ stringifyBruCollection,
8
+ stringifyBruEnvironment
9
+ } from '../formats/bru';
10
+ import {
11
+ parseYmlItem,
12
+ parseYmlFolder,
13
+ parseYmlEnvironment,
14
+ stringifyYmlItem,
15
+ stringifyYmlFolder,
16
+ stringifyYmlEnvironment
17
+ } from '../formats/yml';
4
18
  import { CollectionFormat } from '../types';
5
19
  import { DEFAULT_COLLECTION_FORMAT } from '../constants';
6
20
 
21
+ type TaskType
22
+ = | 'parse'
23
+ | 'stringify'
24
+ | 'parseFolder'
25
+ | 'stringifyFolder'
26
+ | 'parseEnvironment'
27
+ | 'stringifyEnvironment';
28
+
7
29
  interface WorkerMessage {
8
- taskType: 'parse' | 'stringify';
30
+ taskType: TaskType;
9
31
  data: {
10
32
  data: any;
11
33
  format?: CollectionFormat;
12
34
  };
13
35
  }
14
36
 
37
+ const runTask = (taskType: TaskType, data: any, format: CollectionFormat): any => {
38
+ switch (taskType) {
39
+ case 'parse':
40
+ return format === 'yml' ? parseYmlItem(data) : parseBruRequest(data);
41
+ case 'stringify':
42
+ return format === 'yml' ? stringifyYmlItem(data) : stringifyBruRequest(data);
43
+ case 'parseFolder':
44
+ return format === 'yml' ? parseYmlFolder(data) : parseBruCollection(data);
45
+ case 'stringifyFolder':
46
+ return format === 'yml' ? stringifyYmlFolder(data) : stringifyBruCollection(data, true);
47
+ case 'parseEnvironment':
48
+ return format === 'yml' ? parseYmlEnvironment(data) : parseBruEnvironment(data);
49
+ case 'stringifyEnvironment':
50
+ return format === 'yml' ? stringifyYmlEnvironment(data) : stringifyBruEnvironment(data);
51
+ default:
52
+ throw new Error(`Unknown task type: ${taskType}`);
53
+ }
54
+ };
55
+
15
56
  parentPort?.on('message', async (message: WorkerMessage) => {
16
57
  try {
17
58
  const { taskType, data: messageData } = message;
18
59
  const { data, format = DEFAULT_COLLECTION_FORMAT } = messageData;
19
- let result: any;
20
-
21
- if (taskType === 'parse') {
22
- if (format === 'yml') {
23
- result = parseYmlItem(data);
24
- } else {
25
- result = parseBruRequest(data);
26
- }
27
- } else if (taskType === 'stringify') {
28
- if (format === 'yml') {
29
- result = stringifyYmlItem(data);
30
- } else {
31
- result = stringifyBruRequest(data);
32
- }
33
- } else {
34
- throw new Error(`Unknown task type: ${taskType}`);
35
- }
36
-
60
+ const result = runTask(taskType, data, format);
37
61
  parentPort?.postMessage(result);
38
62
  } catch (error: any) {
39
63
  console.error('Worker error:', error);