mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
Merge branch 'master' into feature/wordpress-node
This commit is contained in:
27
.github/workflows/tests.yml
vendored
Normal file
27
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Node CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [10.x, 12.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: npm install, build, and test
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run bootstrap
|
||||||
|
npm run build --if-present
|
||||||
|
npm test
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
@@ -100,11 +100,13 @@ export N8N_CUSTOM_EXTENSIONS="/home/jim/n8n/custom-nodes;/data/n8n/nodes"
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Use built-in modules in Function-Nodes
|
## Use built-in and external modules in Function-Nodes
|
||||||
|
|
||||||
By default is it for security reasons not allowed to import modules in Function-Nodes.
|
For security reasons, importing modules is restricted by default in Function-Nodes.
|
||||||
It is, however, possible to lift that restriction for built-in modules by setting the
|
It is, however, possible to lift that restriction for built-in and external modules by
|
||||||
environment variable `NODE_FUNCTION_ALLOW_BUILTIN`.
|
setting the following environment variables:
|
||||||
|
`NODE_FUNCTION_ALLOW_BUILTIN`: For builtin modules
|
||||||
|
`NODE_FUNCTION_ALLOW_EXTERNAL`: For external modules sourced from n8n/node_modules directory. External module support is disabled when env variable is not set.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Allows usage of all builtin modules
|
# Allows usage of all builtin modules
|
||||||
@@ -115,6 +117,9 @@ export NODE_FUNCTION_ALLOW_BUILTIN=crypto
|
|||||||
|
|
||||||
# Allows usage of only crypto and fs
|
# Allows usage of only crypto and fs
|
||||||
export NODE_FUNCTION_ALLOW_BUILTIN=crypto,fs
|
export NODE_FUNCTION_ALLOW_BUILTIN=crypto,fs
|
||||||
|
|
||||||
|
# Allow usage of external npm modules. Wildcard matching is not supported.
|
||||||
|
export NODE_FUNCTION_ALLOW_EXTERNAL=moment,lodash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import {
|
|||||||
} from "n8n-core";
|
} from "n8n-core";
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
const open = require('open');
|
const open = require('open');
|
||||||
import { promisify } from 'util';
|
// import { dirname } from 'path';
|
||||||
import { dirname } from 'path';
|
|
||||||
|
|
||||||
import * as config from '../config';
|
import * as config from '../config';
|
||||||
import {
|
import {
|
||||||
@@ -20,7 +19,6 @@ import {
|
|||||||
TestWebhooks,
|
TestWebhooks,
|
||||||
} from "../src";
|
} from "../src";
|
||||||
|
|
||||||
const tunnel = promisify(localtunnel);
|
|
||||||
|
|
||||||
// // Add support for internationalization
|
// // Add support for internationalization
|
||||||
// const fullIcuPath = require.resolve('full-icu');
|
// const fullIcuPath = require.resolve('full-icu');
|
||||||
@@ -151,7 +149,7 @@ export class Start extends Command {
|
|||||||
const port = config.get('port') as number;
|
const port = config.get('port') as number;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const webhookTunnel = await tunnel(port, tunnelSettings);
|
const webhookTunnel = await localtunnel(port, tunnelSettings);
|
||||||
|
|
||||||
process.env.WEBHOOK_TUNNEL_URL = webhookTunnel.url + '/';
|
process.env.WEBHOOK_TUNNEL_URL = webhookTunnel.url + '/';
|
||||||
this.log(`Tunnel URL: ${process.env.WEBHOOK_TUNNEL_URL}\n`);
|
this.log(`Tunnel URL: ${process.env.WEBHOOK_TUNNEL_URL}\n`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n",
|
"name": "n8n",
|
||||||
"version": "0.40.0",
|
"version": "0.41.0",
|
||||||
"description": "n8n Workflow Automation Tool",
|
"description": "n8n Workflow Automation Tool",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
@@ -52,24 +52,24 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@oclif/dev-cli": "^1.22.2",
|
"@oclif/dev-cli": "^1.22.2",
|
||||||
"@types/basic-auth": "^1.1.2",
|
"@types/basic-auth": "^1.1.2",
|
||||||
"@types/compression": "0.0.36",
|
"@types/compression": "1.0.1",
|
||||||
"@types/connect-history-api-fallback": "^1.3.1",
|
"@types/connect-history-api-fallback": "^1.3.1",
|
||||||
"@types/convict": "^4.2.1",
|
"@types/convict": "^4.2.1",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^8.2.0",
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.16.1",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^24.0.18",
|
||||||
"@types/localtunnel": "^1.9.0",
|
"@types/localtunnel": "^1.9.0",
|
||||||
"@types/lodash.get": "^4.4.2",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/node": "^10.10.1",
|
"@types/node": "^10.10.1",
|
||||||
"@types/open": "^6.1.0",
|
"@types/open": "^6.1.0",
|
||||||
"@types/parseurl": "^1.3.1",
|
"@types/parseurl": "^1.3.1",
|
||||||
"@types/request-promise-native": "^1.0.15",
|
"@types/request-promise-native": "^1.0.15",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"nodemon": "^1.19.1",
|
"nodemon": "^2.0.2",
|
||||||
"run-script-os": "^1.0.7",
|
"run-script-os": "^1.0.7",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"tslint": "^5.17.0",
|
"tslint": "^5.17.0",
|
||||||
"typescript": "~3.5.2"
|
"typescript": "~3.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oclif/command": "^1.5.18",
|
"@oclif/command": "^1.5.18",
|
||||||
@@ -86,17 +86,17 @@
|
|||||||
"flatted": "^2.0.0",
|
"flatted": "^2.0.0",
|
||||||
"glob-promise": "^3.4.0",
|
"glob-promise": "^3.4.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"inquirer": "^6.5.1",
|
"inquirer": "^7.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"jwks-rsa": "^1.6.0",
|
"jwks-rsa": "^1.6.0",
|
||||||
"localtunnel": "^1.9.1",
|
"localtunnel": "^2.0.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"mongodb": "^3.2.3",
|
"mongodb": "^3.2.3",
|
||||||
"n8n-core": "~0.18.0",
|
"n8n-core": "~0.18.0",
|
||||||
"n8n-editor-ui": "~0.29.0",
|
"n8n-editor-ui": "~0.29.0",
|
||||||
"n8n-nodes-base": "~0.35.0",
|
"n8n-nodes-base": "~0.36.0",
|
||||||
"n8n-workflow": "~0.18.0",
|
"n8n-workflow": "~0.18.0",
|
||||||
"open": "^6.1.0",
|
"open": "^7.0.0",
|
||||||
"pg": "^7.11.0",
|
"pg": "^7.11.0",
|
||||||
"request-promise-native": "^1.0.7",
|
"request-promise-native": "^1.0.7",
|
||||||
"sqlite3": "^4.0.6",
|
"sqlite3": "^4.0.6",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"@types/crypto-js": "^3.1.43",
|
"@types/crypto-js": "^3.1.43",
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.16.1",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^24.0.18",
|
||||||
"@types/lodash.get": "^4.4.5",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/mmmagic": "^0.4.29",
|
"@types/mmmagic": "^0.4.29",
|
||||||
"@types/node": "^10.10.1",
|
"@types/node": "^10.10.1",
|
||||||
"@types/request-promise-native": "^1.0.15",
|
"@types/request-promise-native": "^1.0.15",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"source-map-support": "^0.5.9",
|
"source-map-support": "^0.5.9",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"tslint": "^5.17.0",
|
"tslint": "^5.17.0",
|
||||||
"typescript": "~3.5.2"
|
"typescript": "~3.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-js": "^3.1.9-1",
|
"crypto-js": "^3.1.9-1",
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
'extends': [
|
'extends': [
|
||||||
'plugin:vue/essential',
|
'plugin:vue/essential',
|
||||||
'@vue/standard',
|
|
||||||
'@vue/typescript',
|
'@vue/typescript',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
@@ -18,6 +17,6 @@ module.exports = {
|
|||||||
'no-labels': 0,
|
'no-labels': 0,
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: 'typescript-eslint-parser',
|
parser: '@typescript-eslint/parser',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,25 +32,29 @@
|
|||||||
"@types/dateformat": "^3.0.0",
|
"@types/dateformat": "^3.0.0",
|
||||||
"@types/file-saver": "^2.0.1",
|
"@types/file-saver": "^2.0.1",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^24.0.18",
|
||||||
"@types/lodash.get": "^4.4.5",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/lodash.set": "^4.3.6",
|
"@types/lodash.set": "^4.3.6",
|
||||||
|
"@types/node": "12.12.22",
|
||||||
"@types/quill": "^2.0.1",
|
"@types/quill": "^2.0.1",
|
||||||
"@vue/cli-plugin-babel": "^3.8.0",
|
"@typescript-eslint/eslint-plugin": "^2.13.0",
|
||||||
"@vue/cli-plugin-e2e-cypress": "^3.8.0",
|
"@typescript-eslint/parser": "^2.13.0",
|
||||||
"@vue/cli-plugin-eslint": "^3.8.0",
|
"@vue/cli-plugin-babel": "^4.1.2",
|
||||||
"@vue/cli-plugin-typescript": "~3.8.1",
|
"@vue/cli-plugin-e2e-cypress": "^4.1.2",
|
||||||
"@vue/cli-plugin-unit-jest": "^3.8.0",
|
"@vue/cli-plugin-eslint": "^4.1.2",
|
||||||
"@vue/cli-service": "^3.8.0",
|
"@vue/cli-plugin-typescript": "~4.1.2",
|
||||||
"@vue/eslint-config-standard": "^4.0.0",
|
"@vue/cli-plugin-unit-jest": "^4.1.2",
|
||||||
"@vue/eslint-config-typescript": "~3.2.0",
|
"@vue/cli-service": "^4.1.2",
|
||||||
"@vue/test-utils": "^1.0.0-beta.20",
|
"@vue/eslint-config-standard": "^5.0.1",
|
||||||
"axios": "^0.18.1",
|
"@vue/eslint-config-typescript": "~5.0.1",
|
||||||
|
"@vue/test-utils": "^1.0.0-beta.24",
|
||||||
|
"axios": "^0.19.0",
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"dateformat": "^3.0.3",
|
"dateformat": "^3.0.3",
|
||||||
"element-ui": "~2.4.11",
|
"element-ui": "~2.13.0",
|
||||||
"eslint": "^5.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-vue": "^5.0.0-0",
|
"eslint-plugin-import": "^2.19.1",
|
||||||
|
"eslint-plugin-vue": "^6.1.1",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"flatted": "^2.0.0",
|
"flatted": "^2.0.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
@@ -64,18 +68,18 @@
|
|||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
"quill": "^2.0.0-dev.3",
|
"quill": "^2.0.0-dev.3",
|
||||||
"quill-autoformat": "^0.1.1",
|
"quill-autoformat": "^0.1.1",
|
||||||
"sass-loader": "^7.0.1",
|
"sass-loader": "^8.0.0",
|
||||||
"string-template-parser": "^1.2.6",
|
"string-template-parser": "^1.2.6",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"tslint": "^5.17.0",
|
"tslint": "^5.17.0",
|
||||||
"typescript": "~3.5.2",
|
"typescript": "~3.7.4",
|
||||||
"vue": "^2.6.9",
|
"vue": "^2.6.9",
|
||||||
"vue-cli-plugin-webpack-bundle-analyzer": "^1.3.0",
|
"vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
|
||||||
"vue-json-tree": "^0.4.1",
|
"vue-json-tree": "^0.4.1",
|
||||||
"vue-prism-editor": "^0.3.0",
|
"vue-prism-editor": "^0.3.0",
|
||||||
"vue-router": "^3.0.6",
|
"vue-router": "^3.0.6",
|
||||||
"vue-template-compiler": "^2.5.17",
|
"vue-template-compiler": "^2.5.17",
|
||||||
"vue-typed-mixins": "^0.1.0",
|
"vue-typed-mixins": "^0.2.0",
|
||||||
"vuex": "^3.1.1"
|
"vuex": "^3.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IBinaryData,
|
IBinaryData,
|
||||||
IBinaryKeyData,
|
|
||||||
IRunData,
|
IRunData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
Workflow,
|
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||||
|
|||||||
@@ -37,8 +37,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
|
||||||
|
|
||||||
import { restApi } from '@/components/mixins/restApi';
|
import { restApi } from '@/components/mixins/restApi';
|
||||||
import { ICredentialsResponse } from '@/Interface';
|
import { ICredentialsResponse } from '@/Interface';
|
||||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||||
@@ -72,7 +70,7 @@ export default mixins(
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
dialogVisible (newValue, oldValue) {
|
dialogVisible (newValue) {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.loadCredentials();
|
this.loadCredentials();
|
||||||
this.loadCredentialTypes();
|
this.loadCredentialTypes();
|
||||||
@@ -146,9 +144,8 @@ export default mixins(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
try {
|
||||||
result = await this.restApi().deleteCredentials(credential.id!);
|
await this.restApi().deleteCredentials(credential.id!);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$showError(error, 'Problem deleting credentials', 'There was a problem deleting the credentials:');
|
this.$showError(error, 'Problem deleting credentials', 'There was a problem deleting the credentials:');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IExecutionResponse,
|
IExecutionResponse,
|
||||||
@@ -349,7 +350,7 @@ export default mixins(
|
|||||||
cancelButtonText: 'Cancel',
|
cancelButtonText: 'Cancel',
|
||||||
inputErrorMessage: 'Invalid URL',
|
inputErrorMessage: 'Invalid URL',
|
||||||
inputPattern: /^http[s]?:\/\/.*\.json$/i,
|
inputPattern: /^http[s]?:\/\/.*\.json$/i,
|
||||||
});
|
}) as MessageBoxInputData;
|
||||||
|
|
||||||
this.$root.$emit('importWorkflowUrl', { url: promptResponse.value });
|
this.$root.$emit('importWorkflowUrl', { url: promptResponse.value });
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -361,7 +362,7 @@ export default mixins(
|
|||||||
inputValue: this.workflowName,
|
inputValue: this.workflowName,
|
||||||
confirmButtonText: 'Rename',
|
confirmButtonText: 'Rename',
|
||||||
cancelButtonText: 'Cancel',
|
cancelButtonText: 'Cancel',
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
|
<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-input v-else ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">
|
<el-input v-else v-model="tempValue" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">
|
||||||
<font-awesome-icon v-if="!isValueExpression && !isReadOnly" slot="suffix" icon="external-link-alt" class="edit-window-button clickable" title="Open Edit Window" @click="displayEditDialog()" />
|
<font-awesome-icon v-if="!isValueExpression && !isReadOnly" slot="suffix" icon="external-link-alt" class="edit-window-button clickable" title="Open Edit Window" @click="displayEditDialog()" />
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
<div v-else-if="parameter.type === 'color'" ref="inputField" class="color-input">
|
<div v-else-if="parameter.type === 'color'" ref="inputField" class="color-input">
|
||||||
<el-color-picker :value="displayValue" :disabled="isReadOnly" @change="valueChanged" size="small" class="color-picker" @focus="setFocus" :title="displayTitle" ></el-color-picker>
|
<el-color-picker :value="displayValue" :disabled="isReadOnly" @change="valueChanged" size="small" class="color-picker" @focus="setFocus" :title="displayTitle" ></el-color-picker>
|
||||||
<el-input size="small" type="text" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" ></el-input>
|
<el-input v-model="tempValue" size="small" type="text" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" ></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="parameter.type === 'boolean'">
|
<div v-else-if="parameter.type === 'boolean'">
|
||||||
@@ -173,7 +173,7 @@ export default mixins(
|
|||||||
remoteParameterOptionsLoading: false,
|
remoteParameterOptionsLoading: false,
|
||||||
remoteParameterOptionsLoadingIssues: null as string | null,
|
remoteParameterOptionsLoadingIssues: null as string | null,
|
||||||
textEditDialogVisible: false,
|
textEditDialogVisible: false,
|
||||||
tempValue: '', // el-date-picker does not seem to work without v-model so add one
|
tempValue: '', // el-date-picker and el-input does not seem to work without v-model so add one
|
||||||
dateTimePickerOptions: {
|
dateTimePickerOptions: {
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ export default mixins(
|
|||||||
},
|
},
|
||||||
getArgument (
|
getArgument (
|
||||||
argumentName: string,
|
argumentName: string,
|
||||||
parameter: INodeProperties
|
parameter: INodeProperties,
|
||||||
): string | number | boolean | undefined {
|
): string | number | boolean | undefined {
|
||||||
if (parameter.typeOptions === undefined) {
|
if (parameter.typeOptions === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export default mixins(
|
|||||||
key: fullpath,
|
key: fullpath,
|
||||||
allowParentSelect: true,
|
allowParentSelect: true,
|
||||||
dataType: 'array',
|
dataType: 'array',
|
||||||
} as IVariableSelectorOption
|
} as IVariableSelectorOption,
|
||||||
);
|
);
|
||||||
} else if (typeof inputData === 'object') {
|
} else if (typeof inputData === 'object') {
|
||||||
const tempValue: IVariableSelectorOption[] = [];
|
const tempValue: IVariableSelectorOption[] = [];
|
||||||
@@ -207,7 +207,7 @@ export default mixins(
|
|||||||
key: fullpath,
|
key: fullpath,
|
||||||
allowParentSelect: true,
|
allowParentSelect: true,
|
||||||
dataType: 'object',
|
dataType: 'object',
|
||||||
} as IVariableSelectorOption
|
} as IVariableSelectorOption,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -222,7 +222,7 @@ export default mixins(
|
|||||||
name: propertyName,
|
name: propertyName,
|
||||||
key: fullpath,
|
key: fullpath,
|
||||||
value: inputData,
|
value: inputData,
|
||||||
} as IVariableSelectorOption
|
} as IVariableSelectorOption,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -294,7 +294,7 @@ export default mixins(
|
|||||||
{
|
{
|
||||||
name: 'JSON',
|
name: 'JSON',
|
||||||
options: this.sortOptions(jsonDataOptions),
|
options: this.sortOptions(jsonDataOptions),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,7 +321,7 @@ export default mixins(
|
|||||||
name: propertyName,
|
name: propertyName,
|
||||||
key: `$node["${nodeName}"].binary.${dataPropertyName}.${propertyName}`,
|
key: `$node["${nodeName}"].binary.${dataPropertyName}.${propertyName}`,
|
||||||
value: outputData.binary![dataPropertyName][propertyName],
|
value: outputData.binary![dataPropertyName][propertyName],
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ export default mixins(
|
|||||||
key: `$node["${nodeName}"].binary.${dataPropertyName}`,
|
key: `$node["${nodeName}"].binary.${dataPropertyName}`,
|
||||||
options: this.sortOptions(binaryPropertyData),
|
options: this.sortOptions(binaryPropertyData),
|
||||||
allowParentSelect: true,
|
allowParentSelect: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +343,7 @@ export default mixins(
|
|||||||
key: `$node["${nodeName}"].binary`,
|
key: `$node["${nodeName}"].binary`,
|
||||||
options: this.sortOptions(binaryData),
|
options: this.sortOptions(binaryData),
|
||||||
allowParentSelect: true,
|
allowParentSelect: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,7 +476,7 @@ export default mixins(
|
|||||||
{
|
{
|
||||||
name: 'Input Data',
|
name: 'Input Data',
|
||||||
options: this.sortOptions(tempOutputData),
|
options: this.sortOptions(tempOutputData),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Data is to large so do not add
|
// Data is to large so do not add
|
||||||
@@ -488,7 +488,7 @@ export default mixins(
|
|||||||
name: '[Data to large]',
|
name: '[Data to large]',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -504,14 +504,14 @@ export default mixins(
|
|||||||
{
|
{
|
||||||
name: 'Parameters',
|
name: 'Parameters',
|
||||||
options: this.sortOptions(this.getNodeParameters(activeNode.name, initialPath, skipParameter, filterText) as IVariableSelectorOption[]),
|
options: this.sortOptions(this.getNodeParameters(activeNode.name, initialPath, skipParameter, filterText) as IVariableSelectorOption[]),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
returnData.push(
|
returnData.push(
|
||||||
{
|
{
|
||||||
name: 'Current Node',
|
name: 'Current Node',
|
||||||
options: this.sortOptions(currentNodeData),
|
options: this.sortOptions(currentNodeData),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add the input data
|
// Add the input data
|
||||||
@@ -562,7 +562,7 @@ export default mixins(
|
|||||||
{
|
{
|
||||||
name: 'Output Data',
|
name: 'Output Data',
|
||||||
options: this.sortOptions(tempOutputData),
|
options: this.sortOptions(tempOutputData),
|
||||||
} as IVariableSelectorOption
|
} as IVariableSelectorOption,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -571,7 +571,7 @@ export default mixins(
|
|||||||
{
|
{
|
||||||
name: nodeName,
|
name: nodeName,
|
||||||
options: this.sortOptions(nodeOptions),
|
options: this.sortOptions(nodeOptions),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +579,7 @@ export default mixins(
|
|||||||
{
|
{
|
||||||
name: 'Nodes',
|
name: 'Nodes',
|
||||||
options: this.sortOptions(allNodesData),
|
options: this.sortOptions(allNodesData),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove empty entries and return
|
// Remove empty entries and return
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export default mixins(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -104,13 +104,13 @@ export default mixins(
|
|||||||
workflowData.updatedAt = this.convertToDisplayDate(workflowData.updatedAt as number);
|
workflowData.updatedAt = this.convertToDisplayDate(workflowData.updatedAt as number);
|
||||||
});
|
});
|
||||||
this.isDataLoading = false;
|
this.isDataLoading = false;
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.catch(
|
.catch(
|
||||||
(error: Error) => {
|
(error: Error) => {
|
||||||
this.$showError(error, 'Problem loading workflows', 'There was a problem loading the workflows:');
|
this.$showError(error, 'Problem loading workflows', 'There was a problem loading the workflows:');
|
||||||
this.isDataLoading = false;
|
this.isDataLoading = false;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
workflowActiveChanged (data: { id: string, active: boolean }) {
|
workflowActiveChanged (data: { id: string, active: boolean }) {
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export default mixins(
|
|||||||
key: 'none',
|
key: 'none',
|
||||||
value: 'Do not save',
|
value: 'Do not save',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async loadSaveDataSuccessExecutionOptions () {
|
async loadSaveDataSuccessExecutionOptions () {
|
||||||
@@ -204,7 +204,7 @@ export default mixins(
|
|||||||
key: 'none',
|
key: 'none',
|
||||||
value: 'Do not save',
|
value: 'Do not save',
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async loadSaveManualOptions () {
|
async loadSaveManualOptions () {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const genericHelpers = mixins(showMessage).extend({
|
|||||||
text: 'Loading',
|
text: 'Loading',
|
||||||
spinner: 'el-icon-loading',
|
spinner: 'el-icon-loading',
|
||||||
background: 'rgba(255, 255, 255, 0.8)',
|
background: 'rgba(255, 255, 255, 0.8)',
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
stopLoading () {
|
stopLoading () {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const pushConnection = mixins(
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
receivedData = JSON.parse(event.data);
|
receivedData = JSON.parse(event.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('The received push data is not valid JSON.');
|
console.error('The received push data is not valid JSON.'); // eslint-disable-line no-console
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { parse } from 'flatted';
|
import { parse } from 'flatted';
|
||||||
|
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig, Method } from 'axios';
|
||||||
import {
|
import {
|
||||||
IActivationError,
|
IActivationError,
|
||||||
ICredentialsDecryptedResponse,
|
ICredentialsDecryptedResponse,
|
||||||
@@ -94,7 +94,7 @@ export const restApi = Vue.extend({
|
|||||||
restApi (): IRestApi {
|
restApi (): IRestApi {
|
||||||
const self = this;
|
const self = this;
|
||||||
return {
|
return {
|
||||||
async makeRestApiRequest (method: string, endpoint: string, data?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
async makeRestApiRequest (method: Method, endpoint: string, data?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||||
try {
|
try {
|
||||||
const options: AxiosRequestConfig = {
|
const options: AxiosRequestConfig = {
|
||||||
method,
|
method,
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ export const workflowHelpers = mixins(
|
|||||||
{
|
{
|
||||||
confirmButtonText: 'Save',
|
confirmButtonText: 'Save',
|
||||||
cancelButtonText: 'Cancel',
|
cancelButtonText: 'Cancel',
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -403,7 +403,7 @@ export const workflowHelpers = mixins(
|
|||||||
if (currentWorkflow === undefined || withNewName === true) {
|
if (currentWorkflow === undefined || withNewName === true) {
|
||||||
// Workflow is new or is supposed to get saved under a new name
|
// Workflow is new or is supposed to get saved under a new name
|
||||||
// so create a new entry in database
|
// so create a new entry in database
|
||||||
workflowData.name = workflowName.trim() as string;
|
workflowData.name = workflowName!.trim() as string;
|
||||||
|
|
||||||
if (withNewName === true) {
|
if (withNewName === true) {
|
||||||
// If an existing workflow gets resaved with a new name
|
// If an existing workflow gets resaved with a new name
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const workflowSave = mixins(
|
|||||||
{
|
{
|
||||||
confirmButtonText: 'Save',
|
confirmButtonText: 'Save',
|
||||||
cancelButtonText: 'Cancel',
|
cancelButtonText: 'Cancel',
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -62,7 +62,7 @@ export const workflowSave = mixins(
|
|||||||
if (currentWorkflow === undefined || withNewName === true) {
|
if (currentWorkflow === undefined || withNewName === true) {
|
||||||
// Workflow is new or is supposed to get saved under a new name
|
// Workflow is new or is supposed to get saved under a new name
|
||||||
// so create a new entry in database
|
// so create a new entry in database
|
||||||
workflowData.name = workflowName.trim() as string;
|
workflowData.name = workflowName!.trim() as string;
|
||||||
|
|
||||||
if (withNewName === true) {
|
if (withNewName === true) {
|
||||||
// If an existing workflow gets resaved with a new name
|
// If an existing workflow gets resaved with a new name
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
faClone,
|
faClone,
|
||||||
faCloud,
|
faCloud,
|
||||||
faCopy,
|
faCopy,
|
||||||
|
faCut,
|
||||||
faDotCircle,
|
faDotCircle,
|
||||||
faEdit,
|
faEdit,
|
||||||
faEnvelope,
|
faEnvelope,
|
||||||
@@ -106,6 +107,7 @@ library.add(faCogs);
|
|||||||
library.add(faClone);
|
library.add(faClone);
|
||||||
library.add(faCloud);
|
library.add(faCloud);
|
||||||
library.add(faCopy);
|
library.add(faCopy);
|
||||||
|
library.add(faCut);
|
||||||
library.add(faDotCircle);
|
library.add(faDotCircle);
|
||||||
library.add(faEdit);
|
library.add(faEdit);
|
||||||
library.add(faEnvelope);
|
library.add(faEnvelope);
|
||||||
|
|||||||
@@ -102,6 +102,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||||
import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb';
|
import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb';
|
||||||
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
||||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||||
@@ -943,7 +944,7 @@ export default mixins(
|
|||||||
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
// newNodeData.position = [activeNode.position[0], activeNode.position[1] + 60];
|
||||||
newNodeData.position = this.getNewNodePosition(
|
newNodeData.position = this.getNewNodePosition(
|
||||||
[lastSelectedNode.position[0] + 150, lastSelectedNode.position[1]],
|
[lastSelectedNode.position[0] + 150, lastSelectedNode.position[1]],
|
||||||
[100, 0]
|
[100, 0],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If no node is active find a free spot
|
// If no node is active find a free spot
|
||||||
@@ -970,7 +971,7 @@ export default mixins(
|
|||||||
|
|
||||||
// Add connections of active node to newly created one
|
// Add connections of active node to newly created one
|
||||||
let connections = this.$store.getters.connectionsByNodeName(
|
let connections = this.$store.getters.connectionsByNodeName(
|
||||||
lastSelectedNode.name
|
lastSelectedNode.name,
|
||||||
);
|
);
|
||||||
connections = JSON.parse(JSON.stringify(connections));
|
connections = JSON.parse(JSON.stringify(connections));
|
||||||
|
|
||||||
@@ -1406,7 +1407,7 @@ export default mixins(
|
|||||||
|
|
||||||
newNodeData.position = this.getNewNodePosition(
|
newNodeData.position = this.getNewNodePosition(
|
||||||
[node.position[0], node.position[1] + 150],
|
[node.position[0], node.position[1] + 150],
|
||||||
[0, 150]
|
[0, 150],
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.addNodes([newNodeData]);
|
await this.addNodes([newNodeData]);
|
||||||
@@ -1504,7 +1505,7 @@ export default mixins(
|
|||||||
nameInput.select();
|
nameInput.select();
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptResponse = await promptResponsePromise;
|
const promptResponse = await promptResponsePromise as MessageBoxInputData;
|
||||||
|
|
||||||
this.renameNode(currentName, promptResponse.value);
|
this.renameNode(currentName, promptResponse.value);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -1602,7 +1603,7 @@ export default mixins(
|
|||||||
for (const type of Object.keys(connections[sourceNode])) {
|
for (const type of Object.keys(connections[sourceNode])) {
|
||||||
for (let sourceIndex = 0; sourceIndex < connections[sourceNode][type].length; sourceIndex++) {
|
for (let sourceIndex = 0; sourceIndex < connections[sourceNode][type].length; sourceIndex++) {
|
||||||
connections[sourceNode][type][sourceIndex].forEach((
|
connections[sourceNode][type][sourceIndex].forEach((
|
||||||
targetData
|
targetData,
|
||||||
) => {
|
) => {
|
||||||
connectionData = [
|
connectionData = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ module.exports = {
|
|||||||
css: {
|
css: {
|
||||||
loaderOptions: {
|
loaderOptions: {
|
||||||
sass: {
|
sass: {
|
||||||
data: `
|
prependData: `
|
||||||
@import "@/n8n-theme-variables.scss";
|
@import "@/n8n-theme-variables.scss";
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,11 +58,11 @@
|
|||||||
"change-case": "^3.1.0",
|
"change-case": "^3.1.0",
|
||||||
"copyfiles": "^2.1.1",
|
"copyfiles": "^2.1.1",
|
||||||
"inquirer": "^7.0.0",
|
"inquirer": "^7.0.0",
|
||||||
"n8n-core": "^0.10.0",
|
"n8n-core": "^0.18.0",
|
||||||
"n8n-workflow": "^0.11.0",
|
"n8n-workflow": "^0.18.0",
|
||||||
"replace-in-file": "^4.1.0",
|
"replace-in-file": "^4.1.0",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"tmp-promise": "^2.0.2",
|
"tmp-promise": "^2.0.2",
|
||||||
"typescript": "~3.5.2"
|
"typescript": "~3.7.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,8 @@ export class Function implements INodeType {
|
|||||||
console: 'inherit',
|
console: 'inherit',
|
||||||
sandbox,
|
sandbox,
|
||||||
require: {
|
require: {
|
||||||
external: false,
|
external: false as boolean | { modules: string[] },
|
||||||
builtin: [] as string[],
|
builtin: [] as string[],
|
||||||
root: './',
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,6 +72,11 @@ export class Function implements INodeType {
|
|||||||
options.require.builtin = process.env.NODE_FUNCTION_ALLOW_BUILTIN.split(',');
|
options.require.builtin = process.env.NODE_FUNCTION_ALLOW_BUILTIN.split(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_FUNCTION_ALLOW_EXTERNAL) {
|
||||||
|
options.require.external = { modules: process.env.NODE_FUNCTION_ALLOW_EXTERNAL.split(',') };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const vm = new NodeVM(options);
|
const vm = new NodeVM(options);
|
||||||
|
|
||||||
// Get the code to execute
|
// Get the code to execute
|
||||||
@@ -80,7 +84,7 @@ export class Function implements INodeType {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Execute the function code
|
// Execute the function code
|
||||||
items = (await vm.run(`module.exports = async function() {${functionCode}}()`));
|
items = (await vm.run(`module.exports = async function() {${functionCode}}()`, './'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(e);
|
return Promise.reject(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export class GoogleSheet {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data.values;
|
return response.data.values as string[][] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -141,9 +141,9 @@ export class GoogleSheet {
|
|||||||
async batchUpdate(updateData: ISheetUpdateData[], valueInputMode: ValueInputOption) {
|
async batchUpdate(updateData: ISheetUpdateData[], valueInputMode: ValueInputOption) {
|
||||||
const client = await this.getAuthenticationClient();
|
const client = await this.getAuthenticationClient();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const response = await Sheets.spreadsheets.values.batchUpdate(
|
const response = await Sheets.spreadsheets.values.batchUpdate(
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
auth: client,
|
auth: client,
|
||||||
spreadsheetId: this.id,
|
spreadsheetId: this.id,
|
||||||
valueInputOption: valueInputMode,
|
valueInputOption: valueInputMode,
|
||||||
@@ -163,6 +163,7 @@ export class GoogleSheet {
|
|||||||
async setData(range: string, data: string[][], valueInputMode: ValueInputOption) {
|
async setData(range: string, data: string[][], valueInputMode: ValueInputOption) {
|
||||||
const client = await this.getAuthenticationClient();
|
const client = await this.getAuthenticationClient();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const response = await Sheets.spreadsheets.values.update(
|
const response = await Sheets.spreadsheets.values.update(
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -186,9 +187,9 @@ export class GoogleSheet {
|
|||||||
async appendData(range: string, data: string[][], valueInputMode: ValueInputOption) {
|
async appendData(range: string, data: string[][], valueInputMode: ValueInputOption) {
|
||||||
const client = await this.getAuthenticationClient();
|
const client = await this.getAuthenticationClient();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const response = await Sheets.spreadsheets.values.append(
|
const response = await Sheets.spreadsheets.values.append(
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
auth: client,
|
auth: client,
|
||||||
spreadsheetId: this.id,
|
spreadsheetId: this.id,
|
||||||
range,
|
range,
|
||||||
|
|||||||
277
packages/nodes-base/nodes/HtmlExtract/HtmlExtract.node.ts
Normal file
277
packages/nodes-base/nodes/HtmlExtract/HtmlExtract.node.ts
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
import { IExecuteFunctions } from 'n8n-core';
|
||||||
|
import {
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeDescription,
|
||||||
|
IDataObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
interface IValueData {
|
||||||
|
attribute?: string;
|
||||||
|
cssSelector: string;
|
||||||
|
returnValue: string;
|
||||||
|
key: string;
|
||||||
|
returnArray: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The extraction functions
|
||||||
|
const extractFunctions: {
|
||||||
|
[key: string]: ($: Cheerio, valueData: IValueData) => string | undefined;
|
||||||
|
} = {
|
||||||
|
attribute: ($: Cheerio, valueData: IValueData): string | undefined => $.attr(valueData.attribute!),
|
||||||
|
html: ($: Cheerio, valueData: IValueData): string | undefined => $.html() || undefined,
|
||||||
|
text: ($: Cheerio, valueData: IValueData): string | undefined => $.text(),
|
||||||
|
value: ($: Cheerio, valueData: IValueData): string | undefined => $.val(),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple helper function which applies options
|
||||||
|
*/
|
||||||
|
function getValue($: Cheerio, valueData: IValueData, options: IDataObject) {
|
||||||
|
const value = extractFunctions[valueData.returnValue]($, valueData);
|
||||||
|
if (options.trimValues === false || value === undefined) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class HtmlExtract implements INodeType {
|
||||||
|
description: INodeTypeDescription = {
|
||||||
|
displayName: 'HTML Extract',
|
||||||
|
name: 'htmlExtract',
|
||||||
|
icon: 'fa:cut',
|
||||||
|
group: ['transform'],
|
||||||
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["sourceData"] + ": " + $parameter["dataPropertyName"]}}',
|
||||||
|
description: 'Extracts data from HTML',
|
||||||
|
defaults: {
|
||||||
|
name: 'HTML Extract',
|
||||||
|
color: '#333377',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Source Data',
|
||||||
|
name: 'sourceData',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Binary',
|
||||||
|
value: 'binary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'JSON',
|
||||||
|
value: 'json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'json',
|
||||||
|
description: 'If HTML should be read from binary or json data.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Binary Property',
|
||||||
|
name: 'dataPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
sourceData: [
|
||||||
|
'binary',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'data',
|
||||||
|
required: true,
|
||||||
|
description: 'Name of the binary property in which the HTML to extract the data from can be found.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'JSON Property',
|
||||||
|
name: 'dataPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
sourceData: [
|
||||||
|
'json',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'data',
|
||||||
|
required: true,
|
||||||
|
description: 'Name of the json property in which the HTML to extract the data from can be found.<br />The property can either contain a string or an array of strings.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Extraction Values',
|
||||||
|
name: 'extractionValues',
|
||||||
|
placeholder: 'Add Value',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
description: 'The extraction values.',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'values',
|
||||||
|
displayName: 'Values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The key under which the extracted value should be saved.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CSS Selector',
|
||||||
|
name: 'cssSelector',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: '.price',
|
||||||
|
description: 'The CSS selector to use.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return Value',
|
||||||
|
name: 'returnValue',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Attribute',
|
||||||
|
value: 'attribute',
|
||||||
|
description: 'Get an attribute value like "class" from an element.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
description: 'Get the HTML the element contains.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'text',
|
||||||
|
description: 'Get only the text content of the element.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Value',
|
||||||
|
value: 'value',
|
||||||
|
description: 'Get value of an input, select or textarea.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'text',
|
||||||
|
description: 'What kind of data should be returned.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Attribute',
|
||||||
|
name: 'attribute',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
returnValue: [
|
||||||
|
'attribute',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
placeholder: 'class',
|
||||||
|
description: 'The name of the attribute to return the value off.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Return Array',
|
||||||
|
name: 'returnArray',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Returns the values as an array so if multiple ones get found they also get<br />returned separately.If not set all will be returned as a single string.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Trim Values',
|
||||||
|
name: 'trimValues',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
description: 'Removes automatically all spaces and newlines from<br />the beginning and end of the values.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||||
|
const items = this.getInputData();
|
||||||
|
|
||||||
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
let item: INodeExecutionData;
|
||||||
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||||
|
const dataPropertyName = this.getNodeParameter('dataPropertyName', itemIndex) as string;
|
||||||
|
const extractionValues = this.getNodeParameter('extractionValues', itemIndex) as IDataObject;
|
||||||
|
const options = this.getNodeParameter('options', itemIndex, {}) as IDataObject;
|
||||||
|
const sourceData = this.getNodeParameter('sourceData', itemIndex) as string;
|
||||||
|
|
||||||
|
item = items[itemIndex];
|
||||||
|
|
||||||
|
let htmlArray: string[] | string = [];
|
||||||
|
if (sourceData === 'json') {
|
||||||
|
if (item.json[dataPropertyName] === undefined) {
|
||||||
|
throw new Error(`No property named "${dataPropertyName}" exists!`);
|
||||||
|
}
|
||||||
|
htmlArray = item.json[dataPropertyName] as string;
|
||||||
|
} else {
|
||||||
|
if (item.binary === undefined) {
|
||||||
|
throw new Error(`No item does not contain binary data!`);
|
||||||
|
}
|
||||||
|
if (item.binary[dataPropertyName] === undefined) {
|
||||||
|
throw new Error(`No property named "${dataPropertyName}" exists!`);
|
||||||
|
}
|
||||||
|
htmlArray = Buffer.from(item.binary[dataPropertyName].data, 'base64').toString('utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert it always to array that it works with a string or an array of strings
|
||||||
|
if (!Array.isArray(htmlArray)) {
|
||||||
|
htmlArray = [htmlArray];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const html of htmlArray as string[]) {
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
|
||||||
|
const newItem: INodeExecutionData = {
|
||||||
|
json: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Itterate over all the defined values which should be extracted
|
||||||
|
let htmlElement;
|
||||||
|
for (const valueData of extractionValues.values as IValueData[]) {
|
||||||
|
htmlElement = $(valueData.cssSelector);
|
||||||
|
|
||||||
|
if (valueData.returnArray === true) {
|
||||||
|
// An array should be returned so itterate over one
|
||||||
|
// value at a time
|
||||||
|
newItem.json[valueData.key as string] = [];
|
||||||
|
htmlElement.each((i, el) => {
|
||||||
|
(newItem.json[valueData.key as string] as Array<string | undefined>).push(getValue($(el), valueData, options));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// One single value should be returned
|
||||||
|
newItem.json[valueData.key as string] = getValue(htmlElement, valueData, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returnData.push(newItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prepareOutputData(returnData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ export class HttpRequest implements INodeType {
|
|||||||
icon: 'fa:at',
|
icon: 'fa:at',
|
||||||
group: ['input'],
|
group: ['input'],
|
||||||
version: 1,
|
version: 1,
|
||||||
|
subtitle: '={{$parameter["requestMethod"] + ": " + $parameter["url"]}}',
|
||||||
description: 'Makes a HTTP request and returns the received data',
|
description: 'Makes a HTTP request and returns the received data',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'HTTP Request',
|
name: 'HTTP Request',
|
||||||
|
|||||||
@@ -109,27 +109,27 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
type: 'options',
|
type: 'options',
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: 'csv',
|
name: 'CSV',
|
||||||
value: 'csv',
|
value: 'csv',
|
||||||
description: 'Comma-separated values',
|
description: 'Comma-separated values',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ods',
|
name: 'HTML',
|
||||||
value: 'ods',
|
|
||||||
description: 'OpenDocument Spreadsheet',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rtf',
|
|
||||||
value: 'rtf',
|
|
||||||
description: 'Rich Text Format',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'html',
|
|
||||||
value: 'html',
|
value: 'html',
|
||||||
description: 'HTML Table',
|
description: 'HTML Table',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'xls',
|
name: 'ODS',
|
||||||
|
value: 'ods',
|
||||||
|
description: 'OpenDocument Spreadsheet',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RTF',
|
||||||
|
value: 'rtf',
|
||||||
|
description: 'Rich Text Format',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'XLS',
|
||||||
value: 'xls',
|
value: 'xls',
|
||||||
description: 'Excel',
|
description: 'Excel',
|
||||||
},
|
},
|
||||||
@@ -166,22 +166,68 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
name: 'options',
|
name: 'options',
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
placeholder: 'Add Option',
|
placeholder: 'Add Option',
|
||||||
displayOptions: {
|
|
||||||
show: {
|
|
||||||
operation: [
|
|
||||||
'toFile',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
default: {},
|
default: {},
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
displayName: 'File Name',
|
displayName: 'File Name',
|
||||||
name: 'fileName',
|
name: 'fileName',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/operation': [
|
||||||
|
'toFile',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
default: '',
|
default: '',
|
||||||
description: 'File name to set in binary data. By default will "spreadsheet.<fileFormat>" be used.',
|
description: 'File name to set in binary data. By default will "spreadsheet.<fileFormat>" be used.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'RAW Data',
|
||||||
|
name: 'rawData',
|
||||||
|
type: 'boolean',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/operation': [
|
||||||
|
'fromFile'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: false,
|
||||||
|
description: 'If the data should be returned RAW instead of parsed.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sheet Name',
|
||||||
|
name: 'sheetName',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/operation': [
|
||||||
|
'fromFile',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'Sheet',
|
||||||
|
description: 'Name of the sheet to read from in the spreadsheet (if supported). If not set, the first one gets chosen.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sheet Name',
|
||||||
|
name: 'sheetName',
|
||||||
|
type: 'string',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/operation': [
|
||||||
|
'toFile',
|
||||||
|
],
|
||||||
|
'/fileFormat': [
|
||||||
|
'ods',
|
||||||
|
'xls',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: 'Sheet',
|
||||||
|
description: 'Name of the sheet to create in the spreadsheet.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -203,7 +249,8 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
item = items[i];
|
item = items[i];
|
||||||
|
|
||||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||||
|
const options = this.getNodeParameter('options', i, {}) as IDataObject;
|
||||||
|
|
||||||
if (item.binary === undefined || item.binary[binaryPropertyName] === undefined) {
|
if (item.binary === undefined || item.binary[binaryPropertyName] === undefined) {
|
||||||
// Property did not get found on item
|
// Property did not get found on item
|
||||||
@@ -212,14 +259,22 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
|
|
||||||
// Read the binary spreadsheet data
|
// Read the binary spreadsheet data
|
||||||
const binaryData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING);
|
const binaryData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING);
|
||||||
const workbook = xlsxRead(binaryData);
|
const workbook = xlsxRead(binaryData, { raw: options.rawData as boolean });
|
||||||
|
|
||||||
if (workbook.SheetNames.length === 0) {
|
if (workbook.SheetNames.length === 0) {
|
||||||
throw new Error('File does not have any sheets!');
|
throw new Error('Spreadsheet does not have any sheets!');
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheetName = workbook.SheetNames[0];
|
||||||
|
if (options.sheetName) {
|
||||||
|
if (!workbook.SheetNames.includes(options.sheetName as string)) {
|
||||||
|
throw new Error(`Spreadsheet does not contain sheet called "${options.sheetName}"!`);
|
||||||
|
}
|
||||||
|
sheetName = options.sheetName as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert it to json
|
// Convert it to json
|
||||||
const sheetJson = xlsxUtils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]]);
|
const sheetJson = xlsxUtils.sheet_to_json(workbook.Sheets[sheetName]);
|
||||||
|
|
||||||
// Check if data could be found in file
|
// Check if data could be found in file
|
||||||
if (sheetJson.length === 0) {
|
if (sheetJson.length === 0) {
|
||||||
@@ -267,7 +322,7 @@ export class SpreadsheetFile implements INodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the data in the correct format
|
// Convert the data in the correct format
|
||||||
const sheetName = 'Sheet';
|
const sheetName = options.sheetName as string || 'Sheet';
|
||||||
const wb: WorkBook = {
|
const wb: WorkBook = {
|
||||||
SheetNames: [sheetName],
|
SheetNames: [sheetName],
|
||||||
Sheets: {
|
Sheets: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-nodes-base",
|
"name": "n8n-nodes-base",
|
||||||
"version": "0.35.0",
|
"version": "0.36.0",
|
||||||
"description": "Base nodes of n8n",
|
"description": "Base nodes of n8n",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"homepage": "https://n8n.io",
|
"homepage": "https://n8n.io",
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
"dist/nodes/Google/GoogleDrive.node.js",
|
"dist/nodes/Google/GoogleDrive.node.js",
|
||||||
"dist/nodes/Google/GoogleSheets.node.js",
|
"dist/nodes/Google/GoogleSheets.node.js",
|
||||||
"dist/nodes/GraphQL/GraphQL.node.js",
|
"dist/nodes/GraphQL/GraphQL.node.js",
|
||||||
|
"dist/nodes/HtmlExtract/HtmlExtract.node.js",
|
||||||
"dist/nodes/HttpRequest.node.js",
|
"dist/nodes/HttpRequest.node.js",
|
||||||
"dist/nodes/Hubspot/Hubspot.node.js",
|
"dist/nodes/Hubspot/Hubspot.node.js",
|
||||||
"dist/nodes/If.node.js",
|
"dist/nodes/If.node.js",
|
||||||
@@ -167,6 +168,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/aws4": "^1.5.1",
|
"@types/aws4": "^1.5.1",
|
||||||
"@types/basic-auth": "^1.1.2",
|
"@types/basic-auth": "^1.1.2",
|
||||||
|
"@types/cheerio": "^0.22.15",
|
||||||
"@types/cron": "^1.6.1",
|
"@types/cron": "^1.6.1",
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.16.1",
|
||||||
"@types/gm": "^1.18.2",
|
"@types/gm": "^1.18.2",
|
||||||
@@ -184,15 +186,16 @@
|
|||||||
"n8n-workflow": "~0.18.0",
|
"n8n-workflow": "~0.18.0",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"tslint": "^5.17.0",
|
"tslint": "^5.17.0",
|
||||||
"typescript": "~3.5.2"
|
"typescript": "~3.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aws4": "^1.8.0",
|
"aws4": "^1.8.0",
|
||||||
"basic-auth": "^2.0.1",
|
"basic-auth": "^2.0.1",
|
||||||
|
"cheerio": "^1.0.0-rc.3",
|
||||||
"cron": "^1.6.0",
|
"cron": "^1.6.0",
|
||||||
"glob-promise": "^3.4.0",
|
"glob-promise": "^3.4.0",
|
||||||
"gm": "^1.23.1",
|
"gm": "^1.23.1",
|
||||||
"googleapis": "^42.0.0",
|
"googleapis": "^46.0.0",
|
||||||
"imap-simple": "^4.3.0",
|
"imap-simple": "^4.3.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.2",
|
"lodash.set": "^4.3.2",
|
||||||
|
|||||||
@@ -27,12 +27,12 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.16.1",
|
"@types/express": "^4.16.1",
|
||||||
"@types/jest": "^24.0.18",
|
"@types/jest": "^24.0.18",
|
||||||
"@types/lodash.get": "^4.4.5",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/node": "^10.10.1",
|
"@types/node": "^10.10.1",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"ts-jest": "^24.0.2",
|
"ts-jest": "^24.0.2",
|
||||||
"tslint": "^5.17.0",
|
"tslint": "^5.17.0",
|
||||||
"typescript": "~3.5.2"
|
"typescript": "~3.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user