n8n integration (#1940)

This commit is contained in:
LawyZheng
2025-03-20 01:32:55 +08:00
committed by GitHub
parent 05433d68d2
commit d764f5dd12
19 changed files with 13899 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[package.json]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[*.yml]
indent_style = space
indent_size = 2

View File

@@ -0,0 +1,58 @@
/**
* @type {import('@types/eslint').ESLint.ConfigData}
*/
module.exports = {
root: true,
env: {
browser: true,
es6: true,
node: true,
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: ["./tsconfig.json"],
sourceType: "module",
extraFileExtensions: [".json"],
},
ignorePatterns: [
".eslintrc.js",
"**/*.js",
"**/node_modules/**",
"**/dist/**",
],
overrides: [
{
files: ["package.json"],
plugins: ["eslint-plugin-n8n-nodes-base"],
extends: ["plugin:n8n-nodes-base/community"],
rules: {
"n8n-nodes-base/community-package-json-name-still-default": "off",
},
},
{
files: ["./credentials/**/*.ts"],
plugins: ["eslint-plugin-n8n-nodes-base"],
extends: ["plugin:n8n-nodes-base/credentials"],
rules: {
"n8n-nodes-base/cred-class-field-documentation-url-missing": "off",
"n8n-nodes-base/cred-class-field-documentation-url-miscased": "off",
},
},
{
files: ["./nodes/**/*.ts"],
plugins: ["eslint-plugin-n8n-nodes-base"],
extends: ["plugin:n8n-nodes-base/nodes"],
rules: {
"n8n-nodes-base/node-execute-block-missing-continue-on-fail": "off",
"n8n-nodes-base/node-resource-description-filename-against-convention":
"off",
"n8n-nodes-base/node-param-fixed-collection-type-unsorted-items": "off",
},
},
],
};

View File

@@ -0,0 +1,16 @@
/**
* @type {import('@types/eslint').ESLint.ConfigData}
*/
module.exports = {
extends: "./.eslintrc.js",
overrides: [
{
files: ["package.json"],
plugins: ["eslint-plugin-n8n-nodes-base"],
rules: {
"n8n-nodes-base/community-package-json-name-still-default": "error",
},
},
],
};

8
integrations/n8n/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules
.DS_Store
.tmp
tmp
dist
npm-debug.log*
yarn.lock
.vscode/launch.json

View File

@@ -0,0 +1,4 @@
.DS_Store
*.tsbuildinfo
node_modules/
dist/

View File

@@ -0,0 +1,51 @@
module.exports = {
/**
* https://prettier.io/docs/en/options.html#semicolons
*/
semi: true,
/**
* https://prettier.io/docs/en/options.html#trailing-commas
*/
trailingComma: "all",
/**
* https://prettier.io/docs/en/options.html#bracket-spacing
*/
bracketSpacing: true,
/**
* https://prettier.io/docs/en/options.html#tabs
*/
useTabs: true,
/**
* https://prettier.io/docs/en/options.html#tab-width
*/
tabWidth: 2,
/**
* https://prettier.io/docs/en/options.html#arrow-function-parentheses
*/
arrowParens: "always",
/**
* https://prettier.io/docs/en/options.html#quotes
*/
singleQuote: true,
/**
* https://prettier.io/docs/en/options.html#quote-props
*/
quoteProps: "as-needed",
/**
* https://prettier.io/docs/en/options.html#end-of-line
*/
endOfLine: "lf",
/**
* https://prettier.io/docs/en/options.html#print-width
*/
printWidth: 100,
};

View File

@@ -0,0 +1,90 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contributor Covenant Code of Conduct](#contributor-covenant-code-of-conduct)
- [Our Pledge](#our-pledge)
- [Our Standards](#our-standards)
- [Our Responsibilities](#our-responsibilities)
- [Scope](#scope)
- [Enforcement](#enforcement)
- [Attribution](#attribution)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at jan@n8n.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@@ -0,0 +1,19 @@
Copyright 2022 n8n
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,45 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [n8n-nodes-skyvern](#n8n-nodes-skyvern)
- [Installation](#installation)
- [Operations](#operations)
- [Credentials](#credentials)
- [Resources](#resources)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# n8n-nodes-skyvern
This is an n8n community node. It lets you use [Skyvern](https://www.skyvern.com/) in your n8n workflows.
[Skyvern](https://www.skyvern.com/) is an [open-source](https://github.com/Skyvern-AI/skyvern) product. It automates browser-based workflows using LLMs and computer vision. It provides a simple API endpoint to fully automate manual workflows on a large number of websites, replacing brittle or unreliable automation solutions.
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
[Installation](#installation)
[Operations](#operations)
[Credentials](#credentials)
[Resources](#resources)
## Installation
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
## Operations
- Task: interact with Skyvern task
- Workflow: interact with Skyvern workflow
## Credentials
- API Token: Skyvern API token. You can get it from the Skyvern UI
- Base URL: The endpoint for your Skyvern service. Default is Skyvern Cloud Service, you can also set it to your self-host Skyvern.
## Resources
* [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
* [Skyvern documentation](https://docs.skyvern.com/introduction)

View File

@@ -0,0 +1,38 @@
import {
IAuthenticateGeneric,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class SkyvernApi implements ICredentialType {
name = 'skyvernApi';
displayName = 'Skyvern API';
// Uses the link to this tutorial as an example
// Replace with your own docs links when building your own nodes
documentationUrl = 'https://docs.skyvern.ai/';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true },
default: '',
},
{
displayName: 'Base URL',
name: 'baseUrl',
type: 'string',
default: 'https://api.skyvern.com',
placeholder: 'https://api.skyvern.com',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
'x-api-key': '={{$credentials.apiKey}}',
'Content-Type': 'application/json',
}
},
};
}

View File

@@ -0,0 +1,16 @@
const path = require("path");
const { task, src, dest } = require("gulp");
task("build:icons", copyIcons);
function copyIcons() {
const nodeSource = path.resolve("nodes", "**", "*.{png,svg}");
const nodeDestination = path.resolve("dist", "nodes");
src(nodeSource).pipe(dest(nodeDestination));
const credSource = path.resolve("credentials", "**", "*.{png,svg}");
const credDestination = path.resolve("dist", "credentials");
return src(credSource).pipe(dest(credDestination));
}

View File

View File

@@ -0,0 +1,21 @@
{
"node": "n8n-nodes-skyvern",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Miscellaneous",
"Utility"
],
"resources": {
"credentialDocumentation": [
{
"url": ""
}
],
"primaryDocumentation": [
{
"url": ""
}
]
}
}

View File

@@ -0,0 +1,423 @@
import { FieldType, IDataObject, IExecuteSingleFunctions, IHttpRequestMethods, IHttpRequestOptions, ILoadOptionsFunctions, INodePropertyOptions, INodeType, INodeTypeDescription, NodeConnectionType, ResourceMapperField, ResourceMapperFields } from 'n8n-workflow';
const fetch = require('node-fetch');
export class Skyvern implements INodeType {
description: INodeTypeDescription = {
displayName: 'Skyvern',
name: 'skyvern',
icon: 'file:skyvern.png', // eslint-disable-line
group: ['transform'],
description: 'Node to interact with Skyvern',
defaults: {
name: 'Skyvern',
},
inputs: [NodeConnectionType.Main], // eslint-disable-line
outputs: [NodeConnectionType.Main], // eslint-disable-line
credentials: [
{
name: 'skyvernApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Task',
value: 'task',
},
{
name: 'Workflow',
value: 'workflow',
},
],
default: 'task',
},
{
displayName: 'Operation',
name: 'taskOperation',
type: 'options',
required: true,
default: 'dispatch',
options: [
{
name: 'Dispatch a Task',
value: 'dispatch',
description: 'Dispatch a task to execute asynchronously',
},
{
name: 'Get a Task',
value: 'get',
description: 'Get a task by ID',
},
],
displayOptions: {
show: {
resource: ['task'],
},
},
routing: {
request: {
baseURL: '={{$credentials.baseUrl}}',
method: '={{ $value === "dispatch" ? "POST" : "GET" }}' as IHttpRequestMethods,
url: '={{"/api/" + ($parameter["taskOptions"]["engine"] ? $parameter["taskOptions"]["engine"] : "v2") + "/tasks"}}',
},
send: {
preSend: [
async function (this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions> {
const taskOperation = this.getNodeParameter('taskOperation');
if (taskOperation === "get") return requestOptions;
const taskOptions: IDataObject = this.getNodeParameter('taskOptions') as IDataObject;
if (taskOptions["engine"] !== "v1") return requestOptions;
// trigger the generate task v1 logic
const credentials = await this.getCredentials('skyvernApi');
const userPrompt = this.getNodeParameter('userPrompt');
const response = await fetch(credentials['baseUrl'] + '/api/v1/generate/task', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': credentials['apiKey'],
},
body: JSON.stringify({
prompt: userPrompt,
}),
});
if (!response.ok) {
throw new Error('Request to generate Task V1 failed'); // eslint-disable-line
}
const data = await response.json();
requestOptions.body = {
url: data.url,
navigation_goal: data.navigation_goal,
navigation_payload: data.navigation_payload,
data_extraction_goal: data.data_extraction_goal,
extracted_information_schema: data.extracted_information_schema,
};
return requestOptions;
},
],
},
},
},
{
displayName: 'User Prompt',
description: 'The prompt for Skyvern to execute',
name: 'userPrompt',
type: 'string',
required: true,
default: '',
placeholder: 'eg: Navigate to the Hacker News homepage and get the top 3 posts.',
displayOptions: {
show: {
resource: ['task'],
taskOperation: ['dispatch'],
},
},
routing: {
request: {
body: {
user_prompt: '={{$value}}',
},
},
},
},
{
displayName: 'URL',
description: 'The URL to navigate to',
name: 'url',
type: 'string',
default: '',
placeholder: 'eg: https://news.ycombinator.com/',
displayOptions: {
show: {
resource: ['task'],
taskOperation: ['dispatch'],
},
},
routing: {
request: {
body: {
url: '={{$value ? $value : null}}',
},
},
},
},
{
displayName: 'Task ID',
description: 'The ID of the task',
name: 'taskId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['task'],
taskOperation: ['get'],
},
},
routing: {
request: {
method: 'GET',
url: '={{"/api/" + ($parameter["taskOptions"]["engine"] ? $parameter["taskOptions"]["engine"] : "v2") + "/tasks/" + $value}}',
},
},
},
{
displayName: 'Task Options',
name: 'taskOptions',
type: 'collection',
description: 'Optional Configuration for the task',
placeholder: 'Add Task Options',
default: {},
options: [
{
displayName: 'Engine',
name: 'engine',
type: 'options',
default: 'v2',
options: [
{
name: 'TaskV1',
value: 'v1',
},
{
name: 'TaskV2',
value: 'v2',
},
],
},
],
displayOptions: {
show: {
resource: ['task'],
},
},
},
{
displayName: 'Workflow Title or ID', // eslint-disable-line
description: 'The title of the workflow. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
name: 'workflowId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getWorkflows',
loadOptionsDependsOn: ['resource'],
},
required: true,
default: '',
displayOptions: {
show: {
resource: ['workflow'],
},
},
},
{
displayName: 'Workflow Operation',
name: 'workflowOperation',
type: 'options',
required: true,
default: 'get',
options: [
{
name: 'Get a Workflow Run',
value: 'get',
description: 'Get a workflow run by ID',
},
{
name: 'Dispatch a Workflow Run',
value: 'dispatch',
description: 'Dispatch a workflow run to execute asynchronously',
},
],
displayOptions: {
show: {
resource: ['workflow'],
},
},
routing: {
request: {
baseURL: '={{$credentials.baseUrl}}',
method: '={{ $value === "dispatch" ? "POST" : "GET" }}' as IHttpRequestMethods,
},
},
},
{
displayName: 'Workflow Run ID',
description: 'The ID of the workflow run',
name: 'workflowRunId',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: ['workflow'],
workflowOperation: ['get'],
},
},
routing: {
request: {
url: '={{"/api/v1/workflows/" + $parameter["workflowId"] + "/runs/" + $value}}',
},
},
},
{
displayName: 'Workflow Run Parameters',
name: 'workflowRunParameters',
type: 'resourceMapper',
noDataExpression: true,
description: 'The JSON-formatted parameters to pass the workflow run to execute',
required: true,
default: {
mappingMode: 'defineBelow',
value: null,
},
displayOptions: {
show: {
resource: ['workflow'],
workflowOperation: ['dispatch'],
},
},
typeOptions: {
loadOptionsDependsOn: ['workflowId'],
resourceMapper: {
resourceMapperMethod: 'getWorkflowRunParameters',
mode: 'update',
fieldWords: {
singular: 'workflowRunParameter',
plural: 'workflowRunParameters',
},
addAllFields: true,
multiKeyMatch: true,
},
},
routing: {
request: {
url: '={{"/api/v1/workflows/" + $parameter["workflowId"] + "/run"}}',
body: {
data: '={{$value["value"]}}',
},
},
},
},
],
version: 1,
};
methods = {
loadOptions: {
async getWorkflows(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const resource = this.getCurrentNodeParameter('resource') as string;
if (resource !== 'workflow') return [];
const credentials = await this.getCredentials('skyvernApi');
const response = await fetch(credentials['baseUrl'] + '/api/v1/workflows?page_size=100', {
headers: {
'x-api-key': credentials['apiKey'],
},
});
if (!response.ok) {
throw new Error('Request to get workflows failed'); // eslint-disable-line
}
const data = await response.json();
return data.map((workflow: any) => ({
name: workflow.title,
value: workflow.workflow_permanent_id,
}));
},
},
resourceMapping: {
async getWorkflowRunParameters(this: ILoadOptionsFunctions): Promise<ResourceMapperFields> {
const resource = this.getCurrentNodeParameter('resource') as string;
if (resource !== 'workflow') return { fields: [] };
const workflowOperation = this.getCurrentNodeParameter('workflowOperation') as string;
if (workflowOperation !== 'dispatch') return { fields: [] };
const workflowId = this.getCurrentNodeParameter('workflowId') as string;
if (!workflowId) return { fields: [] };
const credentials = await this.getCredentials('skyvernApi');
const response = await fetch(credentials['baseUrl'] + '/api/v1/workflows/' + workflowId, {
headers: {
'x-api-key': credentials['apiKey'],
},
});
if (!response.ok) {
throw new Error('Request to get workflow failed'); // eslint-disable-line
}
const workflow = await response.json();
const parameters: any[] = workflow.workflow_definition.parameters;
const fields: ResourceMapperField[] = await Promise.all(
parameters.filter((parameter: any) => parameter.parameter_type === 'workflow' || parameter.parameter_type === 'credential')
.map(async (parameter: any) => {
let options: INodePropertyOptions[] | undefined = undefined;
let parameterType: FieldType | undefined = undefined;
if (parameter.parameter_type === 'credential') {
const response = await fetch(credentials['baseUrl'] + '/api/v1/credentials', {
headers: {
'x-api-key': credentials['apiKey'],
},
});
if (!response.ok) {
throw new Error('Request to get credentials failed'); // eslint-disable-line
}
const data = await response.json();
options = data.map((credential: any) => ({
name: credential.name,
value: credential.credential_id,
}));
parameterType = 'options';
}else{
const parameter_type_map: Record<string, string> = {
'string': 'string',
'integer': 'number',
'float': 'number',
'boolean': 'boolean',
'json': 'json',
'file_url': 'url',
}
parameterType = parameter_type_map[parameter.workflow_parameter_type] as FieldType;
}
return {
id: parameter.key,
displayName: parameter.key,
defaultMatch: true,
canBeUsedToMatch: false,
required: parameter.default_value === undefined || parameter.default_value === null,
display: true,
type: parameterType,
options: options,
};
})
);
// HACK: If there are no parameters, add a empty field to avoid the resource mapper from crashing
if (fields.length === 0) {
fields.push({
id: 'NO_PARAMETERS',
displayName: 'No Parameters',
defaultMatch: false,
canBeUsedToMatch: false,
required: false,
display: true,
type: 'string',
});
}
return {
fields: fields,
}
},
},
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

7712
integrations/n8n/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,62 @@
{
"name": "n8n-nodes-skyvern",
"version": "0.0.1",
"description": "Skyvern Node for n8n",
"keywords": [
"n8n-community-node-package"
],
"license": "MIT",
"homepage": "",
"author": {
"name": "lawy",
"email": "lawy@skyvern.com"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Skyvern-AI/skyvern.git"
},
"engines": {
"node": ">=18.10",
"pnpm": ">=9.1"
},
"packageManager": "pnpm@9.1.4",
"main": "index.js",
"scripts": {
"preinstall": "npx only-allow pnpm",
"build": "rimraf dist && tsc && gulp build:icons",
"dev": "tsc --watch",
"format": "prettier nodes credentials --write",
"lint": "eslint nodes credentials package.json",
"lintfix": "eslint nodes credentials package.json --fix",
"prepublishOnly": "pnpm build && pnpm lint -c .eslintrc.prepublish.js nodes credentials package.json"
},
"files": [
"dist"
],
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/SkyvernApi.credentials.js"
],
"nodes": [
"dist/nodes/Skyvern/Skyvern.node.js"
]
},
"devDependencies": {
"@types/node": "^22.13.10",
"@typescript-eslint/parser": "^7.15.0",
"eslint": "^8.56.0",
"eslint-plugin-n8n-nodes-base": "^1.16.1",
"gulp": "^4.0.2",
"prettier": "^3.3.2",
"rimraf": "^6.0.1",
"typescript": "^5.5.3"
},
"peerDependencies": {
"n8n-workflow": "*"
},
"dependencies": {
"node-fetch": "2",
"sqlite3": "^5.1.7"
}
}

5286
integrations/n8n/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"strict": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2019",
"lib": ["es2019", "es2020", "es2022.error"],
"removeComments": true,
"useUnknownInCatchVariables": false,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"declaration": true,
"sourceMap": true,
"skipLibCheck": true,
"outDir": "./dist/",
},
"include": [
"credentials/**/*",
"nodes/**/*",
"nodes/**/*.json",
"package.json",
],
}