From 28c6a94dea90ed3caaf5faa185607d7b9030ee47 Mon Sep 17 00:00:00 2001 From: ejseqera Date: Fri, 7 Nov 2025 14:07:14 -0500 Subject: [PATCH] feat: add datalink create node --- CLAUDE.md | 2 + nodes/_utils.js | 101 +++++++++++- nodes/datalink-add.html | 230 ++++++++++++++++++++++++++ nodes/datalink-add.js | 170 +++++++++++++++++++ nodes/datalink-utils.js | 12 +- package-lock.json | 350 +++++++++++++++++++++++++++++++++++++++- package.json | 7 + 7 files changed, 868 insertions(+), 4 deletions(-) create mode 100644 nodes/datalink-add.html create mode 100644 nodes/datalink-add.js diff --git a/CLAUDE.md b/CLAUDE.md index 8329377..d436d82 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,11 +51,13 @@ This config is referenced via `node.seqeraConfig = RED.nodes.getNode(config.seqe - `buildHeaders(node, extraHeaders)` - Constructs headers with Bearer token from seqera-config - `apiCall(node, method, url, options)` - Axios wrapper that merges auth headers, logs failures, and re-throws errors +- `resolveResource(RED, node, msg, resourceType, resourceName, options)` - Generic resolver for Seqera resources (credentials, pipelines, compute-envs, datasets, data-links) from name to ID - `handleDatalinkAutoComplete(RED, req, res)` - HTTP endpoint handler for Data Link name autocomplete (used by datalink-list and datalink-poll nodes) **[nodes/datalink-utils.js](nodes/datalink-utils.js) - Data Link specific utilities:** - `evalProp(RED, node, msg, value, type)` - Property evaluation helper supporting JSONata expressions +- `resolveCredentials(RED, node, msg, credentialsName, options)` - Convenience wrapper around `resolveResource` for credentials - `resolveDataLink(RED, node, msg, dataLinkName, options)` - Resolves Data Link by name, returns IDs and metadata - `listDataLink(RED, node, msg)` - Core implementation for listing files/folders from Data Links with filtering and recursion diff --git a/nodes/_utils.js b/nodes/_utils.js index 14a0f7d..28b7d4e 100644 --- a/nodes/_utils.js +++ b/nodes/_utils.js @@ -46,12 +46,111 @@ async function apiCall(node, method, url, options = {}) { node.warn({ message: `Seqera API ${method.toUpperCase()} call to ${url} failed`, error: err, + response: err.response?.data, // Include API error response request: { method: method.toUpperCase(), url, ...finalOpts }, }); throw err; } } +/** + * Generic resource resolver for Seqera Platform API. + * Resolves a resource name to its ID by querying the appropriate endpoint. + * + * Supported resource types: + * - credentials: /credentials → credentials[].id + * - data-links: /data-links → dataLinks[].id + * - pipelines: /pipelines → pipelines[].pipelineId + * - compute-envs: /compute-envs → computeEnvs[].id + * - datasets: /datasets → datasets[].id + * + * @param {object} RED - Node-RED runtime + * @param {object} node - Node-RED node instance + * @param {object} msg - Message object (for JSONata context) + * @param {string} resourceType - Type of resource (e.g., 'credentials', 'data-links', 'pipelines') + * @param {string} resourceName - Name of the resource to resolve + * @param {object} options - Options object with baseUrl and workspaceId + * @returns {Promise} The resource ID, or null if resourceName is empty + */ +async function resolveResource(RED, node, msg, resourceType, resourceName, { baseUrl, workspaceId = null } = {}) { + if (!resourceName || resourceName.trim() === "") { + return null; // No resource specified + } + + // Define metadata for each resource type + const resourceConfig = { + credentials: { + endpoint: "/credentials", + responseKey: "credentials", + idField: "id", + nameField: "name", + queryParams: { pageSize: "100" }, + }, + "data-links": { + endpoint: "/data-links", + responseKey: "dataLinks", + idField: "id", + nameField: "name", + queryParams: { pageSize: "100", search: resourceName }, + }, + pipelines: { + endpoint: "/pipelines", + responseKey: "pipelines", + idField: "pipelineId", + nameField: "name", + queryParams: { max: "50", offset: "0", search: resourceName, visibility: "all" }, + }, + "compute-envs": { + endpoint: "/compute-envs", + responseKey: "computeEnvs", + idField: "id", + nameField: "name", + queryParams: { max: "100" }, + }, + datasets: { + endpoint: "/datasets", + responseKey: "datasets", + idField: "id", + nameField: "name", + queryParams: { max: "100" }, + }, + }; + + const config = resourceConfig[resourceType]; + if (!config) { + throw new Error(`Unsupported resource type: ${resourceType}`); + } + + // Build query string + const qs = new URLSearchParams(); + if (workspaceId != null) qs.append("workspaceId", workspaceId); + for (const [key, value] of Object.entries(config.queryParams)) { + qs.append(key, value); + } + + const searchUrl = `${baseUrl.replace(/\/$/, "")}${config.endpoint}?${qs.toString()}`; + + const searchResp = await apiCall(node, "get", searchUrl, { headers: { Accept: "application/json" } }); + const items = searchResp.data?.[config.responseKey] || []; + + // Find exact match by name + const matchingItems = items.filter((item) => item[config.nameField] === resourceName); + + if (!matchingItems.length) { + throw new Error(`Could not find ${resourceType.replace("-", " ")} with name '${resourceName}'`); + } + if (matchingItems.length > 1) { + throw new Error( + `Found ${matchingItems.length} ${resourceType.replace( + "-", + " ", + )}s with name '${resourceName}'. Please use a unique name.`, + ); + } + + return matchingItems[0][config.idField]; +} + /** * Shared handler for datalink auto-complete HTTP endpoint. * Used by both datalink-list and datalink-poll nodes. @@ -137,4 +236,4 @@ async function handleDatalinkAutoComplete(RED, req, res) { } } -module.exports = { buildHeaders, apiCall, handleDatalinkAutoComplete }; +module.exports = { buildHeaders, apiCall, resolveResource, handleDatalinkAutoComplete }; diff --git a/nodes/datalink-add.html b/nodes/datalink-add.html new file mode 100644 index 0000000..aff911a --- /dev/null +++ b/nodes/datalink-add.html @@ -0,0 +1,230 @@ + + + + + + diff --git a/nodes/datalink-add.js b/nodes/datalink-add.js new file mode 100644 index 0000000..6587139 --- /dev/null +++ b/nodes/datalink-add.js @@ -0,0 +1,170 @@ +module.exports = function (RED) { + function SeqeraDatalinkAddNode(config) { + RED.nodes.createNode(this, config); + const node = this; + + // Store typedInput properties + node.dataLinkNameProp = config.dataLinkName; + node.dataLinkNamePropType = config.dataLinkNameType; + node.descriptionProp = config.description; + node.descriptionPropType = config.descriptionType; + node.resourceRefProp = config.resourceRef; + node.resourceRefPropType = config.resourceRefType; + node.providerProp = config.provider; + node.providerPropType = config.providerType; + node.credentialsNameProp = config.credentialsName; + node.credentialsNamePropType = config.credentialsNameType; + node.resourceType = config.resourceType || "bucket"; + node.publicAccessible = config.publicAccessible !== undefined ? config.publicAccessible : false; + node.baseUrlProp = config.baseUrl; + node.baseUrlPropType = config.baseUrlType; + node.workspaceIdProp = config.workspaceId; + node.workspaceIdPropType = config.workspaceIdType; + + // Reference to the shared Seqera config node + node.seqeraConfig = RED.nodes.getNode(config.seqera); + node.defaultBaseUrl = (node.seqeraConfig && node.seqeraConfig.baseUrl) || "https://api.cloud.seqera.io"; + node.credentials = RED.nodes.getCredentials(node.id); + + const { apiCall } = require("./_utils"); + const { resolveCredentials } = require("./datalink-utils"); + + // Helper to format date as yyyy-mm-dd HH:MM:SS + const formatDateTime = () => { + const d = new Date(); + const pad = (n) => n.toString().padStart(2, "0"); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${d.toLocaleTimeString()}`; + }; + + node.on("input", async function (msg, send, done) { + // evaluate properties + const evalProp = async (p, t) => { + if (t === "jsonata") { + const expr = RED.util.prepareJSONataExpression(p, node); + return await new Promise((resolve, reject) => { + RED.util.evaluateJSONataExpression(expr, msg, (err, value) => { + if (err) return reject(err); + resolve(value); + }); + }); + } + return RED.util.evaluateNodeProperty(p, t, node, msg); + }; + + const dataLinkName = await evalProp(node.dataLinkNameProp, node.dataLinkNamePropType); + const description = await evalProp(node.descriptionProp, node.descriptionPropType); + const resourceRef = await evalProp(node.resourceRefProp, node.resourceRefPropType); + const provider = await evalProp(node.providerProp, node.providerPropType); + const credentialsName = await evalProp(node.credentialsNameProp, node.credentialsNamePropType); + const baseUrlOverride = await evalProp(node.baseUrlProp, node.baseUrlPropType); + const workspaceIdOverride = await evalProp(node.workspaceIdProp, node.workspaceIdPropType); + + const baseUrl = baseUrlOverride || (node.seqeraConfig && node.seqeraConfig.baseUrl) || node.defaultBaseUrl; + const workspaceId = workspaceIdOverride || (node.seqeraConfig && node.seqeraConfig.workspaceId) || null; + + // Validate required fields + if (!dataLinkName) { + const err = new Error("dataLinkName not provided"); + if (done) done(err); + else node.error(err, msg); + return; + } + if (!resourceRef) { + const err = new Error("resourceRef not provided"); + if (done) done(err); + else node.error(err, msg); + return; + } + if (!provider) { + const err = new Error("provider not provided"); + if (done) done(err); + else node.error(err, msg); + return; + } + + try { + node.status({ fill: "blue", shape: "ring", text: `creating: ${formatDateTime()}` }); + + // Resolve credentials name to ID if provided + let credentialsId = null; + if (credentialsName && credentialsName.trim() !== "") { + node.status({ fill: "yellow", shape: "ring", text: `resolving credentials: ${formatDateTime()}` }); + credentialsId = await resolveCredentials(RED, node, msg, credentialsName, { + baseUrl, + workspaceId, + }); + } + + // Build the request body + const createBody = { + name: dataLinkName, + resourceRef: resourceRef, + provider: provider, + type: node.resourceType, + publicAccessible: String(node.publicAccessible), + }; + + // Add optional fields + if (description != null && description !== "") { + createBody.description = description; + } + if (credentialsId != null) { + createBody.credentialsId = credentialsId; + } + + // Build the URL with workspace ID + let createUrl = `${baseUrl.replace(/\/$/, "")}/data-links`; + if (workspaceId != null) createUrl += `?workspaceId=${workspaceId}`; + + // Make the API call + const createResp = await apiCall(node, "post", createUrl, { + headers: { "Content-Type": "application/json" }, + data: createBody, + }); + + const dataLinkId = createResp.data?.dataLink?.id || createResp.data?.id || null; + + const outMsg = { + ...msg, + payload: createResp.data, + dataLinkId: dataLinkId, + dataLinkName: dataLinkName, + }; + node.status({ fill: "green", shape: "dot", text: `created: ${formatDateTime()}` }); + send(outMsg); + if (done) done(); + } catch (err) { + node.status({ fill: "red", shape: "dot", text: `error: ${formatDateTime()}` }); + node.error(`Seqera data-link create failed: ${err.message}`, msg); + if (done) done(err); + return; + } + }); + } + + RED.nodes.registerType("seqera-datalink-add", SeqeraDatalinkAddNode, { + credentials: { token: { type: "password" } }, + defaults: { + name: { value: "" }, + seqera: { value: "", type: "seqera-config", required: true }, + dataLinkName: { value: "", required: true }, + dataLinkNameType: { value: "str" }, + description: { value: "" }, + descriptionType: { value: "str" }, + resourceRef: { value: "", required: true }, + resourceRefType: { value: "str" }, + provider: { value: "aws", required: true }, + providerType: { value: "str" }, + credentialsName: { value: "" }, + credentialsNameType: { value: "str" }, + resourceType: { value: "bucket" }, + publicAccessible: { value: false }, + workspaceId: { value: "" }, + workspaceIdType: { value: "str" }, + baseUrl: { value: "" }, + baseUrlType: { value: "str" }, + token: { value: "" }, + tokenType: { value: "str" }, + }, + }); +}; diff --git a/nodes/datalink-utils.js b/nodes/datalink-utils.js index 671060a..9a8662c 100644 --- a/nodes/datalink-utils.js +++ b/nodes/datalink-utils.js @@ -1,5 +1,5 @@ const axios = require("axios"); -const { apiCall } = require("./_utils"); +const { apiCall, resolveResource } = require("./_utils"); /** * Helper to evaluate a typed-input property value, supporting JSONata. @@ -18,6 +18,14 @@ async function evalProp(RED, node, msg, value, type) { return RED.util.evaluateNodeProperty(value, type, node, msg); } +/** + * Resolve Seqera Platform Credentials by name. Returns the credentials ID. + * Convenience wrapper around resolveResource from _utils.js. + */ +async function resolveCredentials(RED, node, msg, credentialsName, { baseUrl, workspaceId = null } = {}) { + return resolveResource(RED, node, msg, "credentials", credentialsName, { baseUrl, workspaceId }); +} + /** * Resolve a Seqera Platform Data Link by name. Returns an object containing * the IDs and metadata needed by other helper functions. @@ -176,6 +184,8 @@ async function listDataLink(RED, node, msg = {}) { } module.exports = { + evalProp, listDataLink, resolveDataLink, + resolveCredentials, }; diff --git a/package-lock.json b/package-lock.json index cb9968d..b620623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,37 @@ { "name": "@seqera/node-red-seqera", - "version": "1.1.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@seqera/node-red-seqera", - "version": "1.1.0", + "version": "1.3.0", "license": "Apache-2.0", "dependencies": { "axios": "^1.10.0", "form-data": "^4.0.3" }, + "devDependencies": { + "nodemon": "^3.1.10" + }, "engines": { "node": ">=12.0.0" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -33,6 +49,46 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -46,6 +102,30 @@ "node": ">= 0.4" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -58,6 +138,29 @@ "node": ">= 0.8" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -126,6 +229,18 @@ "node": ">= 0.4" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -162,6 +277,20 @@ "node": ">= 6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -208,6 +337,18 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -220,6 +361,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -259,6 +409,54 @@ "node": ">= 0.4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -289,11 +487,159 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true } } } diff --git a/package.json b/package.json index 575161a..4a0e97b 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "seqera-workflow-launch": "nodes/workflow-launch.js", "seqera-workflow-monitor": "nodes/workflow-monitor.js", "seqera-dataset-add": "nodes/dataset-add.js", + "seqera-datalink-add": "nodes/datalink-add.js", "seqera-datalink-list": "nodes/datalink-list.js", "seqera-datalink-poll": "nodes/datalink-poll.js", "seqera-studios-add": "nodes/studios-add.js", @@ -42,5 +43,11 @@ "examples": { "Seqera Platform": "examples" } + }, + "devDependencies": { + "nodemon": "^3.1.10" + }, + "scripts": { + "dev": "nodemon --exec 'node-red -u ./test-instance' --watch nodes --ext js" } }