From 9738a2afb7bcd1b6e84f366aee733e4e140774bd Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Thu, 18 Dec 2025 21:19:29 +0530 Subject: [PATCH] feat: opencollection actions --- package-lock.json | 16 ++-- package.json | 1 + packages/bruno-filestore/package.json | 3 +- .../src/formats/yml/common/actions.ts | 78 +++++++++++++++++++ .../src/formats/yml/common/variables.ts | 49 ++++-------- .../formats/yml/items/parseGraphQLRequest.ts | 9 ++- .../src/formats/yml/items/parseHttpRequest.ts | 9 ++- .../yml/items/stringifyGraphQLRequest.ts | 10 +++ .../formats/yml/items/stringifyHttpRequest.ts | 10 +++ packages/bruno-filestore/tsconfig.json | 4 +- 10 files changed, 141 insertions(+), 48 deletions(-) create mode 100644 packages/bruno-filestore/src/formats/yml/common/actions.ts diff --git a/package-lock.json b/package-lock.json index 1c2c7c6c0..31a51878a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@eslint/compat": "^1.3.2", "@faker-js/faker": "^7.6.0", "@jest/globals": "^29.2.0", + "@opencollection/types": "0.3.0", "@playwright/test": "^1.51.1", "@rollup/plugin-json": "^6.1.0", "@stylistic/eslint-plugin": "^5.3.1", @@ -5680,6 +5681,13 @@ "node": ">= 8" } }, + "node_modules/@opencollection/types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@opencollection/types/-/types-0.3.0.tgz", + "integrity": "sha512-kw+co3sM4ATDQI85lgy5UmOilEHFVdNYvOjZXmnSw6PUDUTAGBiaZNdwdSXwp//o4IwKbTQb8/0I3UdjLKh+qA==", + "dev": true, + "license": "MIT" + }, "node_modules/@parcel/watcher": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", @@ -33626,7 +33634,6 @@ "devDependencies": { "@babel/preset-env": "^7.22.0", "@babel/preset-typescript": "^7.22.0", - "@opencollection/types": "0.2.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.0.1", @@ -33646,13 +33653,6 @@ "typescript": "^4.8.4" } }, - "packages/bruno-filestore/node_modules/@opencollection/types": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@opencollection/types/-/types-0.2.0.tgz", - "integrity": "sha512-Lucjjoy+ZzfdjL0/9HF6PFlNSDG/m11VZBiR2K5XU6ChJ2XXfJyKocRB2g0tm7e5zQNMoVL3oUoDJ2gexx6xyg==", - "dev": true, - "license": "MIT" - }, "packages/bruno-filestore/node_modules/@rollup/plugin-typescript": { "version": "12.3.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz", diff --git a/package.json b/package.json index c13afb087..296129af0 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@eslint/compat": "^1.3.2", "@faker-js/faker": "^7.6.0", "@jest/globals": "^29.2.0", + "@opencollection/types": "0.3.0", "@playwright/test": "^1.51.1", "@rollup/plugin-json": "^6.1.0", "@stylistic/eslint-plugin": "^5.3.1", diff --git a/packages/bruno-filestore/package.json b/packages/bruno-filestore/package.json index 771fda1ee..7f738784f 100644 --- a/packages/bruno-filestore/package.json +++ b/packages/bruno-filestore/package.json @@ -22,8 +22,6 @@ "devDependencies": { "@babel/preset-env": "^7.22.0", "@babel/preset-typescript": "^7.22.0", - "@opencollection/types": "0.2.0", - "@usebruno/schema-types": "0.0.1", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.0.1", @@ -31,6 +29,7 @@ "@types/jest": "^29.5.11", "@types/lodash": "^4.14.191", "@types/node": "^24.1.0", + "@usebruno/schema-types": "0.0.1", "babel-jest": "^29.7.0", "jest": "^29.2.0", "nanoid": "3.3.8", diff --git a/packages/bruno-filestore/src/formats/yml/common/actions.ts b/packages/bruno-filestore/src/formats/yml/common/actions.ts new file mode 100644 index 000000000..4a0006604 --- /dev/null +++ b/packages/bruno-filestore/src/formats/yml/common/actions.ts @@ -0,0 +1,78 @@ +import type { Action, ActionSetVariable, ActionVariableScope } from '@opencollection/types/common/actions'; +import type { Variable as BrunoVariable, Variables as BrunoVariables } from '@usebruno/schema-types/common/variables'; +import { uuid } from '../../../utils'; + +/** + * Convert Bruno post-response variables to OpenCollection actions. + * Post-response variables in Bruno are converted to 'set-variable' actions + * with phase 'after-response'. + */ +export const toOpenCollectionActions = (resVariables: BrunoVariables | null | undefined): Action[] | undefined => { + if (!resVariables?.length) { + return undefined; + } + + const actions: Action[] = resVariables.map((v: BrunoVariable): ActionSetVariable => { + const action: ActionSetVariable = { + type: 'set-variable', + phase: 'after-response', + selector: { + expression: v.value || '', + method: 'jsonq' + }, + variable: { + name: v.name || '', + scope: v.local ? 'request' : 'runtime' as ActionVariableScope + } + }; + + if (v.description?.trim().length) { + action.description = v.description; + } + + if (v.enabled === false) { + action.disabled = true; + } + + return action; + }); + + return actions.length > 0 ? actions : undefined; +}; + +/** + * Convert OpenCollection actions to Bruno post-response variables. + * Only 'set-variable' actions with phase 'after-response' are converted. + */ +export const toBrunoPostResponseVariables = (actions: Action[] | null | undefined): BrunoVariables => { + if (!actions?.length) { + return []; + } + + const resVars: BrunoVariables = []; + + actions.forEach((action: Action) => { + // Only process 'set-variable' actions with 'after-response' phase + if (action.type === 'set-variable' && action.phase === 'after-response') { + const setVarAction = action as ActionSetVariable; + + const variable: BrunoVariable = { + uid: uuid(), + name: setVarAction.variable?.name || '', + value: setVarAction.selector?.expression || '', + enabled: setVarAction.disabled !== true, + local: false + }; + + if (setVarAction.description) { + variable.description = typeof setVarAction.description === 'string' + ? setVarAction.description + : (setVarAction.description as any)?.content || ''; + } + + resVars.push(variable); + } + }); + + return resVars; +}; diff --git a/packages/bruno-filestore/src/formats/yml/common/variables.ts b/packages/bruno-filestore/src/formats/yml/common/variables.ts index ad5856f63..6972d4b12 100644 --- a/packages/bruno-filestore/src/formats/yml/common/variables.ts +++ b/packages/bruno-filestore/src/formats/yml/common/variables.ts @@ -3,36 +3,28 @@ import { FolderRequest as BrunoFolderRequest } from '@usebruno/schema-types/coll import { Variable as BrunoVariable, Variables as BrunoVariables } from '@usebruno/schema-types/common/variables'; import { uuid } from '../../../utils'; +/** + * Convert Bruno pre-request variables to OpenCollection variables format. + * Note: Post-response variables are now converted to actions (see actions.ts). + */ export const toOpenCollectionVariables = (variables: BrunoFolderRequest['vars'] | BrunoVariables | null | undefined): Variable[] | undefined => { - // Handle folder variables (has req/res structure) + // Handle folder variables (has req/res structure) - only use req vars const hasReqRes = variables && 'req' in variables; const reqVars = hasReqRes ? variables.req : variables as BrunoVariables; - const resVars = hasReqRes && 'res' in variables ? variables.res : []; const reqVarsArray = Array.isArray(reqVars) ? reqVars : []; - const resVarsArray = Array.isArray(resVars) ? resVars : []; - const allVars = [...reqVarsArray, ...resVarsArray]; - - if (!allVars.length) { + if (!reqVarsArray.length) { return undefined; } - const ocVariables: Variable[] = allVars.map((v: BrunoVariable, index: number): Variable => { - const isResVar = index >= reqVarsArray.length; + const ocVariables: Variable[] = reqVarsArray.map((v: BrunoVariable): Variable => { const variable: Variable = { name: v.name || '', value: v.value || '' }; - if (isResVar) { - const scopeMarker = '[post-response]'; - if (v?.description?.trim().length) { - variable.description = `${scopeMarker} ${v.description}`; - } else { - variable.description = scopeMarker; - } - } else if (v?.description?.trim().length) { + if (v?.description?.trim().length) { variable.description = v.description; } @@ -45,18 +37,18 @@ export const toOpenCollectionVariables = (variables: BrunoFolderRequest['vars'] return ocVariables.length > 0 ? ocVariables : undefined; }; +/** + * Convert OpenCollection variables to Bruno pre-request variables format. + * Note: Post-response variables come from actions (see actions.ts). + */ export const toBrunoVariables = (variables: Variable[] | null | undefined): { req: BrunoVariables; res: BrunoVariables } => { if (!variables?.length) { return { req: [], res: [] }; } - const scopeMarker = '[post-response]'; const reqVars: BrunoVariables = []; - const resVars: BrunoVariables = []; variables.forEach((v: Variable) => { - const isPostResponse = typeof v.description === 'string' && v.description.startsWith(scopeMarker); - const variable: BrunoVariable = { uid: uuid(), name: v.name || '', @@ -65,19 +57,12 @@ export const toBrunoVariables = (variables: Variable[] | null | undefined): { re local: false }; - if (isPostResponse) { - const cleanDesc = (v.description as string).substring(scopeMarker.length).trim(); - if (cleanDesc) { - variable.description = cleanDesc; - } - resVars.push(variable); - } else { - if (v.description) { - variable.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || ''; - } - reqVars.push(variable); + if (v.description) { + variable.description = typeof v.description === 'string' ? v.description : (v.description as any)?.content || ''; } + + reqVars.push(variable); }); - return { req: reqVars, res: resVars }; + return { req: reqVars, res: [] }; }; diff --git a/packages/bruno-filestore/src/formats/yml/items/parseGraphQLRequest.ts b/packages/bruno-filestore/src/formats/yml/items/parseGraphQLRequest.ts index f75a5f2e2..65b434be8 100644 --- a/packages/bruno-filestore/src/formats/yml/items/parseGraphQLRequest.ts +++ b/packages/bruno-filestore/src/formats/yml/items/parseGraphQLRequest.ts @@ -5,6 +5,7 @@ import { toBrunoAuth } from '../common/auth'; import { toBrunoHttpHeaders } from '../common/headers'; import { toBrunoParams } from '../common/params'; import { toBrunoVariables } from '../common/variables'; +import { toBrunoPostResponseVariables } from '../common/actions'; import { toBrunoScripts } from '../common/scripts'; import { toBrunoAssertions } from '../common/assertions'; import { uuid } from '../../../utils'; @@ -61,9 +62,13 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => { brunoRequest.tests = scripts.tests; } - // variables + // variables (pre-request from variables, post-response from actions) const variables = toBrunoVariables(runtime?.variables); - brunoRequest.vars = variables; + const postResponseVars = toBrunoPostResponseVariables(runtime?.actions); + brunoRequest.vars = { + req: variables.req, + res: postResponseVars + }; // assertions const assertions = toBrunoAssertions(runtime?.assertions); diff --git a/packages/bruno-filestore/src/formats/yml/items/parseHttpRequest.ts b/packages/bruno-filestore/src/formats/yml/items/parseHttpRequest.ts index 589f1d8fd..ec9a1d9c8 100644 --- a/packages/bruno-filestore/src/formats/yml/items/parseHttpRequest.ts +++ b/packages/bruno-filestore/src/formats/yml/items/parseHttpRequest.ts @@ -6,6 +6,7 @@ import { toBrunoHttpHeaders } from '../common/headers'; import { toBrunoParams } from '../common/params'; import { toBrunoBody } from '../common/body'; import { toBrunoVariables } from '../common/variables'; +import { toBrunoPostResponseVariables } from '../common/actions'; import { toBrunoScripts } from '../common/scripts'; import { toBrunoAssertions } from '../common/assertions'; import { uuid } from '../../../utils'; @@ -59,9 +60,13 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => { brunoRequest.tests = scripts.tests; } - // variables + // variables (pre-request from variables, post-response from actions) const variables = toBrunoVariables(runtime?.variables); - brunoRequest.vars = variables; + const postResponseVars = toBrunoPostResponseVariables(runtime?.actions); + brunoRequest.vars = { + req: variables.req, + res: postResponseVars + }; // assertions const assertions = toBrunoAssertions(runtime?.assertions); diff --git a/packages/bruno-filestore/src/formats/yml/items/stringifyGraphQLRequest.ts b/packages/bruno-filestore/src/formats/yml/items/stringifyGraphQLRequest.ts index 963d3f4b5..521303782 100644 --- a/packages/bruno-filestore/src/formats/yml/items/stringifyGraphQLRequest.ts +++ b/packages/bruno-filestore/src/formats/yml/items/stringifyGraphQLRequest.ts @@ -5,6 +5,7 @@ import type { Auth } from '@opencollection/types/common/auth'; import type { Scripts } from '@opencollection/types/common/scripts'; import type { Variable } from '@opencollection/types/common/variables'; import type { Assertion } from '@opencollection/types/common/assertions'; +import type { Action } from '@opencollection/types/common/actions'; import type { HttpRequestParam, HttpRequestHeader } from '@opencollection/types/requests/http'; import { stringifyYml } from '../utils'; import { isNonEmptyString, isNumber } from '../../../utils'; @@ -12,6 +13,7 @@ import { toOpenCollectionAuth } from '../common/auth'; import { toOpenCollectionHttpHeaders } from '../common/headers'; import { toOpenCollectionParams } from '../common/params'; import { toOpenCollectionVariables } from '../common/variables'; +import { toOpenCollectionActions } from '../common/actions'; import { toOpenCollectionScripts } from '../common/scripts'; import { toOpenCollectionAssertions } from '../common/assertions'; @@ -98,6 +100,14 @@ const stringifyGraphQLRequest = (item: BrunoItem): string => { hasRuntime = true; } + // actions (from post-response variables) + const resVars = brunoRequest.vars?.res; + const actions: Action[] | undefined = toOpenCollectionActions(resVars); + if (actions) { + runtime.actions = actions; + hasRuntime = true; + } + // auth const auth: Auth | undefined = toOpenCollectionAuth(brunoRequest.auth); if (auth) { diff --git a/packages/bruno-filestore/src/formats/yml/items/stringifyHttpRequest.ts b/packages/bruno-filestore/src/formats/yml/items/stringifyHttpRequest.ts index 61a4d0684..eced35b2c 100644 --- a/packages/bruno-filestore/src/formats/yml/items/stringifyHttpRequest.ts +++ b/packages/bruno-filestore/src/formats/yml/items/stringifyHttpRequest.ts @@ -5,6 +5,7 @@ import type { Auth } from '@opencollection/types/common/auth'; import type { Scripts } from '@opencollection/types/common/scripts'; import type { Variable } from '@opencollection/types/common/variables'; import type { Assertion } from '@opencollection/types/common/assertions'; +import type { Action } from '@opencollection/types/common/actions'; import type { HttpRequestParam, HttpRequestBody } from '@opencollection/types/requests/http'; import { stringifyYml } from '../utils'; import { toOpenCollectionAuth } from '../common/auth'; @@ -12,6 +13,7 @@ import { toOpenCollectionHttpHeaders, toOpenCollectionResponseHeaders } from '.. import { toOpenCollectionParams } from '../common/params'; import { toOpenCollectionBody } from '../common/body'; import { toOpenCollectionVariables } from '../common/variables'; +import { toOpenCollectionActions } from '../common/actions'; import { toOpenCollectionScripts } from '../common/scripts'; import { toOpenCollectionAssertions } from '../common/assertions'; import { isNumber, isNonEmptyString } from '../../../utils'; @@ -85,6 +87,14 @@ const stringifyHttpRequest = (item: BrunoItem): string => { hasRuntime = true; } + // actions (from post-response variables) + const resVars = brunoRequest.vars?.res; + const actions: Action[] | undefined = toOpenCollectionActions(resVars); + if (actions) { + runtime.actions = actions; + hasRuntime = true; + } + // auth const auth: Auth | undefined = toOpenCollectionAuth(brunoRequest.auth); if (auth) { diff --git a/packages/bruno-filestore/tsconfig.json b/packages/bruno-filestore/tsconfig.json index 7f8887e45..53a360ba2 100644 --- a/packages/bruno-filestore/tsconfig.json +++ b/packages/bruno-filestore/tsconfig.json @@ -22,8 +22,8 @@ "paths": { "@usebruno/schema-types": ["packages/bruno-schema-types/dist/index.d.ts"], "@usebruno/schema-types/*": ["packages/bruno-schema-types/dist/*"], - "@opencollection/types": ["packages/bruno-filestore/node_modules/@opencollection/types/dist/opencollection.d.ts"], - "@opencollection/types/*": ["packages/bruno-filestore/node_modules/@opencollection/types/dist/*"] + "@opencollection/types": ["node_modules/@opencollection/types/dist/opencollection.d.ts"], + "@opencollection/types/*": ["node_modules/@opencollection/types/dist/*"] } }, "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.d.ts"],