Initial commit to release
This commit is contained in:
230
packages/node-dev/LICENSE
Normal file
230
packages/node-dev/LICENSE
Normal file
@@ -0,0 +1,230 @@
|
||||
“Commons Clause” License Condition v1.0
|
||||
|
||||
The Software is provided to you by the Licensor under the
|
||||
License, as defined below, subject to the following condition.
|
||||
|
||||
Without limiting other conditions in the License, the grant
|
||||
of rights under the License will not include, and the License
|
||||
does not grant to you, the right to Sell the Software.
|
||||
|
||||
For purposes of the foregoing, “Sell” means practicing any or
|
||||
all of the rights granted to you under the License to provide
|
||||
to third parties, for a fee or other consideration (including
|
||||
without limitation fees for hosting or consulting/ support
|
||||
services related to the Software), a product or service whose
|
||||
value derives, entirely or substantially, from the functionality
|
||||
of the Software. Any license notice or attribution required by
|
||||
the License must also include this Commons Clause License
|
||||
Condition notice.
|
||||
|
||||
Software: n8n
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
Licensor: Jan Oberhauser
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
41
packages/node-dev/README.md
Normal file
41
packages/node-dev/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# n8n-node-dev
|
||||
|
||||

|
||||
|
||||
Currently very simple and not very sophisticated CLI which makes it easier
|
||||
to create credentials and nodes in TypeScript for n8n.
|
||||
|
||||
```
|
||||
npm install n8n-node-dev -g
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The commandline tool can be started with `n8n-node-dev <COMMAND>`
|
||||
|
||||
|
||||
|
||||
## Commands
|
||||
|
||||
The following commands exist:
|
||||
|
||||
|
||||
### build
|
||||
|
||||
Builds credentials and nodes in the current folder and copies them into the
|
||||
n8n custom extension folder (`~/.n8n/custom/`) unless destination path is
|
||||
overwritten with `--destination <FOLDER_PATH>`
|
||||
|
||||
When "--watch" gets set it starts in watch mode and automatically builds and
|
||||
copies files whenever they change. To stop press "ctrl + c".
|
||||
|
||||
|
||||
### new
|
||||
|
||||
Creates new basic credentials or node of the selected type to have a first starting point.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0 with Commons Clause](LICENSE)
|
||||
49
packages/node-dev/commands/build.ts
Normal file
49
packages/node-dev/commands/build.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import Vorpal = require('vorpal');
|
||||
import { Args } from 'vorpal';
|
||||
import {
|
||||
buildFiles,
|
||||
IBuildOptions,
|
||||
} from '../src';
|
||||
|
||||
import {
|
||||
UserSettings,
|
||||
} from "n8n-core";
|
||||
|
||||
module.exports = (vorpal: Vorpal) => {
|
||||
return vorpal
|
||||
.command('build')
|
||||
// @ts-ignore
|
||||
.description('Builds credentials and nodes and copies it to n8n custom extension folder')
|
||||
.option('--destination <folder>',
|
||||
`The path to copy the compiles files to [default: ${UserSettings.getUserN8nFolderCustomExtensionPath()}]`)
|
||||
.option('--watch',
|
||||
'Starts in watch mode and automatically builds and copies file whenever they change')
|
||||
.option('\n')
|
||||
.action(async (args: Args) => {
|
||||
|
||||
console.log('\nBuild credentials and nodes');
|
||||
console.log('=========================');
|
||||
|
||||
try {
|
||||
const options: IBuildOptions = {};
|
||||
|
||||
if (args.options.destination) {
|
||||
options.destinationFolder = args.options.destination;
|
||||
}
|
||||
if (args.options.watch) {
|
||||
options.watch = true;
|
||||
}
|
||||
|
||||
const outputDirectory = await buildFiles(options);
|
||||
|
||||
console.log(`The nodes got build and saved into the following folder:\n${outputDirectory}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\nGOT ERROR');
|
||||
console.error('====================================');
|
||||
console.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
160
packages/node-dev/commands/new.ts
Normal file
160
packages/node-dev/commands/new.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import * as changeCase from 'change-case';
|
||||
import * as fs from 'fs';
|
||||
import * as inquirer from 'inquirer';
|
||||
import { join } from 'path';
|
||||
import Vorpal = require('vorpal');
|
||||
import { Args } from 'vorpal';
|
||||
|
||||
const { promisify } = require('util');
|
||||
const fsAccess = promisify(fs.access);
|
||||
|
||||
import {
|
||||
createTemplate
|
||||
} from '../src';
|
||||
|
||||
module.exports = (vorpal: Vorpal) => {
|
||||
return vorpal
|
||||
.command('new')
|
||||
// @ts-ignore
|
||||
.description('Create new credentials/node')
|
||||
.action(async (args: Args) => {
|
||||
try {
|
||||
console.log('\nCreate new credentials/node');
|
||||
console.log('=========================');
|
||||
|
||||
// Ask for the type of not to be created
|
||||
const typeQuestion: inquirer.Questions = {
|
||||
name: 'type',
|
||||
type: 'list',
|
||||
default: 'Node',
|
||||
message: 'What do you want to create?',
|
||||
choices: [
|
||||
'Credentials',
|
||||
'Node',
|
||||
],
|
||||
};
|
||||
|
||||
const typeAnswers = await inquirer.prompt(typeQuestion);
|
||||
|
||||
let sourceFolder = '';
|
||||
const sourceFileName = 'simple.ts';
|
||||
let defaultName = '';
|
||||
let getDescription = false;
|
||||
|
||||
|
||||
if (typeAnswers.type === 'Node') {
|
||||
// Create new node
|
||||
|
||||
getDescription = true;
|
||||
|
||||
const nodeTypeQuestion: inquirer.Questions = {
|
||||
name: 'nodeType',
|
||||
type: 'list',
|
||||
default: 'Execute',
|
||||
message: 'What kind of node do you want to create?',
|
||||
choices: [
|
||||
'Execute',
|
||||
'Trigger',
|
||||
'Webhook',
|
||||
],
|
||||
};
|
||||
|
||||
const nodeTypeAnswers = await inquirer.prompt(nodeTypeQuestion);
|
||||
|
||||
// Choose a the template-source-file depending on user input.
|
||||
sourceFolder = 'execute';
|
||||
defaultName = 'My Node';
|
||||
if (nodeTypeAnswers.nodeType === 'Trigger') {
|
||||
sourceFolder = 'trigger';
|
||||
defaultName = 'My Trigger';
|
||||
} else if (nodeTypeAnswers.nodeType === 'Webhook') {
|
||||
sourceFolder = 'webhook';
|
||||
defaultName = 'My Webhook';
|
||||
}
|
||||
} else {
|
||||
// Create new credentials
|
||||
|
||||
sourceFolder = 'credentials';
|
||||
defaultName = 'My Service API';
|
||||
}
|
||||
|
||||
// Ask additional questions to know with what values the
|
||||
// variables in the template file should be replaced with
|
||||
const additionalQuestions = [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'input',
|
||||
default: defaultName,
|
||||
message: 'How should the node be called?',
|
||||
},
|
||||
];
|
||||
|
||||
if (getDescription === true) {
|
||||
// Get also a node description
|
||||
additionalQuestions.push({
|
||||
name: 'description',
|
||||
type: 'input',
|
||||
default: 'Node converts input data to chocolate',
|
||||
message: 'What should the node description be?',
|
||||
});
|
||||
}
|
||||
|
||||
const additionalAnswers = await inquirer.prompt(additionalQuestions as inquirer.Questions);
|
||||
|
||||
const nodeName = additionalAnswers.name;
|
||||
|
||||
// Define the source file to be used and the location and name of the new
|
||||
// node file
|
||||
const destinationFilePath = join(process.cwd(), `${changeCase.pascalCase(nodeName)}.${typeAnswers.type.toLowerCase()}.ts`);
|
||||
|
||||
const sourceFilePath = join(__dirname, '../../templates', sourceFolder, sourceFileName);
|
||||
|
||||
// Check if node with the same name already exists in target folder
|
||||
// to not overwrite it by accident
|
||||
try {
|
||||
await fsAccess(destinationFilePath);
|
||||
|
||||
// File does already exist. So ask if it should be overwritten.
|
||||
const overwriteQuestion: inquirer.Questions = [
|
||||
{
|
||||
name: 'overwrite',
|
||||
type: 'confirm',
|
||||
default: false,
|
||||
message: `The file "${destinationFilePath}" already exists and would be overwritten. Do you want to proceed and overwrite the file?`,
|
||||
},
|
||||
];
|
||||
|
||||
const overwriteAnswers = await inquirer.prompt(overwriteQuestion);
|
||||
|
||||
if (overwriteAnswers.overwrite === false) {
|
||||
console.log('\nNode creation got canceled!');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// File does not exist. That is exactly what we want so go on.
|
||||
}
|
||||
|
||||
// Make sure that the variables in the template file get formated
|
||||
// in the correct way
|
||||
const replaceValues = {
|
||||
ClassNameReplace: changeCase.pascalCase(nodeName),
|
||||
DisplayNameReplace: changeCase.titleCase(nodeName),
|
||||
N8nNameReplace: changeCase.camelCase(nodeName),
|
||||
NodeDescriptionReplace: additionalAnswers.description,
|
||||
};
|
||||
|
||||
await createTemplate(sourceFilePath, destinationFilePath, replaceValues);
|
||||
|
||||
console.log('\nExecution was successfull:');
|
||||
console.log('====================================');
|
||||
|
||||
console.log('Node got created: ' + destinationFilePath);
|
||||
} catch (error) {
|
||||
console.error('\nGOT ERROR');
|
||||
console.error('====================================');
|
||||
console.error(error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
40
packages/node-dev/index.ts
Normal file
40
packages/node-dev/index.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import Vorpal = require('vorpal');
|
||||
|
||||
if (process.argv.length === 2) {
|
||||
// When no command is given choose by default help
|
||||
process.argv.push('help');
|
||||
}
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
// Check if the command the user did enter is supported else stop
|
||||
const supportedCommands = [
|
||||
'build',
|
||||
'help',
|
||||
'new',
|
||||
];
|
||||
|
||||
if (!supportedCommands.includes(command)) {
|
||||
console.log(`The command "${command}" is not known!`);
|
||||
process.argv.push('help');
|
||||
}
|
||||
|
||||
const vorpal = new Vorpal();
|
||||
vorpal
|
||||
.use(require('./commands/build.js'))
|
||||
.use(require('./commands/new.js'))
|
||||
.delimiter('')
|
||||
.show()
|
||||
.parse(process.argv);
|
||||
|
||||
|
||||
process
|
||||
.on('unhandledRejection', (reason, p) => {
|
||||
console.error(reason, 'Unhandled Rejection at Promise', p);
|
||||
})
|
||||
.on('uncaughtException', err => {
|
||||
console.error(err, 'Uncaught Exception thrown');
|
||||
process.exit(1);
|
||||
});
|
||||
44
packages/node-dev/package.json
Normal file
44
packages/node-dev/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.1.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"author": {
|
||||
"name": "Jan Oberhauser",
|
||||
"email": "jan@n8n.io"
|
||||
},
|
||||
"main": "dist/src/index",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"tslint": "tslint -p tsconfig.json -c tslint.json",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"bin": {
|
||||
"n8n-node-dev": "./dist/index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"development",
|
||||
"node",
|
||||
"helper",
|
||||
"n8n"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^6.0.3",
|
||||
"@types/node": "^10.10.1",
|
||||
"@types/vorpal": "^1.11.0",
|
||||
"tslint": "^5.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"change-case": "^3.1.0",
|
||||
"inquirer": "^6.3.1",
|
||||
"n8n-core": "^0.1.0",
|
||||
"n8n-workflow": "^0.1.0",
|
||||
"replace-in-file": "^4.1.0",
|
||||
"typescript": "~3.3.0",
|
||||
"vorpal": "^1.12.0"
|
||||
}
|
||||
}
|
||||
63
packages/node-dev/src/Build.ts
Normal file
63
packages/node-dev/src/Build.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { join } from 'path';
|
||||
import { ChildProcess, spawn } from 'child_process';
|
||||
|
||||
import { IBuildOptions } from '.';
|
||||
|
||||
import {
|
||||
UserSettings,
|
||||
} from "n8n-core";
|
||||
|
||||
|
||||
/**
|
||||
* Builds and copies credentials and nodes
|
||||
*
|
||||
* @export
|
||||
* @param {IBuildOptions} [options] Options to overwrite default behaviour
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
||||
options = options || {};
|
||||
|
||||
// Get the path of the TypeScript cli of this project
|
||||
const tscPath = join(__dirname, '../../node_modules/typescript/bin/tsc');
|
||||
|
||||
// Get path to simple tsconfig file which should be used for build
|
||||
const tsconfigPath = join(__dirname, '../../src/tsconfig-build.json');
|
||||
|
||||
const outputDirectory = options.destinationFolder || UserSettings.getUserN8nFolderCustomExtensionPath();
|
||||
|
||||
let buildCommand = `${tscPath} --p ${tsconfigPath} --outDir ${outputDirectory}`;
|
||||
if (options.watch === true) {
|
||||
buildCommand += ' --watch';
|
||||
}
|
||||
|
||||
let buildProcess: ChildProcess;
|
||||
try {
|
||||
buildProcess = spawn('node', buildCommand.split(' '), { windowsVerbatimArguments: true, cwd: process.cwd() });
|
||||
|
||||
// Forward the output of the child process to the main one
|
||||
// that the user can see what is happening
|
||||
buildProcess.stdout.pipe(process.stdout);
|
||||
buildProcess.stderr.pipe(process.stderr);
|
||||
|
||||
// Make sure that the child process gets also always terminated
|
||||
// when the main process does
|
||||
process.on('exit', () => {
|
||||
buildProcess.kill();
|
||||
});
|
||||
} catch (error) {
|
||||
let errorMessage = error.message;
|
||||
|
||||
if (error.stdout !== undefined) {
|
||||
errorMessage = `${errorMessage}\nGot following output:\n${error.stdout}`;
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
buildProcess.on('exit', code => {
|
||||
resolve(outputDirectory);
|
||||
});
|
||||
});
|
||||
}
|
||||
36
packages/node-dev/src/Create.ts
Normal file
36
packages/node-dev/src/Create.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import replaceInFile, { ReplaceInFileConfig } from 'replace-in-file';
|
||||
|
||||
const { promisify } = require('util');
|
||||
const fsCopyFile = promisify(fs.copyFile);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new credentials or node
|
||||
*
|
||||
* @export
|
||||
* @param {string} sourceFilePath The path to the source template file
|
||||
* @param {string} destinationFilePath The path the write the new file to
|
||||
* @param {object} replaceValues The values to replace in the template file
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function createTemplate(sourceFilePath: string, destinationFilePath: string, replaceValues: object): Promise<void> {
|
||||
|
||||
// Copy the file to then replace the values in it
|
||||
await fsCopyFile(sourceFilePath, destinationFilePath);
|
||||
|
||||
// Replace the variables in the template file
|
||||
const options: ReplaceInFileConfig = {
|
||||
files: [
|
||||
destinationFilePath
|
||||
],
|
||||
from: [],
|
||||
to: [],
|
||||
};
|
||||
options.from = Object.keys(replaceValues).map((key) => {
|
||||
return new RegExp(key, 'g');
|
||||
});
|
||||
options.to = Object.values(replaceValues);
|
||||
await replaceInFile(options);
|
||||
}
|
||||
4
packages/node-dev/src/Interfaces.ts
Normal file
4
packages/node-dev/src/Interfaces.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IBuildOptions {
|
||||
destinationFolder?: string;
|
||||
watch?: boolean;
|
||||
}
|
||||
3
packages/node-dev/src/index.ts
Normal file
3
packages/node-dev/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './Build';
|
||||
export * from './Create';
|
||||
export * from './Interfaces';
|
||||
27
packages/node-dev/src/tsconfig-build.json
Normal file
27
packages/node-dev/src/tsconfig-build.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2017"
|
||||
],
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"importHelpers": true,
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"target": "es2017",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"../*.credentials.ts",
|
||||
"../*.node.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
27
packages/node-dev/templates/credentials/simple.ts
Normal file
27
packages/node-dev/templates/credentials/simple.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class ClassNameReplace implements ICredentialType {
|
||||
name = 'N8nNameReplace';
|
||||
displayName = 'DisplayNameReplace';
|
||||
properties = [
|
||||
// The credentials to get from user and save encrypted.
|
||||
// Properties can be defined exactly in the same way
|
||||
// as node properties.
|
||||
{
|
||||
displayName: 'User',
|
||||
name: 'user',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
57
packages/node-dev/templates/execute/simple.ts
Normal file
57
packages/node-dev/templates/execute/simple.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { IExecuteFunctions } from 'n8n-core';
|
||||
import {
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class ClassNameReplace implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'DisplayNameReplace',
|
||||
name: 'N8nNameReplace',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'NodeDescriptionReplace',
|
||||
defaults: {
|
||||
name: 'DisplayNameReplace',
|
||||
color: '#772244',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
// Node properties which the user gets displayed and
|
||||
// can change on the node.
|
||||
{
|
||||
displayName: 'My String',
|
||||
name: 'myString',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Placeholder value',
|
||||
description: 'The description text',
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
|
||||
const items = this.getInputData();
|
||||
|
||||
let item: INodeExecutionData;
|
||||
let myString: string;
|
||||
|
||||
// Itterates over all input items and add the key "myString" with the
|
||||
// value the parameter "myString" resolves to.
|
||||
// (This could be a different value for each item in case it contains an expression)
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
myString = this.getNodeParameter('myString', itemIndex, '') as string;
|
||||
item = items[itemIndex];
|
||||
|
||||
item.json['myString'] = myString;
|
||||
}
|
||||
|
||||
return this.prepareOutputData(items);
|
||||
|
||||
}
|
||||
}
|
||||
83
packages/node-dev/templates/trigger/simple.ts
Normal file
83
packages/node-dev/templates/trigger/simple.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { ITriggerFunctions } from 'n8n-core';
|
||||
import {
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ITriggerResponse,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class ClassNameReplace implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'DisplayNameReplace',
|
||||
name: 'N8nNameReplace',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'NodeDescriptionReplace',
|
||||
defaults: {
|
||||
name: 'DisplayNameReplace',
|
||||
color: '#00FF00',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
// Node properties which the user gets displayed and
|
||||
// can change on the node.
|
||||
{
|
||||
displayName: 'Interval',
|
||||
name: 'interval',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
default: 1,
|
||||
description: 'Every how many minutes the workflow should be triggered.',
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||
|
||||
const interval = this.getNodeParameter('interval', 1) as number;
|
||||
|
||||
if (interval <= 0) {
|
||||
throw new Error('The interval has to be set to at least 1 or higher!');
|
||||
}
|
||||
|
||||
const executeTrigger = () => {
|
||||
// Every time the emit function gets called a new workflow
|
||||
// executions gets started with the provided entries.
|
||||
const entry = {
|
||||
'exampleKey': 'exampleData'
|
||||
};
|
||||
this.emit([this.helpers.returnJsonArray([entry])]);
|
||||
};
|
||||
|
||||
// Sets an interval and triggers the workflow all n seconds
|
||||
// (depends on what the user selected on the node)
|
||||
const intervalValue = interval * 60 * 1000;
|
||||
const intervalObj = setInterval(executeTrigger, intervalValue);
|
||||
|
||||
// The "closeFunction" function gets called by n8n whenever
|
||||
// the workflow gets deactivated and can so clean up.
|
||||
async function closeFunction() {
|
||||
clearInterval(intervalObj);
|
||||
}
|
||||
|
||||
// The "manualTriggerFunction" function gets called by n8n
|
||||
// when a user is in the workflow editor and starts the
|
||||
// workflow manually. So the function has to make sure that
|
||||
// the emit() gets called with similar data like when it
|
||||
// would trigger by itself so that the user knows what data
|
||||
// to expect.
|
||||
async function manualTriggerFunction() {
|
||||
executeTrigger();
|
||||
}
|
||||
|
||||
return {
|
||||
closeFunction,
|
||||
manualTriggerFunction,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
70
packages/node-dev/templates/webhook/simple.ts
Normal file
70
packages/node-dev/templates/webhook/simple.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
INodeType,
|
||||
IWebhookResonseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class ClassNameReplace implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'DisplayNameReplace',
|
||||
name: 'N8nNameReplace',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'NodeDescriptionReplace',
|
||||
defaults: {
|
||||
name: 'DisplayNameReplace',
|
||||
color: '#885577',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
reponseMode: 'onReceived',
|
||||
// Each webhook property can either be hardcoded
|
||||
// like the above ones or referenced from a parameter
|
||||
// like the "path" property bellow
|
||||
path: '={{$parameter["path"]}}',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Path',
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '',
|
||||
required: true,
|
||||
description: 'The path to listen to',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResonseData> {
|
||||
|
||||
// The data to return and so start the workflow with
|
||||
const returnData: IDataObject[] = [];
|
||||
returnData.push(
|
||||
{
|
||||
body: this.getBodyData(),
|
||||
headers: this.getHeaderData(),
|
||||
query: this.getQueryData(),
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(returnData)
|
||||
],
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
31
packages/node-dev/tsconfig.json
Normal file
31
packages/node-dev/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2017"
|
||||
],
|
||||
"types": [
|
||||
"node",
|
||||
],
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist/",
|
||||
"target": "es2017",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.d.ts",
|
||||
"commands/**/*",
|
||||
"index.ts",
|
||||
"src/**/*",
|
||||
],
|
||||
"exclude": [
|
||||
"dist",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
103
packages/node-dev/tslint.json
Normal file
103
packages/node-dev/tslint.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"node_modules/**/*"
|
||||
]
|
||||
},
|
||||
"defaultSeverity": "error",
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"array-type": [
|
||||
true,
|
||||
"array-simple"
|
||||
],
|
||||
"arrow-return-shorthand": true,
|
||||
"ban": [
|
||||
true,
|
||||
{
|
||||
"name": "Array",
|
||||
"message": "tsstyle#array-constructor"
|
||||
}
|
||||
],
|
||||
"ban-types": [
|
||||
true,
|
||||
[
|
||||
"Object",
|
||||
"Use {} instead."
|
||||
],
|
||||
[
|
||||
"String",
|
||||
"Use 'string' instead."
|
||||
],
|
||||
[
|
||||
"Number",
|
||||
"Use 'number' instead."
|
||||
],
|
||||
[
|
||||
"Boolean",
|
||||
"Use 'boolean' instead."
|
||||
]
|
||||
],
|
||||
"class-name": true,
|
||||
"curly": [
|
||||
true,
|
||||
"ignore-same-line"
|
||||
],
|
||||
"forin": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"member-access": [
|
||||
true,
|
||||
"no-public"
|
||||
],
|
||||
"new-parens": true,
|
||||
"no-angle-bracket-type-assertion": true,
|
||||
"no-any": true,
|
||||
"no-arg": true,
|
||||
"no-conditional-assignment": true,
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-default-export": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-inferrable-types": true,
|
||||
"no-namespace": [
|
||||
true,
|
||||
"allow-declarations"
|
||||
],
|
||||
"no-reference": true,
|
||||
"no-string-throw": true,
|
||||
"no-unused-expression": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-shorthand": true,
|
||||
"only-arrow-functions": [
|
||||
true,
|
||||
"allow-declarations",
|
||||
"allow-named-functions"
|
||||
],
|
||||
"prefer-const": true,
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always",
|
||||
"ignore-bound-class-methods"
|
||||
],
|
||||
"switch-default": true,
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"use-isnan": true,
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"variable-name": [
|
||||
true,
|
||||
"check-format",
|
||||
"ban-keywords",
|
||||
"allow-leading-underscore",
|
||||
"allow-trailing-underscore"
|
||||
]
|
||||
},
|
||||
"rulesDirectory": []
|
||||
}
|
||||
Reference in New Issue
Block a user