diff --git a/components/ironclad/actions/create-record/create-record.mjs b/components/ironclad/actions/create-record/create-record.mjs new file mode 100644 index 0000000000000..e6b3a19ba4571 --- /dev/null +++ b/components/ironclad/actions/create-record/create-record.mjs @@ -0,0 +1,103 @@ +import ironclad from "../../ironclad.app.mjs"; + +export default { + key: "ironclad-create-record", + name: "Create Record", + description: "Creates a new record in Ironclad. [See the documentation](https://developer.ironcladapp.com/reference/create-a-record)", + version: "0.0.1", + type: "action", + props: { + ironclad, + name: { + type: "string", + label: "Name", + description: "Name of the record", + }, + type: { + propDefinition: [ + ironclad, + "recordType", + ], + }, + links: { + propDefinition: [ + ironclad, + "recordId", + ], + type: "string[]", + label: "Links", + description: "Record ID's to link to the new record", + }, + parent: { + propDefinition: [ + ironclad, + "recordId", + ], + label: "Parent", + description: "Record ID to be set as the parent of the current record", + }, + children: { + propDefinition: [ + ironclad, + "recordId", + ], + type: "string[]", + label: "Children", + description: "Record ID's to be set as child records of the current record", + }, + properties: { + propDefinition: [ + ironclad, + "properties", + ], + reloadProps: true, + }, + }, + async additionalProps() { + const props = {}; + if (!this.properties?.length) { + return props; + } + const { properties } = await this.ironclad.getRecordsSchema(); + for (const property of this.properties) { + props[property] = { + type: properties[property].type === "boolean" + ? "boolean" + : "string", + label: properties[property].displayName, + description: properties[property].description ?? `Value of ${properties[property].displayName}`, + }; + } + return props; + }, + async run({ $ }) { + const { properties } = await this.ironclad.getRecordsSchema(); + const propertiesData = {}; + for (const property of this.properties) { + propertiesData[property] = { + type: properties[property].type, + value: this[property], + }; + } + + const response = await this.ironclad.createRecord({ + $, + data: { + name: this.name, + type: this.type, + links: this.links?.length && this.links.map((link) => ({ + recordId: link, + })), + parent: this.parent && { + recordId: this.parent, + }, + children: this.children?.length && this.children.map((child) => ({ + recordId: child, + })), + properties: propertiesData, + }, + }); + $.export("$summary", `Created record with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/ironclad/actions/launch-workflow/launch-workflow.mjs b/components/ironclad/actions/launch-workflow/launch-workflow.mjs new file mode 100644 index 0000000000000..f8f7c1d8b239b --- /dev/null +++ b/components/ironclad/actions/launch-workflow/launch-workflow.mjs @@ -0,0 +1,105 @@ +import ironclad from "../../ironclad.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; +import { + getAttributeDescription, parseValue, +} from "../../common/utils.mjs"; + +export default { + key: "ironclad-launch-workflow", + name: "Launch Workflow", + description: "Launches a new workflow in Ironclad. [See the documentation](https://developer.ironcladapp.com/reference/launch-a-new-workflow)", + version: "0.0.1", + type: "action", + props: { + ironclad, + templateId: { + propDefinition: [ + ironclad, + "templateId", + ], + reloadProps: true, + }, + }, + async additionalProps() { + const props = {}; + if (!this.templateId) { + return props; + } + const { schema } = await this.ironclad.getWorkflowSchema({ + templateId: this.templateId, + }); + for (const [ + key, + value, + ] of Object.entries(schema)) { + if (!value.readOnly) { + props[key] = { + type: value.type === "boolean" + ? "boolean" + : value.type === "array" + ? "string[]" + : "string", + label: value.displayName, + description: getAttributeDescription(value), + optional: (!(key === "counterpartyName") && !value.displayName.toLowerCase().includes("required")), + }; + if (key === "paperSource") { + props[key].options = [ + "Counterparty paper", + "Our paper", + ]; + } + if (key === "recordType") { + const { recordTypes } = await this.ironclad.getRecordsSchema(); + props[key].options = Object.values(recordTypes) + .map((recordType) => recordType.displayName); + } + } + } + return props; + }, + async run({ $ }) { + const { + ironclad, + templateId, + ...attributes + } = this; + + const parsedAttributes = {}; + for (const [ + key, + value, + ] of Object.entries(attributes)) { + parsedAttributes[key] = parseValue(value); + } + + try { + const response = await ironclad.launchWorkflow({ + $, + params: { + useDefaultValues: true, + }, + data: { + template: templateId, + attributes: parsedAttributes, + }, + }); + $.export("$summary", `Workflow launched successfully with ID ${response.id}`); + return response; + } catch (error) { + const msg = JSON.parse(error.message); + const { schema } = await ironclad.getWorkflowSchema({ + templateId, + }); + if (msg.code === "MISSING_PARAM") { + const paramNames = (JSON.parse(msg.param)).map((p) => `\`${schema[p].displayName}\``); + throw new ConfigurationError(`Please enter or update the following required parameters: ${paramNames.join(", ")}`); + } + if (msg.code === "INVALID_PARAM") { + const paramName = schema[msg.metadata.keyPath].displayName; + throw new ConfigurationError(`Invalid parameter: \`${paramName}\`. ${msg.message}`); + } + throw new ConfigurationError(msg.message); + } + }, +}; diff --git a/components/ironclad/actions/update-workflow/update-workflow.mjs b/components/ironclad/actions/update-workflow/update-workflow.mjs new file mode 100644 index 0000000000000..4c3a222736541 --- /dev/null +++ b/components/ironclad/actions/update-workflow/update-workflow.mjs @@ -0,0 +1,112 @@ +import ironclad from "../../ironclad.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; +import { + getAttributeDescription, parseValue, +} from "../../common/utils.mjs"; + +export default { + key: "ironclad-update-workflow", + name: "Update Workflow Metadata", + description: "Updates the metadata of an existing workflow. [See the documentation]()", + version: "0.0.1", + type: "action", + props: { + ironclad, + workflowId: { + propDefinition: [ + ironclad, + "workflowId", + ], + reloadProps: true, + }, + comment: { + type: "string", + label: "Comment", + description: "A comment that explains the updates you are making to the workflow", + optional: true, + }, + }, + async additionalProps() { + const props = {}; + if (!this.workflowId) { + return props; + } + const { schema } = await this.ironclad.getWorkflow({ + workflowId: this.workflowId, + }); + for (const [ + key, + value, + ] of Object.entries(schema)) { + if (!value?.readOnly) { + props[key] = { + type: value.type === "boolean" + ? "boolean" + : value.type === "array" + ? "string[]" + : "string", + label: value.displayName, + description: getAttributeDescription(value), + optional: true, + }; + if (key === "paperSource") { + props[key].options = [ + "Counterparty paper", + "Our paper", + ]; + } + } + } + return props; + }, + async run({ $ }) { + const { + ironclad, + workflowId, + comment, + ...attributes + } = this; + + const parsedAttributes = {}; + for (const [ + key, + value, + ] of Object.entries(attributes)) { + parsedAttributes[key] = parseValue(value); + } + + try { + const response = await ironclad.updateWorkflowMetadata({ + $, + workflowId: workflowId, + data: { + updates: Object.entries(parsedAttributes).map(([ + key, + value, + ]) => ({ + action: "set", + path: key, + value, + })), + comment: comment, + }, + }); + $.export("$summary", `Workflow ${workflowId} updated successfully`); + return response; + } catch (error) { + const msg = JSON.parse(error.message); + const { schema } = await ironclad.getWorkflow({ + workflowId, + }); + if (msg.code === "MISSING_PARAM") { + const paramNames = (JSON.parse(msg.param)).map((p) => `\`${schema[p].displayName}\``); + throw new ConfigurationError(`Please enter or update the following required parameters: ${paramNames.join(", ")}`); + } + if (msg.code === "INVALID_PARAM") { + const paramName = schema[msg.metadata.keyPath].displayName; + throw new ConfigurationError(`Invalid parameter: \`${paramName}\`. ${msg.message}`); + } + throw new ConfigurationError(msg.message); + } + }, +}; diff --git a/components/ironclad/common/utils.mjs b/components/ironclad/common/utils.mjs new file mode 100644 index 0000000000000..fcf1ae0908023 --- /dev/null +++ b/components/ironclad/common/utils.mjs @@ -0,0 +1,64 @@ +export function getAttributeDescription({ + type, displayName, elementType, +}) { + const description = `Value of ${displayName}`; + if (type === "address") { + return `${description}. Example: \`{ + "lines": [ + "325 5th Street", + "Suite 200" + ], + "locality": "San Francisco", + "region": "California", + "postcode": "94107", + "country": "USA" + }\``; + } + if (type === "monetaryAmount") { + return `${description}. Example: \`{ + "currency": "USD", + "amount": 25.37 + }\``; + } + if (type === "date") { + return `${description}. Example: \`2021-05-11T17:16:53-07:00\``; + } + if (type === "duration") { + return `${description}. Example \`{ + "years": 1, + "months": 2, + "weeks": 3, + "days": 4 + }\``; + } + if (type === "email") { + return `${description}. Example: \`test@gmail.com\``; + } + if (type === "array") { + if (elementType.type === "document") { + return `${description}. Array of type \`${elementType.type}\`. Example: \`{"url": "https://your.file.server.test/test-doc-1.docx"}\``; + } + if (elementType.type === "object") { + return `${description}. Array of type \`${elementType.type}\`. See the [docs](https://developer.ironcladapp.com/docs/launch-a-workflow#32-create-request-body-attributes) for more information about field types.`; + } + return `${description}. Array of type \`${elementType.type}\`.`; + } + return description; +} + +export function parseValue(value) { + if (!value) { + return undefined; + } + try { + if (typeof value === "string") { + return JSON.parse(value); + } + if (Array.isArray(value)) { + return value.map(JSON.parse); + } + return value; + } catch { + return value; + } +} diff --git a/components/ironclad/ironclad.app.mjs b/components/ironclad/ironclad.app.mjs index 9cd862d981fe1..5f1dd54a5d9dd 100644 --- a/components/ironclad/ironclad.app.mjs +++ b/components/ironclad/ironclad.app.mjs @@ -1,11 +1,197 @@ +import { axios } from "@pipedream/platform"; +import events from "./sources/common/events.mjs"; + export default { type: "app", app: "ironclad", - propDefinitions: {}, + propDefinitions: { + recordType: { + type: "string", + label: "Type", + description: "The type of the record", + async options() { + const { recordTypes } = await this.getRecordsSchema(); + return Object.entries(recordTypes).map(([ + key, + value, + ]) => ({ + value: key, + label: value.displayName, + })); + }, + }, + recordId: { + type: "string", + label: "Record ID", + description: "The identifier of a record", + optional: true, + async options({ page }) { + const { list } = await this.listRecords({ + params: { + page, + }, + }); + return list?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + properties: { + type: "string[]", + label: "Properties", + description: "Properties to add to the record", + async options() { + const { properties } = await this.getRecordsSchema(); + return Object.entries(properties).map(([ + key, + value, + ]) => ({ + value: key, + label: value.displayName, + })); + }, + }, + templateId: { + type: "string", + label: "Template ID", + description: "The identifier of a workflow template", + async options() { + const { list } = await this.listWorkflowSchemas(); + return list?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + workflowId: { + type: "string", + label: "Workflow ID", + description: "The identifier of a workflow", + async options({ page }) { + const { list } = await this.listWorkflows({ + params: { + page, + }, + }); + return list?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || []; + }, + }, + selectedEvents: { + type: "string[]", + label: "Selected Events", + description: "Select the Ironclad events to emit", + async options() { + return events.map((event) => ({ + label: event.replace(/_/g, " ").toUpperCase(), + value: event, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://ironcladapp.com/public/api/v1"; + }, + _makeRequest(opts = {}) { + const { + $, path, ...otherOpts + } = opts; + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + ...otherOpts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks", + ...opts, + }); + }, + deleteWebhook({ + webhookId, ...opts + }) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + ...opts, + }); + }, + getRecordsSchema(opts = {}) { + return this._makeRequest({ + path: "/records/metadata", + ...opts, + }); + }, + getWorkflow({ + workflowId, ...opts + }) { + return this._makeRequest({ + path: `/workflows/${workflowId}`, + ...opts, + }); + }, + getWorkflowSchema({ + templateId, ...opts + }) { + return this._makeRequest({ + path: `/workflow-schemas/${templateId}?form=launch`, + ...opts, + }); + }, + listWorkflowSchemas(opts = {}) { + return this._makeRequest({ + path: "/workflow-schemas?form=launch", + ...opts, + }); + }, + listWorkflows(opts = {}) { + return this._makeRequest({ + path: "/workflows", + ...opts, + }); + }, + listRecords(opts = {}) { + return this._makeRequest({ + path: "/records", + ...opts, + }); + }, + launchWorkflow(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/workflows", + ...opts, + }); + }, + createRecord(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/records", + ...opts, + }); + }, + updateWorkflowMetadata({ + workflowId, ...opts + }) { + return this._makeRequest({ + method: "PATCH", + path: `/workflows/${workflowId}/attributes`, + ...opts, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/ironclad/package.json b/components/ironclad/package.json index f5b4c2be43f39..2f5b2a90419dd 100644 --- a/components/ironclad/package.json +++ b/components/ironclad/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/ironclad", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Ironclad Components", "main": "ironclad.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/ironclad/sources/common/base.mjs b/components/ironclad/sources/common/base.mjs new file mode 100644 index 0000000000000..5728c5aab0f68 --- /dev/null +++ b/components/ironclad/sources/common/base.mjs @@ -0,0 +1,54 @@ +import ironclad from "../../ironclad.app.mjs"; + +export default { + props: { + ironclad, + db: "$.service.db", + http: "$.interface.http", + }, + hooks: { + async activate() { + const { id } = await this.ironclad.createWebhook({ + data: { + targetURL: this.http.endpoint, + events: this.getEvents(), + }, + }); + this._setHookId(id); + }, + async deactivate() { + const webhookId = this._getHookId(); + if (webhookId) { + await this.ironclad.deleteWebhook({ + webhookId, + }); + } + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + generateMeta(event) { + return { + id: event.timestamp, + summary: `New ${event.payload.event} event`, + ts: Date.parse(event.timestamp), + }; + }, + getEvents() { + throw new Error("getEvents is not implemented"); + }, + }, + async run(event) { + const { body } = event; + if (!body) { + return; + } + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, +}; diff --git a/components/ironclad/sources/common/events.mjs b/components/ironclad/sources/common/events.mjs new file mode 100644 index 0000000000000..cdf9e8e3e49d8 --- /dev/null +++ b/components/ironclad/sources/common/events.mjs @@ -0,0 +1,36 @@ +export default [ + "workflow_launched", + "workflow_updated", + "workflow_completed", + "workflow_cancelled", + "workflow_approval_status_changed", + "workflow_attribute_updated", + "workflow_comment_added", + "workflow_comment_removed", + "workflow_comment_updated", + "workflow_comment_reaction_added", + "workflow_comment_reaction_removed", + "workflow_counterparty_invite_sent", + "workflow_counterparty_invite_revoked", + "workflow_documents_added", + "workflow_documents_removed", + "workflow_documents_updated", + "workflow_documents_renamed", + "workflow_document_edited", + "workflow_changed_turn", + "workflow_paused", + "workflow_resumed", + "workflow_roles_assigned", + "workflow_signature_packet_sent", + "workflow_signature_packet_signer_first_viewed", + "workflow_signature_packet_signer_viewed", + "workflow_signature_packet_uploaded", + "workflow_signature_packet_signatures_collected", + "workflow_signature_packet_fully_signed", + "workflow_signature_packet_cancelled", + "workflow_signer_added", + "workflow_signer_removed", + "workflow_signer_reassigned", + "workflow_step_updated", + "*", +]; diff --git a/components/ironclad/sources/new-approval-event-instant/new-approval-event-instant.mjs b/components/ironclad/sources/new-approval-event-instant/new-approval-event-instant.mjs new file mode 100644 index 0000000000000..b0053bfd79597 --- /dev/null +++ b/components/ironclad/sources/new-approval-event-instant/new-approval-event-instant.mjs @@ -0,0 +1,21 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "ironclad-new-approval-event-instant", + name: "New Approval Event Instant", + description: "Emit new event when a fresh approval event is generated.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "workflow_approval_status_changed", + ]; + }, + }, + sampleEmit, +}; diff --git a/components/ironclad/sources/new-approval-event-instant/test-event.mjs b/components/ironclad/sources/new-approval-event-instant/test-event.mjs new file mode 100644 index 0000000000000..31679edf501fd --- /dev/null +++ b/components/ironclad/sources/new-approval-event-instant/test-event.mjs @@ -0,0 +1,14 @@ +export default { + "companyID": "674f5545728c89fc7fabd5ec", + "payload": { + "approvalID": "approver137e74661e0b40e38e3a52dff067cd0c", + "approvalName": "Legal", + "event": "workflow_approval_status_changed", + "status": "approved", + "userEmail": "", + "userID": "67520d8a6e3fe48d53795c29", + "workflowID": "67535b8446f877a720c22b29" + }, + "timestamp": "2024-12-06T20:52:19.625Z", + "webhookID": "67535d36de552a07db17c508" +} \ No newline at end of file diff --git a/components/ironclad/sources/new-workflow-document-event-instant/new-workflow-document-event-instant.mjs b/components/ironclad/sources/new-workflow-document-event-instant/new-workflow-document-event-instant.mjs new file mode 100644 index 0000000000000..b1adf0fbdc8fd --- /dev/null +++ b/components/ironclad/sources/new-workflow-document-event-instant/new-workflow-document-event-instant.mjs @@ -0,0 +1,25 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "ironclad-new-workflow-document-event-instant", + name: "New Workflow Document Event (Instant)", + description: "Emit new event when a workflow document event is freshly established.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvents() { + return [ + "workflow_documents_added", + "workflow_documents_removed", + "workflow_documents_updated", + "workflow_documents_renamed", + "workflow_document_edited", + ]; + }, + }, + sampleEmit, +}; diff --git a/components/ironclad/sources/new-workflow-document-event-instant/test-event.mjs b/components/ironclad/sources/new-workflow-document-event-instant/test-event.mjs new file mode 100644 index 0000000000000..f69363e5dea4c --- /dev/null +++ b/components/ironclad/sources/new-workflow-document-event-instant/test-event.mjs @@ -0,0 +1,14 @@ +export default { + "companyID": "674f5545728c89fc7fabd5ec", + "payload": { + "documentKeys": [ + "RQrFu8XdyR" + ], + "event": "workflow_documents_renamed", + "templateID": "674f55a7cba94ae9d1484c57", + "userID": "67520d8a6e3fe48d53795c29", + "workflowID": "67535b8446f877a720c22b29" + }, + "timestamp": "2024-12-06T20:49:31.127Z", + "webhookID": "67535d6e50dfae9e6b22e157" +} \ No newline at end of file diff --git a/components/ironclad/sources/new-workflow-event-instant/new-workflow-event-instant.mjs b/components/ironclad/sources/new-workflow-event-instant/new-workflow-event-instant.mjs new file mode 100644 index 0000000000000..72587a5e60088 --- /dev/null +++ b/components/ironclad/sources/new-workflow-event-instant/new-workflow-event-instant.mjs @@ -0,0 +1,28 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "ironclad-new-workflow-event-instant", + name: "New Workflow Event (Instant)", + description: "Emit new event when a new workflow event is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + events: { + propDefinition: [ + common.props.ironclad, + "selectedEvents", + ], + }, + }, + methods: { + ...common.methods, + getEvents() { + return this.events; + }, + }, + sampleEmit, +}; diff --git a/components/ironclad/sources/new-workflow-event-instant/test-event.mjs b/components/ironclad/sources/new-workflow-event-instant/test-event.mjs new file mode 100644 index 0000000000000..2284d53eaf6ec --- /dev/null +++ b/components/ironclad/sources/new-workflow-event-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "companyID": "674f5545728c89fc7fabd5ec", + "payload": { + "event": "workflow_updated", + "templateID": "674f55a7cba94ae9d1484c57", + "workflowID": "67535b8446f877a720c22b29" + }, + "timestamp": "2024-12-06T20:42:55.776Z", + "webhookID": "67535d88411fe73b06b27dd3" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfd11476845c8..b3db4135b8282 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5206,7 +5206,11 @@ importers: components/iqair_airvisual: {} - components/ironclad: {} + components/ironclad: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/iscraper: {} @@ -24480,22 +24484,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}