From 21b15beb26e640bf477b699518c0a50685de2638 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Sat, 31 Aug 2024 01:28:18 +0530 Subject: [PATCH 01/24] feat: PREP Notifications Implements Per Resource Events notifications in Node Solid Server. Implementation Notes: + Uses `--experimental-require-module` to load esm packages natively. Requires node > 22.0.0. Start scripts and test invocations have been appropriately modified. + NSS converts strings into an older streaming format which was not being detected by Express-PREP. Express-PREP was modified to ask the user if the body provided is a stream, thus circumventing this issue. + Notifications are triggered from a common middleware, which is invoked after the response has been succesfully sent. + Uses Express PREP supplied default template for notifications. --- bin/solid | 2 +- bin/solid.js | 2 +- lib/create-app.js | 8 ++ lib/handlers/get.js | 45 +++++- lib/handlers/notify.js | 12 ++ lib/ldp-middleware.js | 5 +- package-lock.json | 310 ++++++++++++++++++++++++++++++++++++++--- package.json | 13 +- 8 files changed, 366 insertions(+), 31 deletions(-) create mode 100644 lib/handlers/notify.js diff --git a/bin/solid b/bin/solid index 059baef66..427aeb937 100755 --- a/bin/solid +++ b/bin/solid @@ -1,3 +1,3 @@ -#!/usr/bin/env node +#!/usr/bin/env -S node --experimental-require-module const startCli = require('./lib/cli') startCli() diff --git a/bin/solid.js b/bin/solid.js index 059baef66..427aeb937 100755 --- a/bin/solid.js +++ b/bin/solid.js @@ -1,3 +1,3 @@ -#!/usr/bin/env node +#!/usr/bin/env -S node --experimental-require-module const startCli = require('./lib/cli') startCli() diff --git a/lib/create-app.js b/lib/create-app.js index 0cc00b6bb..d98d884e0 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -28,6 +28,11 @@ const ResourceMapper = require('./resource-mapper') const aclCheck = require('@solid/acl-check') const { version } = require('../package.json') +const acceptEvents = require('express-accept-events').default +const events = require('express-negotiate-events').default +const eventID = require('express-prep/event-id').default +const prep = require('express-prep').default + const corsSettings = cors({ methods: [ 'OPTIONS', 'HEAD', 'GET', 'PATCH', 'POST', 'PUT', 'DELETE' @@ -61,6 +66,9 @@ function createApp (argv = {}) { const app = express() + // Add PREP support + app.use(acceptEvents, events, eventID, prep) + initAppLocals(app, argv, ldp) initHeaders(app) initViews(app, configPath) diff --git a/lib/handlers/get.js b/lib/handlers/get.js index b73146ce5..422665410 100644 --- a/lib/handlers/get.js +++ b/lib/handlers/get.js @@ -17,6 +17,7 @@ const translate = require('../utils.js').translate const error = require('../http-error') const RDFs = require('../ldp').mimeTypesAsArray() +const isRdf = require('../ldp').mimeTypeIsRdf async function handler (req, res, next) { const ldp = req.app.locals.ldp @@ -110,15 +111,39 @@ async function handler (req, res, next) { } // If request accepts the content-type we found + // if (stream && negotiator.mediaType([contentType])) { + // res.setHeader('Content-Type', contentType) + // if (contentRange) { + // const headers = { 'Content-Range': contentRange, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize } + // res.writeHead(206, headers) + // return stream.pipe(res) + // } else { + // return stream.pipe(res) + // } + // } + if (stream && negotiator.mediaType([contentType])) { - res.setHeader('Content-Type', contentType) + let headers = { + 'Content-Type': contentType + } if (contentRange) { - const headers = { 'Content-Range': contentRange, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize } - res.writeHead(206, headers) - return stream.pipe(res) - } else { - return stream.pipe(res) + headers = { + ...headers, + 'Content-Range': contentRange, + 'Accept-Ranges': 'bytes', + 'Content-Length': chunksize + } + res.statusCode = 206 } + + if (isRdf(contentType) && !res.sendEvents({ + config: { prep: '' }, + body: stream, + isBodyStream: true, + headers + })) return + res.set(headers) + return stream.pipe(res) } // If it is not in our RDFs we can't even translate, @@ -130,6 +155,14 @@ async function handler (req, res, next) { // Translate from the contentType found to the possibleRDFType desired const data = await translate(stream, baseUri, contentType, possibleRDFType) debug(req.originalUrl + ' translating ' + contentType + ' -> ' + possibleRDFType) + const headers = { + 'Content-Type': possibleRDFType + } + if (isRdf(contentType) && !res.sendEvents({ + config: { prep: '' }, + body: data, + headers + })) return res.setHeader('Content-Type', possibleRDFType) res.send(data) return next() diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js new file mode 100644 index 000000000..42ec21fa7 --- /dev/null +++ b/lib/handlers/notify.js @@ -0,0 +1,12 @@ +module.exports = handler + +function handler (req, res, next) { + res.events.prep.trigger({ + generateNotifications () { + return res.events.prep.defaultNotification({ + ...(res.method === 'POST') && { location: res.getHeader('Content-Location') } + }) + } + }) + next() +} diff --git a/lib/ldp-middleware.js b/lib/ldp-middleware.js index 07b1a82d4..cb76d341b 100644 --- a/lib/ldp-middleware.js +++ b/lib/ldp-middleware.js @@ -10,6 +10,7 @@ const del = require('./handlers/delete') const patch = require('./handlers/patch') const index = require('./handlers/index') const copy = require('./handlers/copy') +const notify = require('./handlers/notify') function LdpMiddleware (corsSettings) { const router = express.Router('/') @@ -24,9 +25,9 @@ function LdpMiddleware (corsSettings) { router.copy('/*', allow('Write'), copy) router.get('/*', index, allow('Read'), header.addPermissions, get) router.post('/*', allow('Append'), post) - router.patch('/*', allow('Append'), patch) + router.patch('/*', allow('Append'), patch, notify) router.put('/*', allow('Append'), put) - router.delete('/*', allow('Write'), del) + router.delete('/*', allow('Write'), del, notify) return router } diff --git a/package-lock.json b/package-lock.json index 731e9a292..7a875cf45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,10 @@ "cors": "^2.8.5", "debug": "^4.3.4", "express": "^4.18.3", + "express-accept-events": "^0.2.2", "express-handlebars": "^5.3.5", + "express-negotiate-events": "^0.2.1", + "express-prep": "^0.5.0", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", @@ -89,7 +92,7 @@ "whatwg-url": "11.0.0" }, "engines": { - "node": ">=12.0" + "node": ">=22.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -4127,7 +4130,9 @@ "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true, + "peer": true }, "node_modules/@graphql-typed-document-node/core": { "version": "3.2.0", @@ -4770,6 +4775,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "optional": true, + "peer": true, "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" @@ -4780,6 +4787,8 @@ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", "deprecated": "This functionality has been moved to @npmcli/fs", + "optional": true, + "peer": true, "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -4792,6 +4801,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -6655,6 +6666,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "devOptional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -6831,7 +6843,8 @@ "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==" + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true }, "node_modules/arg": { "version": "4.1.0", @@ -7713,6 +7726,8 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "optional": true, + "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -7741,6 +7756,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7752,6 +7769,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -7762,7 +7781,9 @@ "node_modules/cacache/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true, + "peer": true }, "node_modules/cached-path-relative": { "version": "1.1.0", @@ -8048,6 +8069,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "peer": true, "engines": { "node": ">=10" } @@ -8119,6 +8142,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "devOptional": true, "engines": { "node": ">=6" } @@ -8856,6 +8880,20 @@ "node": ">=0.10.0" } }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -10573,6 +10611,46 @@ "node": ">= 0.10.0" } }, + "node_modules/express-accept-events": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/express-accept-events/-/express-accept-events-0.2.2.tgz", + "integrity": "sha512-P9hVug6sMVZAFqzYK0vc/J18/PRXyaKCEBbvVYemEJEhb68MNNffiI/KZq3iDaWlHWYR+35BxM5n1FqFqHyKHg==", + "license": "MPL-2.0", + "dependencies": { + "debug": "^4.3.5", + "no-try": "^4.0.0", + "structured-field-utils": "1.1.0-nested-sf.0", + "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + } + }, + "node_modules/express-accept-events/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express-accept-events/node_modules/structured-headers": { + "name": "@cxres/structured-headers", + "version": "2.0.0-alpha.1-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", + "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "license": "MIT", + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, "node_modules/express-handlebars": { "version": "5.3.5", "resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-5.3.5.tgz", @@ -10586,6 +10664,123 @@ "node": ">=v10.24.1" } }, + "node_modules/express-negotiate-events": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/express-negotiate-events/-/express-negotiate-events-0.2.1.tgz", + "integrity": "sha512-ehSjuJ8MCPXKb+UsoNEWzosr9qOQPgzJng1xVi5UDSdsCCuN8YgseULxxMxwqnTMfVpceQ0F1Mpgdz+mQyRrJg==", + "license": "MPL-2.0", + "dependencies": { + "debug": "^4.3.5", + "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + } + }, + "node_modules/express-negotiate-events/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express-negotiate-events/node_modules/structured-headers": { + "name": "@cxres/structured-headers", + "version": "2.0.0-alpha.1-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", + "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "license": "MIT", + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, + "node_modules/express-prep": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.5.0.tgz", + "integrity": "sha512-TdFsBOW4fMXxmKg4FA5ah/NRPKGx853huf27QyQj+nuKrH32ajVdiGWTQTQerU/vYdlyZzeJh4Cr5kyFge3x/g==", + "license": "MPL-2.0", + "dependencies": { + "crypto-random-string": "^5.0.0", + "debug": "^4.3.5", + "dedent": "^1.5.3", + "lodash": "^4.17.21", + "no-try": "^4.0.0", + "structured-field-utils": "1.1.0-nested-sf.0", + "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + }, + "peerDependencies": { + "express-accept-events": "^0.2.2", + "express-negotiate-events": "^0.2.1" + }, + "peerDependenciesMeta": { + "express-negotiate-events": { + "optional": true + } + } + }, + "node_modules/express-prep/node_modules/crypto-random-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-5.0.0.tgz", + "integrity": "sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express-prep/node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/express-prep/node_modules/structured-headers": { + "name": "@cxres/structured-headers", + "version": "2.0.0-alpha.1-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", + "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "license": "MIT", + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, + "node_modules/express-prep/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/express-session": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", @@ -11174,6 +11369,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -11839,6 +12036,8 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "optional": true, + "peer": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -11850,6 +12049,8 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -11860,7 +12061,9 @@ "node_modules/hosted-git-info/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true, + "peer": true }, "node_modules/html-escaper": { "version": "2.0.2", @@ -12068,6 +12271,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "devOptional": true, "engines": { "node": ">=8" } @@ -12075,7 +12279,9 @@ "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true, + "peer": true }, "node_modules/inflight": { "version": "1.0.6", @@ -15781,6 +15987,8 @@ "version": "3.1.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -15792,6 +16000,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -15803,6 +16013,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -15814,6 +16026,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -15824,12 +16038,16 @@ "node_modules/minipass/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true, + "peer": true }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -15841,12 +16059,16 @@ "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true, + "peer": true }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "optional": true, + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -16186,6 +16408,12 @@ "isarray": "0.0.1" } }, + "node_modules/no-try": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/no-try/-/no-try-4.0.0.tgz", + "integrity": "sha512-M8zkUDrlKRXhEoDRDWt/5sJEXg4xRGL8rXvHDCXLH3J8QnfJsFjztYmAyJhLEMSMNsZkewXIxn9JO+pd73R5zg==", + "license": "MIT" + }, "node_modules/nocache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", @@ -16614,6 +16842,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-7.0.0.tgz", "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", + "optional": true, + "peer": true, "dependencies": { "hosted-git-info": "^3.0.2", "osenv": "^0.1.5", @@ -16625,6 +16855,8 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "peer": true, "bin": { "semver": "bin/semver" } @@ -16656,7 +16888,6 @@ "version": "1.5.0", "inBundle": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.1.90" } @@ -17319,7 +17550,6 @@ "version": "0.1.13", "inBundle": true, "license": "MIT", - "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -17481,7 +17711,6 @@ "version": "0.6.3", "inBundle": true, "license": "MIT", - "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -18552,8 +18781,7 @@ "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "inBundle": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/npm/node_modules/semver": { "version": "7.3.7", @@ -19290,6 +19518,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "optional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -19315,6 +19545,8 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "optional": true, + "peer": true, "dependencies": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -19375,6 +19607,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "optional": true, + "peer": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -20163,7 +20397,9 @@ "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "optional": true, + "peer": true }, "node_modules/prompts": { "version": "2.4.2", @@ -20287,6 +20523,8 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", "integrity": "sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==", + "optional": true, + "peer": true, "bin": { "qrcode-terminal": "bin/qrcode-terminal.js" } @@ -22537,6 +22775,8 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "optional": true, + "peer": true, "dependencies": { "minipass": "^3.1.1" }, @@ -23020,6 +23260,26 @@ "optional": true, "peer": true }, + "node_modules/structured-field-utils": { + "version": "1.1.0-nested-sf.0", + "resolved": "https://registry.npmjs.org/structured-field-utils/-/structured-field-utils-1.1.0-nested-sf.0.tgz", + "integrity": "sha512-fISHrNmYK9c1PZFxQdMPG19d3uVv1lXwiW9MkKEjjU8SWkpfPWMXmYt7Pw3lt7TshOjPauO8EgTgcu85qMga9g==", + "license": "MPL-2.0", + "dependencies": { + "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + } + }, + "node_modules/structured-field-utils/node_modules/structured-headers": { + "name": "@cxres/structured-headers", + "version": "2.0.0-alpha.1-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", + "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "license": "MIT", + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, "node_modules/structured-headers": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", @@ -23326,6 +23586,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "optional": true, + "peer": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -23342,6 +23604,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -23350,6 +23614,8 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -23360,7 +23626,9 @@ "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true, + "peer": true }, "node_modules/temp": { "version": "0.8.4", @@ -24043,6 +24311,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "optional": true, + "peer": true, "dependencies": { "unique-slug": "^2.0.0" } @@ -24051,6 +24321,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "optional": true, + "peer": true, "dependencies": { "imurmurhash": "^0.1.4" } @@ -24207,6 +24479,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==", + "optional": true, + "peer": true, "dependencies": { "builtins": "^1.0.3" } @@ -24214,7 +24488,9 @@ "node_modules/validate-npm-package-name/node_modules/builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==" + "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", + "optional": true, + "peer": true }, "node_modules/validator": { "version": "13.11.0", @@ -24498,6 +24774,8 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "optional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", diff --git a/package.json b/package.json index 9349a066d..194949dcf 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,10 @@ "cors": "^2.8.5", "debug": "^4.3.4", "express": "^4.18.3", + "express-accept-events": "^0.2.2", "express-handlebars": "^5.3.5", + "express-negotiate-events": "^0.2.1", + "express-prep": "^0.5.0", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", @@ -141,12 +144,12 @@ "main": "index.js", "scripts": { "build": "echo nothing to build", - "solid": "node ./bin/solid", + "solid": "node --experimental-require-module ./bin/solid", "standard": "standard \"{bin,examples,lib,test}/**/*.js\"", "validate": "node ./test/validate-turtle.js", - "nyc": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 nyc --reporter=text-summary mocha --recursive test/integration/ test/unit/", - "mocha": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/ test/unit/", - "mocha-integration": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/http-test.js", + "nyc": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 nyc --reporter=text-summary mocha -n experimental-require-module --recursive test/integration/ test/unit/", + "mocha": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha -n experimental-require-module --recursive test/integration/ test/unit/", + "mocha-integration": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha -n experimental-require-module --recursive test/integration/http-test.js", "mocha-account-creation-oidc": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/account-creation-oidc-test.js", "mocha-account-manager": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/account-manager-test.js", "mocha-account-template": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/account-template-test.js", @@ -181,6 +184,6 @@ "solid": "bin/solid" }, "engines": { - "node": ">=12.0" + "node": ">=22.0" } } From f4fb5a0e8c453441e22729515bae46e5dc5d1021 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Sat, 31 Aug 2024 01:28:58 +0530 Subject: [PATCH 02/24] feat: Add Solid-PREP Notifications Add Solid/Activity Streams format notifications: + Provides notifictions in JSON-LD and Turtle. + Extends notifications to PUT and POST methods. + Add Event-ID header field to the response of a write method. --- lib/handlers/delete.js | 2 + lib/handlers/get.js | 8 ++- lib/handlers/notify.js | 103 ++++++++++++++++++++++++++++--- lib/handlers/patch.js | 2 + lib/handlers/post.js | 4 ++ lib/handlers/put.js | 2 + lib/ldp-middleware.js | 4 +- lib/rdf-notification-template.js | 66 ++++++++++++++++++++ 8 files changed, 179 insertions(+), 12 deletions(-) create mode 100644 lib/rdf-notification-template.js diff --git a/lib/handlers/delete.js b/lib/handlers/delete.js index 77eb7f05f..c267cd266 100644 --- a/lib/handlers/delete.js +++ b/lib/handlers/delete.js @@ -9,6 +9,8 @@ async function handler (req, res, next) { try { await ldp.delete(req) debug('DELETE -- Ok.') + // Add event-id for notifications + res.setHeader('Event-ID', res.setEventID()) res.sendStatus(200) next() } catch (err) { diff --git a/lib/handlers/get.js b/lib/handlers/get.js index 422665410..af29a741d 100644 --- a/lib/handlers/get.js +++ b/lib/handlers/get.js @@ -19,6 +19,8 @@ const error = require('../http-error') const RDFs = require('../ldp').mimeTypesAsArray() const isRdf = require('../ldp').mimeTypeIsRdf +const prepConfig = 'accept=("message/rfc822" "application/ld+json" "text/turtle")' + async function handler (req, res, next) { const ldp = req.app.locals.ldp const includeBody = req.method === 'GET' @@ -104,7 +106,7 @@ async function handler (req, res, next) { debug(' sending data browser file: ' + dataBrowserPath) res.sendFile(dataBrowserPath) return - } else if (stream) { + } else if (stream) { // EXIT text/html res.setHeader('Content-Type', contentType) return stream.pipe(res) } @@ -137,7 +139,7 @@ async function handler (req, res, next) { } if (isRdf(contentType) && !res.sendEvents({ - config: { prep: '' }, + config: { prep: prepConfig }, body: stream, isBodyStream: true, headers @@ -159,7 +161,7 @@ async function handler (req, res, next) { 'Content-Type': possibleRDFType } if (isRdf(contentType) && !res.sendEvents({ - config: { prep: '' }, + config: { prep: prepConfig }, body: data, headers })) return diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index 42ec21fa7..fad9a6892 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -1,12 +1,101 @@ module.exports = handler +const libPath = require('path/posix') + +const headerTemplate = require('express-prep/templates').header +const solidRDFTemplate = require('../rdf-notification-template') + +const ALLOWED_RDF_MIME_TYPES = [ + 'application/ld+json', + 'application/activity+json', + 'text/turtle' +] + +function getActivity (method) { + if (method === 'DELETE') { + return 'Delete' + } + return 'Update' +} + +function getParentActivity (method, status) { + if (method === 'DELETE') { + return 'Remove' + } + if (status === 201) { + return 'Add' + } + return 'Update' +} + function handler (req, res, next) { - res.events.prep.trigger({ - generateNotifications () { - return res.events.prep.defaultNotification({ - ...(res.method === 'POST') && { location: res.getHeader('Content-Location') } - }) - } - }) + const { trigger, defaultNotification } = res.events.prep + + const { method } = req + const { statusCode } = res + const eventID = res.getHeader('event-id') + + const parent = `${libPath.dirname(req.path)}/` + const parentID = res.setEventID(parent) + const fullUrl = new URL(req.path, `${req.protocol}://${req.hostname}/`) + const parentUrl = new URL(parent, fullUrl) + + // Date is a hack since node does not seem to provide access to send date. + // Date needs to be shared with parent notification + const eventDate = res._header.match(/^Date: (.*?)$/m)?.[1] || + new Date().toUTCString() + + // If the resource itself newly created, + // it could not have been subscribed for notifications already + if (!((method === 'PUT' || method === 'PATCH') && statusCode === 201)) { + trigger({ + generateNotification ( + negotiatedFields + ) { + const mediaType = negotiatedFields['content-type'] + + if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { + return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ + activity: getActivity(method), + eventID, + object: String(fullUrl), + date: eventDate, + // We use eTag as a proxy for state for now + state: res.getHeader('ETag'), + mediaType + })}` + } else { + return defaultNotification({ + ...(res.method === 'POST') && { location: res.getHeader('Content-Location') } + }) + } + } + }) + } + + // Write a notification to parent container + // POST in Solid creates a child resource + if (method !== 'POST') { + trigger({ + path: parent, + generateNotification ( + negotiatedFields + ) { + const mediaType = negotiatedFields['content-type'] + if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { + return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ + activity: getParentActivity(method, statusCode), + eventID: parentID, + date: eventDate, + object: String(parentUrl), + target: statusCode === 201 ? String(fullUrl) : undefined, + eTag: undefined, + mediaType + })}` + } + } + }) + } + next() } diff --git a/lib/handlers/patch.js b/lib/handlers/patch.js index 481027fdf..c98e1470e 100644 --- a/lib/handlers/patch.js +++ b/lib/handlers/patch.js @@ -91,6 +91,8 @@ async function patchHandler (req, res, next) { return writeGraph(graph, resource, ldp.resourceMapper.resolveFilePath(req.hostname), ldp.serverUri) }) + // Add event-id for notifications + res.setHeader('Event-ID', res.setEventID()) // Send the status and result to the client res.status(resourceExists ? 200 : 201) res.send(result) diff --git a/lib/handlers/post.js b/lib/handlers/post.js index 5942519f9..cd136bd9b 100644 --- a/lib/handlers/post.js +++ b/lib/handlers/post.js @@ -72,6 +72,8 @@ async function handler (req, res, next) { // Handled by backpressure of streams! busboy.on('finish', function () { debug('Done storing files') + // Add event-id for notifications + res.setHeader('Event-ID', res.setEventID()) res.sendStatus(200) next() }) @@ -91,6 +93,8 @@ async function handler (req, res, next) { debug('File stored in ' + resourcePath) header.addLinks(res, links) res.set('Location', resourcePath) + // Add event-id for notifications + res.setHeader('Event-ID', res.setEventID()) res.sendStatus(201) next() }, diff --git a/lib/handlers/put.js b/lib/handlers/put.js index ba698ff97..d6b984b66 100644 --- a/lib/handlers/put.js +++ b/lib/handlers/put.js @@ -77,6 +77,8 @@ async function putStream (req, res, next, stream = req) { // Fails with Append on existing resource if (!req.originalUrl.endsWith('.acl')) await checkPermission(req, resourceExists) await ldp.put(req, stream, getContentType(req.headers)) + // Add event-id for notifications + res.setHeader('Event-ID', res.setEventID()) res.sendStatus(resourceExists ? 204 : 201) return next() } catch (err) { diff --git a/lib/ldp-middleware.js b/lib/ldp-middleware.js index cb76d341b..53eb90c27 100644 --- a/lib/ldp-middleware.js +++ b/lib/ldp-middleware.js @@ -24,9 +24,9 @@ function LdpMiddleware (corsSettings) { router.copy('/*', allow('Write'), copy) router.get('/*', index, allow('Read'), header.addPermissions, get) - router.post('/*', allow('Append'), post) + router.post('/*', allow('Append'), post, notify) router.patch('/*', allow('Append'), patch, notify) - router.put('/*', allow('Append'), put) + router.put('/*', allow('Append'), put, notify) router.delete('/*', allow('Write'), del, notify) return router diff --git a/lib/rdf-notification-template.js b/lib/rdf-notification-template.js new file mode 100644 index 000000000..8eeee2c7a --- /dev/null +++ b/lib/rdf-notification-template.js @@ -0,0 +1,66 @@ +const CONTEXT_ACTIVITYSTREAMS = 'https://www.w3.org/ns/activitystreams' +const CONTEXT_NOTIFICATION = 'https://www.w3.org/ns/solid/notification/v1' +const CONTEXT_XML_SCHEMA = 'http://www.w3.org/2001/XMLSchema' + +function generateJSONNotification ({ + activity: type, + eventId: id, + date: published, + object, + target, + state = undefined +}) { + return { + published, + type, + id, + object, + ...(type === 'Add') && { target }, + ...(type === 'Remove') && { origin: target }, + ...(state) && { state } + } +} + +function generateTurtleNotification ({ + activity, + eventId, + date, + object, + target, + state = undefined +}) { + const stateLine = `\n notify:state "${state}" ;` + + return `@prefix as: <${CONTEXT_ACTIVITYSTREAMS}#> . +@prefix notify: <${CONTEXT_NOTIFICATION}#> . +@prefix xsd: <${CONTEXT_XML_SCHEMA}#> . + +<${eventId}> a as:${activity} ;${state && stateLine} + as:object ${object} ; + as:published "${date}"^^xsd:dateTime .`.replaceAll('\n', '\r\n') +} + +function serializeToJSONLD (notification, isActivityStreams = false) { + notification['@context'] = [CONTEXT_NOTIFICATION] + if (!isActivityStreams) { + notification['@context'].unshift(CONTEXT_ACTIVITYSTREAMS) + } + return JSON.stringify(notification, null, 2) +} + +function rdfTemplate (props) { + const { mediaType } = props + if (mediaType[0] === 'application/activity+json' || (mediaType[0] === 'application/ld+json' && mediaType[1].get('profile')?.toLowerCase() === 'https://www.w3.org/ns/activitystreams')) { + return serializeToJSONLD(generateJSONNotification(props), true) + } + + if (mediaType[0] === 'application/ld+json') { + return serializeToJSONLD(generateJSONNotification(props)) + } + + if (mediaType[0] === 'text/turtle') { + return generateTurtleNotification(props) + } +} + +module.exports = rdfTemplate From 109ce0942df1341ec0dcfbe228b071b054a80ff9 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Sat, 31 Aug 2024 01:28:58 +0530 Subject: [PATCH 03/24] Bump CI and Docker to node v22 --- .github/workflows/ci.yml | 6 +++--- docker-image/src/Dockerfile | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7d7bb5b1..ef1db3f47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,11 +17,11 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [22.x] os: [ubuntu-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 # extract branch name - if: github.event_name == 'pull_request' run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: olegtarasov/get-tag@v2.1 id: tagName diff --git a/docker-image/src/Dockerfile b/docker-image/src/Dockerfile index 113127649..80e56d92c 100644 --- a/docker-image/src/Dockerfile +++ b/docker-image/src/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16-alpine +FROM node:22-alpine # hadolint ignore=DL3018 RUN apk add --no-cache openssl From 7b23e574a3fc3923aa0bf9fbcd8789d72521bba9 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Sat, 31 Aug 2024 02:18:32 +0530 Subject: [PATCH 04/24] Replace nyc with c8 nyc does not work with ESM called with require(). --- .github/workflows/ci.yml | 2 +- package-lock.json | 1307 +++++++++++++------------------------- package.json | 8 +- 3 files changed, 460 insertions(+), 857 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef1db3f47..edaad93ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: # test code - run: npm run standard - run: npm run validate - - run: npm run nyc + - run: npm run c8 # Test global install of the package - run: npm pack . - run: npm install -g solid-server-*.tgz diff --git a/package-lock.json b/package-lock.json index 7a875cf45..19e9c9aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ }, "devDependencies": { "@solid/solid-auth-oidc": "0.3.0", + "c8": "^10.1.2", "chai": "^4.4.1", "chai-as-promised": "7.1.1", "cross-env": "7.0.3", @@ -80,7 +81,6 @@ "mocha": "^10.3.0", "nock": "^13.5.4", "node-mocks-http": "^1.14.1", - "nyc": "15.1.0", "pre-commit": "1.2.2", "randombytes": "2.1.0", "sinon": "12.0.1", @@ -107,7 +107,8 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -128,7 +129,8 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.9.0" } @@ -137,7 +139,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.6.tgz", "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -167,7 +170,8 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -180,7 +184,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -192,7 +197,8 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -206,7 +212,8 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -215,13 +222,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@babel/core/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -230,7 +239,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -239,7 +249,8 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "devOptional": true, + "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -248,7 +259,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -260,7 +272,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", @@ -301,7 +314,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/compat-data": "^7.23.5", "@babel/helper-validator-option": "^7.23.5", @@ -317,7 +331,8 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "devOptional": true, + "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -405,7 +420,8 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.9.0" } @@ -414,7 +430,8 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/types": "^7.23.0" @@ -427,7 +444,8 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -452,7 +470,8 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.22.15" }, @@ -464,7 +483,8 @@ "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -542,7 +562,8 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -567,7 +588,8 @@ "version": "7.22.6", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -579,7 +601,8 @@ "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.9.0" } @@ -596,7 +619,8 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.9.0" } @@ -620,7 +644,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.6.tgz", "integrity": "sha512-wCfsbN4nBidDRhpDhvcKlzHWCTlgJYUUdSJfzXb2NuBssDSIjc3xcb+znA7l+zYsFljAcGM0aFkN40cR3lXiGA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/template": "^7.22.15", "@babel/traverse": "^7.23.6", @@ -711,7 +736,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", - "devOptional": true, + "optional": true, + "peer": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -2598,7 +2624,8 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.22.13", "@babel/parser": "^7.22.15", @@ -2612,7 +2639,8 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -2625,7 +2653,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -2637,7 +2666,8 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -2651,7 +2681,8 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -2660,13 +2691,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@babel/template/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -2675,7 +2708,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -2684,7 +2718,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -2696,7 +2731,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.6.tgz", "integrity": "sha512-czastdK1e8YByZqezMPFiZ8ahwVMh/ESl9vPgvgdB9AmFMGP5jfpFax74AQgl5zj4XHzqeYAg2l8PuUeRS1MgQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.23.5", "@babel/generator": "^7.23.6", @@ -2717,7 +2753,8 @@ "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" @@ -2730,7 +2767,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -2742,7 +2780,8 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -2756,7 +2795,8 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -2765,13 +2805,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/@babel/traverse/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.8.0" } @@ -2780,7 +2822,8 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -2789,7 +2832,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -2798,7 +2842,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -2810,7 +2855,8 @@ "version": "7.23.6", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -2820,6 +2866,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@digitalbazaar/http-client": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-3.4.1.tgz", @@ -4302,8 +4355,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -4320,8 +4372,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=12" }, @@ -4333,8 +4384,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=12" }, @@ -4346,15 +4396,13 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -4371,8 +4419,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -4387,8 +4434,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -4411,83 +4457,6 @@ "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -4662,7 +4631,8 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -4685,7 +4655,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -4851,7 +4822,6 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "optional": true, - "peer": true, "engines": { "node": ">=14" } @@ -6390,8 +6360,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "optional": true, - "peer": true + "devOptional": true }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", @@ -6666,7 +6635,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -6821,18 +6791,6 @@ "optional": true, "peer": true }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/application-config-path": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/application-config-path/-/application-config-path-0.1.1.tgz", @@ -6840,12 +6798,6 @@ "optional": true, "peer": true }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, "node_modules/arg": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", @@ -7609,7 +7561,6 @@ "version": "4.22.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", - "devOptional": true, "funding": [ { "type": "opencollective", @@ -7624,6 +7575,8 @@ "url": "https://github.com/sponsors/ai" } ], + "optional": true, + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001565", "electron-to-chromium": "^1.4.601", @@ -7722,6 +7675,220 @@ "node": ">= 0.8" } }, + "node_modules/c8": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz", + "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/c8/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/c8/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/c8/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/c8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/c8/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/c8/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/c8/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/cacache": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", @@ -7790,33 +7957,6 @@ "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==" }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/caching-transform/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/call-bind": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", @@ -7894,7 +8034,6 @@ "version": "1.0.30001571", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz", "integrity": "sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==", - "devOptional": true, "funding": [ { "type": "opencollective", @@ -7908,7 +8047,9 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "optional": true, + "peer": true }, "node_modules/canonicalize": { "version": "1.0.8", @@ -8142,7 +8283,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -8379,7 +8521,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/compare-versions": { "version": "3.6.0", @@ -8955,21 +9098,6 @@ "node": ">=4" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -9291,7 +9419,8 @@ "version": "1.4.616", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.616.tgz", "integrity": "sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/elliptic": { "version": "6.5.4", @@ -11106,23 +11235,6 @@ "node": ">=16.0.0" } }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -11235,19 +11347,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -11332,26 +11431,6 @@ "readable-stream": "^2.0.0" } }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -11443,7 +11522,8 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=6.9.0" } @@ -11491,15 +11571,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", @@ -11903,43 +11974,6 @@ "minimalistic-assert": "^1.0.1" } }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -12271,7 +12305,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -12921,15 +12956,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -13014,71 +13040,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -13108,20 +13069,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", @@ -13728,7 +13675,8 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "devOptional": true, + "optional": true, + "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -13802,7 +13750,8 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "devOptional": true, + "optional": true, + "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -14801,12 +14750,6 @@ "optional": true, "peer": true }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -15208,35 +15151,12 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "devOptional": true, + "optional": true, + "peer": true, "dependencies": { "yallist": "^3.0.2" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -16573,23 +16493,12 @@ "node": ">= 0.6" } }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/node-rsa": { "version": "0.4.2", @@ -19013,256 +18922,84 @@ "builtins": "^5.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "1.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/npm/node_modules/which": { - "version": "2.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/npm/node_modules/wrappy": { - "version": "1.0.2", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "4.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "optional": true, - "peer": true - }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/nyc/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "inBundle": true, + "license": "MIT", "dependencies": { - "aggregate-error": "^3.0.0" + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" }, "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "inBundle": true, + "license": "ISC" }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "boolbase": "^1.0.0" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "optional": true, + "peer": true + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -19627,20 +19364,12 @@ "node": ">=6" } }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "BlueOak-1.0.0" }, "node_modules/pane-registry": { "version": "2.4.26", @@ -19776,38 +19505,34 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "optional": true, - "peer": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "devOptional": true, + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", - "optional": true, - "peer": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, + "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -19847,7 +19572,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -19996,70 +19722,6 @@ "node": ">=6" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/pkg-up": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", @@ -20352,18 +20014,6 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/profile-pane": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/profile-pane/-/profile-pane-1.0.19.tgz", @@ -21321,18 +20971,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/remove-trailing-slash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz", @@ -22680,23 +22318,6 @@ "os-shim": "^0.1.2" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -23098,8 +22719,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -23113,8 +22733,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "optional": true, - "peer": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -23204,8 +22823,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -23213,15 +22831,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -23768,20 +23377,6 @@ "optional": true, "peer": true }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/text-decoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", @@ -23909,7 +23504,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "devOptional": true, + "optional": true, + "peer": true, "engines": { "node": ">=4" } @@ -24161,15 +23757,6 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/ua-parser-js": { "version": "1.0.37", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", @@ -24360,7 +23947,6 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", - "devOptional": true, "funding": [ { "type": "opencollective", @@ -24375,6 +23961,8 @@ "url": "https://github.com/sponsors/ai" } ], + "optional": true, + "peer": true, "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -24455,6 +24043,21 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==" }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -24751,8 +24354,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "optional": true, - "peer": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -24893,7 +24495,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "devOptional": true + "optional": true, + "peer": true }, "node_modules/yaml": { "version": "2.3.4", diff --git a/package.json b/package.json index 194949dcf..713f2669d 100644 --- a/package.json +++ b/package.json @@ -118,6 +118,7 @@ }, "devDependencies": { "@solid/solid-auth-oidc": "0.3.0", + "c8": "^10.1.2", "chai": "^4.4.1", "chai-as-promised": "7.1.1", "cross-env": "7.0.3", @@ -127,7 +128,6 @@ "mocha": "^10.3.0", "nock": "^13.5.4", "node-mocks-http": "^1.14.1", - "nyc": "15.1.0", "pre-commit": "1.2.2", "randombytes": "2.1.0", "sinon": "12.0.1", @@ -147,7 +147,7 @@ "solid": "node --experimental-require-module ./bin/solid", "standard": "standard \"{bin,examples,lib,test}/**/*.js\"", "validate": "node ./test/validate-turtle.js", - "nyc": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 nyc --reporter=text-summary mocha -n experimental-require-module --recursive test/integration/ test/unit/", + "c8": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 c8 --reporter=text-summary mocha -n experimental-require-module --recursive test/integration/ test/unit/", "mocha": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha -n experimental-require-module --recursive test/integration/ test/unit/", "mocha-integration": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha -n experimental-require-module --recursive test/integration/http-test.js", "mocha-account-creation-oidc": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/account-creation-oidc-test.js", @@ -159,11 +159,11 @@ "mocha-ldp": "cross-env NODE_TLS_REJECT_UNAUTHORIZED=0 mocha --recursive test/integration/ldp-test.js", "prepublishOnly": "npm test", "postpublish": "git push --follow-tags", - "test": "npm run standard && npm run validate && npm run nyc", + "test": "npm run standard && npm run validate && npm run c8", "clean": "rimraf config/templates config/views", "reset": "rimraf .db data && npm run clean" }, - "nyc": { + "c8": { "reporter": [ "html", "text-summary" From 3d93e1302e9f55a23436a93721b0fe3b042ed85c Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Mon, 9 Sep 2024 03:45:40 +0530 Subject: [PATCH 05/24] fix: Correct Parenting for Solid-PREP Notifications Parent path is correctly determined only for non root resources. Parent notifications are only generated when resources have a parent. --- lib/handlers/notify.js | 102 +++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index fad9a6892..be16a48f4 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -11,6 +11,13 @@ const ALLOWED_RDF_MIME_TYPES = [ 'text/turtle' ] +function getParent (path) { + if (path === '' || path === '/') return + const parent = libPath.dirname(path) + if (parent === '/') return + return `${parent}/` +} + function getActivity (method) { if (method === 'DELETE') { return 'Delete' @@ -34,11 +41,7 @@ function handler (req, res, next) { const { method } = req const { statusCode } = res const eventID = res.getHeader('event-id') - - const parent = `${libPath.dirname(req.path)}/` - const parentID = res.setEventID(parent) const fullUrl = new URL(req.path, `${req.protocol}://${req.hostname}/`) - const parentUrl = new URL(parent, fullUrl) // Date is a hack since node does not seem to provide access to send date. // Date needs to be shared with parent notification @@ -48,53 +51,64 @@ function handler (req, res, next) { // If the resource itself newly created, // it could not have been subscribed for notifications already if (!((method === 'PUT' || method === 'PATCH') && statusCode === 201)) { - trigger({ - generateNotification ( - negotiatedFields - ) { - const mediaType = negotiatedFields['content-type'] + try { + trigger({ + generateNotification ( + negotiatedFields + ) { + const mediaType = negotiatedFields['content-type'] - if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { - return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ - activity: getActivity(method), - eventID, - object: String(fullUrl), - date: eventDate, - // We use eTag as a proxy for state for now - state: res.getHeader('ETag'), - mediaType - })}` - } else { - return defaultNotification({ - ...(res.method === 'POST') && { location: res.getHeader('Content-Location') } - }) + if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { + return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ + activity: getActivity(method), + eventID, + object: String(fullUrl), + date: eventDate, + // We use eTag as a proxy for state for now + state: res.getHeader('ETag'), + mediaType + })}` + } else { + return defaultNotification({ + ...(res.method === 'POST') && { location: res.getHeader('Content-Location') } + }) + } } - } - }) + }) + } catch (error) { + // Failed notification message + } } // Write a notification to parent container // POST in Solid creates a child resource - if (method !== 'POST') { - trigger({ - path: parent, - generateNotification ( - negotiatedFields - ) { - const mediaType = negotiatedFields['content-type'] - if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { - return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ - activity: getParentActivity(method, statusCode), - eventID: parentID, - date: eventDate, - object: String(parentUrl), - target: statusCode === 201 ? String(fullUrl) : undefined, - eTag: undefined, - mediaType - })}` + const parent = getParent(req.path) + if (parent && method !== 'POST') { + try { + const parentID = res.setEventID(parent) + const parentUrl = new URL(parent, fullUrl) + trigger({ + path: parent, + generateNotification ( + negotiatedFields + ) { + const mediaType = negotiatedFields['content-type'] + if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { + return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ + activity: getParentActivity(method, statusCode), + eventID: parentID, + date: eventDate, + object: String(parentUrl), + target: statusCode === 201 ? String(fullUrl) : undefined, + eTag: undefined, + mediaType + })}` + } } - } - }) + }) + } catch (error) { + // Failed notification message + } } next() From fd9908113ee800ca0396342477e193cd54857a23 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Mon, 9 Sep 2024 04:36:05 +0530 Subject: [PATCH 06/24] Step Down to Node 20 Node 20.17.0 LTS supports require(ESM). Therefore, allowing current Node LTS as well. --- .github/workflows/ci.yml | 2 +- docker-image/src/Dockerfile | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edaad93ac..d513c28c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - node-version: [22.x] + node-version: [ '>=20.17.0' ] os: [ubuntu-latest] steps: diff --git a/docker-image/src/Dockerfile b/docker-image/src/Dockerfile index 80e56d92c..4a00b949c 100644 --- a/docker-image/src/Dockerfile +++ b/docker-image/src/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine +FROM node:20-alpine # hadolint ignore=DL3018 RUN apk add --no-cache openssl diff --git a/package.json b/package.json index 713f2669d..56383792a 100644 --- a/package.json +++ b/package.json @@ -184,6 +184,6 @@ "solid": "bin/solid" }, "engines": { - "node": ">=22.0" + "node": ">=20.17.0 <21 || >=22.8.0" } } From 80216332585fbea5ba658133ba94aa0d6148f18f Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Mon, 9 Sep 2024 05:04:37 +0530 Subject: [PATCH 07/24] fix: Relax Content-Type Checks in Integration Tests When checking response `Content-Type` during integration tests, relaxed all media-type values to regex. --- test/integration/formats-test.js | 4 ++-- test/integration/http-test.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/integration/formats-test.js b/test/integration/formats-test.js index 4a8ef11b1..e31463968 100644 --- a/test/integration/formats-test.js +++ b/test/integration/formats-test.js @@ -112,13 +112,13 @@ describe('formats', function () { it('Accept text/plain', function (done) { server.get('/put-input.txt') .set('accept', 'text/plain') - .expect('Content-type', 'text/plain') + .expect('Content-type', /text\/plain/) .expect(200, done) }) it('Accept text/turtle', function (done) { server.get('/put-input.txt') .set('accept', 'text/turtle') - .expect('Content-type', 'text/plain; charset=utf-8') + .expect('Content-type', /text\/plain/) .expect(406, done) }) }) diff --git a/test/integration/http-test.js b/test/integration/http-test.js index 58c9f714f..81b7be3f9 100644 --- a/test/integration/http-test.js +++ b/test/integration/http-test.js @@ -349,7 +349,7 @@ describe('HTTP APIs', function () { it('should return resource link for files', function (done) { server.get('/hello.html') .expect('Link', /; rel="type"/) - .expect('Content-Type', 'text/html') + .expect('Content-Type', /text\/html/) .expect(200, done) }) it('should have glob support', function (done) { @@ -460,30 +460,30 @@ describe('HTTP APIs', function () { describe('HEAD API', function () { it('should return content-type application/octet-stream by default', function (done) { server.head('/sampleContainer/blank') - .expect('Content-Type', 'application/octet-stream; charset=utf-8') + .expect('Content-Type', /application\/octet-stream/) .end(done) }) it('should return content-type text/turtle for container', function (done) { server.head('/sampleContainer2/') - .expect('Content-Type', 'text/turtle; charset=utf-8') + .expect('Content-Type', /text\/turtle/) .end(done) }) it('should have set content-type for turtle files', function (done) { server.head('/sampleContainer2/example1.ttl') - .expect('Content-Type', 'text/turtle; charset=utf-8') + .expect('Content-Type', /text\/turtle/) .end(done) }) it('should have set content-type for implicit turtle files', function (done) { server.head('/sampleContainer/example4') - .expect('Content-Type', 'text/turtle; charset=utf-8') + .expect('Content-Type', /text\/turtle/) .end(done) }) it('should have set content-type for image files', function (done) { server.head('/sampleContainer/solid.png') - .expect('Content-Type', 'image/png; charset=utf-8') + .expect('Content-Type', /image\/png/) .end(done) }) it('should have Access-Control-Allow-Origin as Origin', function (done) { @@ -517,7 +517,7 @@ describe('HTTP APIs', function () { it('should have set Content-Type as text/turtle for Container', function (done) { server.head('/sampleContainer2/') - .expect('Content-Type', 'text/turtle; charset=utf-8') + .expect('Content-Type', /text\/turtle/) .expect(200, done) }) it('should have set Link as Container/BasicContainer', From e0ebc311d6d284dcd33c6f858fa15896219832d1 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Mon, 9 Sep 2024 06:01:46 +0530 Subject: [PATCH 08/24] fix: Repository for Surface Tests Picks the repository from where the PR branch exists. --- .github/workflows/ci.yml | 15 ++++++++++++++- test/surface/docker/server/Dockerfile | 3 ++- test/surface/run-solid-test-suite.sh | 8 ++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d513c28c4..7df9e1371 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,12 +22,25 @@ jobs: steps: - uses: actions/checkout@v4 + + # extract repository name + - if: github.event_name == 'pull_request' + run: echo "REPO_NAME=${{ github.event.pull_request.head.repo.full_name }}" >> $GITHUB_ENV + + - if: github.event_name != 'pull_request' + run: echo "REPO_NAME=${GITHUB_REPOSITORY}" >> $GITHUB_ENV + # extract branch name - if: github.event_name == 'pull_request' run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + - if: github.event_name != 'pull_request' run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + # print repository name + - name: Get repository name + run: echo 'The repository name is' $REPO_NAME + # print branch name - name: Get branch name run: echo 'The branch name is' $BRANCH_NAME @@ -45,7 +58,7 @@ jobs: - run: npm pack . - run: npm install -g solid-server-*.tgz # Run the Solid test-suite - - run: bash test/surface/run-solid-test-suite.sh $BRANCH_NAME + - run: bash test/surface/run-solid-test-suite.sh $BRANCH_NAME $REPO_NAME # TODO: The pipeline should automate publication to npm, so that the docker build gets the correct version # This job will only dockerize solid-server@latest / solid-server@ from npmjs.com! diff --git a/test/surface/docker/server/Dockerfile b/test/surface/docker/server/Dockerfile index 300739334..293be8ac3 100644 --- a/test/surface/docker/server/Dockerfile +++ b/test/surface/docker/server/Dockerfile @@ -1,7 +1,8 @@ FROM node:latest ARG BRANCH=main +ARG REPO=nodeSolidServer/node-solid-server RUN echo Testing branch ${BRANCH} of NSS -RUN git clone https://github.com/nodeSolidServer/node-solid-server +RUN git clone https://github.com/${REPO} WORKDIR node-solid-server RUN git checkout ${BRANCH} RUN git status diff --git a/test/surface/run-solid-test-suite.sh b/test/surface/run-solid-test-suite.sh index d55540bcc..1e385dc5f 100755 --- a/test/surface/run-solid-test-suite.sh +++ b/test/surface/run-solid-test-suite.sh @@ -3,8 +3,9 @@ set -e function setup { echo Branch name: $1 + echo Repoitory: $2 docker network create testnet - docker build -t server --build-arg BRANCH=$1 test/surface/docker/server + docker build -t server --build-arg BRANCH=$1 --build-arg REPO=$2 test/surface/docker/server docker build -t cookie test/surface/docker/cookie docker run -d --env-file test/surface/server-env.list --name server --network=testnet -v `pwd`:/travis -w /node-solid-server server /travis/bin/solid-test start --config-file /node-solid-server/config.json docker run -d --env-file test/surface/thirdparty-env.list --name thirdparty --network=testnet -v `pwd`/test/surface:/surface server /node-solid-server/bin/solid-test start --config-file /surface/thirdparty-config.json @@ -32,7 +33,7 @@ function waitForNss { function runTests { docker pull solidtestsuite/$1:$2 - + echo "Running $1 against server with cookie $COOKIE_server" docker run --rm --network=testnet \ --env COOKIE="$COOKIE_server" \ @@ -54,7 +55,7 @@ function runTestsFromGit { # ... teardown || true -setup $1 +setup $1 $2 waitForNss server runTests webid-provider-tests v2.0.3 runTestsFromGit solid-crud-tests v6.0.0-issue#1743 @@ -72,4 +73,3 @@ teardown # --env COOKIE_BOB="$COOKIE_thirdparty" \ # --env-file test/surface/web-access-control-tests-env.list \ # solidtestsuite/web-access-control-tests:latest /bin/bash - From beaaa032b6f9ea5efe4efca96a2834b3df8a26cb Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Tue, 8 Oct 2024 00:16:04 +0530 Subject: [PATCH 09/24] chore: Bump Express PREP Fixes the following issues: + Linebreaks are not sent after boundaries in the same notification, instead they are sent at the start of notification. + DELETE triggers lastEvent only if the path for notification is the same as the one where events occured. --- package-lock.json | 16 ++++++++++------ package.json | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19e9c9aeb..745f73e60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "express-accept-events": "^0.2.2", "express-handlebars": "^5.3.5", "express-negotiate-events": "^0.2.1", - "express-prep": "^0.5.0", + "express-prep": "^0.5.4", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", @@ -92,7 +92,7 @@ "whatwg-url": "11.0.0" }, "engines": { - "node": ">=22.0" + "node": ">=20.17.0 <21 || >=22.8.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -10832,9 +10832,9 @@ } }, "node_modules/express-prep": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.5.0.tgz", - "integrity": "sha512-TdFsBOW4fMXxmKg4FA5ah/NRPKGx853huf27QyQj+nuKrH32ajVdiGWTQTQerU/vYdlyZzeJh4Cr5kyFge3x/g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.5.4.tgz", + "integrity": "sha512-2PyQfwDt0+T6uXLceSpHWHKWrf0fqmK9quWTEkcCezUhG7+v1E0EozivNCPJ8A4LGUAdyAz1hWiN3+hmX/tQJg==", "license": "MPL-2.0", "dependencies": { "crypto-random-string": "^5.0.0", @@ -16797,6 +16797,7 @@ "version": "1.5.0", "inBundle": true, "license": "MIT", + "optional": true, "engines": { "node": ">=0.1.90" } @@ -17459,6 +17460,7 @@ "version": "0.1.13", "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -17620,6 +17622,7 @@ "version": "0.6.3", "inBundle": true, "license": "MIT", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -18690,7 +18693,8 @@ "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "inBundle": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/npm/node_modules/semver": { "version": "7.3.7", diff --git a/package.json b/package.json index 56383792a..d5e140726 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "express-accept-events": "^0.2.2", "express-handlebars": "^5.3.5", "express-negotiate-events": "^0.2.1", - "express-prep": "^0.5.0", + "express-prep": "^0.5.4", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", From bda034a43edcc1fdd6c30a22e6022dd3787150d5 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Tue, 8 Oct 2024 08:10:09 +0530 Subject: [PATCH 10/24] feat: Add Tests for PREP --- package-lock.json | 69 ++++++++++ package.json | 6 +- test/integration/prep-test.js | 228 ++++++++++++++++++++++++++++++++++ 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 test/integration/prep-test.js diff --git a/package-lock.json b/package-lock.json index 745f73e60..455826a3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "solid": "bin/solid" }, "devDependencies": { + "@cxres/structured-headers": "^2.0.0-alpha.1-nesting.0", "@solid/solid-auth-oidc": "0.3.0", "c8": "^10.1.2", "chai": "^4.4.1", @@ -82,6 +83,7 @@ "nock": "^13.5.4", "node-mocks-http": "^1.14.1", "pre-commit": "1.2.2", + "prep-fetch": "^0.1.0", "randombytes": "2.1.0", "sinon": "12.0.1", "sinon-chai": "3.7.0", @@ -2873,6 +2875,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@cxres/structured-headers": { + "version": "2.0.0-alpha.1-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", + "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, "node_modules/@digitalbazaar/http-client": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@digitalbazaar/http-client/-/http-client-3.4.1.tgz", @@ -5859,6 +5872,13 @@ "react-native": "*" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@segment/loosely-validate-event": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", @@ -16129,6 +16149,21 @@ "resolved": "https://registry.npmjs.org/msrcrypto/-/msrcrypto-1.5.8.tgz", "integrity": "sha512-ujZ0TRuozHKKm6eGbKHfXef7f+esIhEckmThVnz7RNyiOJd7a6MXj2JGBoL9cnPDW+JMG16MoTUh5X+XXjI66Q==" }, + "node_modules/multipart-fetch": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/multipart-fetch/-/multipart-fetch-0.1.1.tgz", + "integrity": "sha512-CgkvfFI6owa28eK8ctdkyKauUwTMJUogwuiY7KOKZaXRxLmmBRaP9YJ2mFisYglKAxMZnoGrBfPJn+jDTCiOfA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "content-type": "^1.0.5", + "streamsearch-web": "^1.0.0" + }, + "engines": { + "node": ">20.6" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -19976,6 +20011,31 @@ "node": ">= 0.8.0" } }, + "node_modules/prep-fetch": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/prep-fetch/-/prep-fetch-0.1.0.tgz", + "integrity": "sha512-11fKs96FHue4VOP2CeOxV5TPEOb0eVfC+2wQ6CbTQ79oG2lVUBZIp2WWxVKv27/iCWz93Fd2u53A71skFezyeQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "multipart-fetch": "^0.1.0", + "structured-headers": "^1.0.1" + }, + "engines": { + "node": ">20.6" + } + }, + "node_modules/prep-fetch/node_modules/structured-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-1.0.1.tgz", + "integrity": "sha512-QYBxdBtA4Tl5rFPuqmbmdrS9kbtren74RTJTcs0VSQNVV5iRhJD4QlYTLD0+81SBwUQctjEQzjTRI3WG4DzICA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14", + "npm": ">=6" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -22684,6 +22744,15 @@ "node": ">= 0.10.0" } }, + "node_modules/streamsearch-web": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/streamsearch-web/-/streamsearch-web-1.0.0.tgz", + "integrity": "sha512-KBBU/O/xSjbr1z+NPwLE9iTrE3Pc/Ue7HumjvjjP1t7oYIM35OOMYRy/lZBoIwsiSKTnQ+uF8QbaJEa7FdJIzA==", + "dev": true, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", diff --git a/package.json b/package.json index d5e140726..67448b579 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "vhost": "^3.0.2" }, "devDependencies": { + "@cxres/structured-headers": "^2.0.0-alpha.1-nesting.0", "@solid/solid-auth-oidc": "0.3.0", "c8": "^10.1.2", "chai": "^4.4.1", @@ -129,6 +130,7 @@ "nock": "^13.5.4", "node-mocks-http": "^1.14.1", "pre-commit": "1.2.2", + "prep-fetch": "^0.1.0", "randombytes": "2.1.0", "sinon": "12.0.1", "sinon-chai": "3.7.0", @@ -177,7 +179,9 @@ "before", "beforeEach", "describe", - "it" + "it", + "fetch", + "AbortController" ] }, "bin": { diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js new file mode 100644 index 000000000..dceb5a2bd --- /dev/null +++ b/test/integration/prep-test.js @@ -0,0 +1,228 @@ +const fs = require('fs') +const path = require('path') +const { expect } = require('chai') +const { parseDictionary } = require('structured-headers') +const prepFetch = require('prep-fetch').default +const { createServer } = require('../utils') + +const samplePath = path.join(__dirname, '../resources', 'sampleContainer') +const sampleFile = fs.readFileSync(path.join(samplePath, 'example1.ttl')) + +describe('Per Resource Events Protocol', function () { + let server + + before((done) => { + server = createServer({ + live: true, + dataBrowserPath: 'default', + root: path.join(__dirname, '../resources'), + auth: 'oidc', + webid: false + }) + server.listen(8443, done) + }) + + after(() => { + server.close() + }) + + it('should set `Accept-Events` header on a GET response with "prep"', + async function () { + const response = await fetch('http://localhost:8443/sampleContainer/example1.ttl') + expect(response.headers.get('Accept-Events')).to.match(/^"prep"/) + expect(response.status).to.equal(200) + } + ) + + it('should send an ordinary response, if `Accept-Events` header is not specified', + async function () { + const response = await fetch('http://localhost:8443/sampleContainer/example1.ttl') + expect(response.headers.get('Content-Type')).to.match(/text\/turtle/) + expect(response.headers.has('Events')).to.equal(false) + expect(response.status).to.equal(200) + }) + + describe('with prep response on container', async function () { + let response + let prepResponse + const controller = new AbortController() + const { signal } = controller + + it('should set headers correctly', async function () { + response = await fetch('http://localhost:8443/sampleContainer/', { + headers: { + 'Accept-Events': '"prep";accept=application/ld+json', + Accept: 'text/turtle' + }, + signal + }) + expect(response.status).to.equal(200) + expect(response.headers.get('Vary')).to.match(/Accept-Events/) + const eventsHeader = parseDictionary(response.headers.get('Events')) + expect(eventsHeader.get('protocol')?.[0]).to.equal('prep') + expect(eventsHeader.get('status')?.[0]).to.equal(200) + expect(eventsHeader.get('expires')?.[0]).to.be.a('string') + expect(response.headers.get('Content-Type')).to.match(/^multipart\/mixed/) + }) + + it('should send a representation as the first part, matching the content size on disk', + async function () { + prepResponse = prepFetch(response) + const representation = await prepResponse.getRepresentation() + expect(representation.headers.get('Content-Type')).to.match(/text\/turtle/) + await representation.text() + }) + + describe('should send notifications in the second part', async function () { + let notifications + let notificationsIterator + + it('when a contained resource is created', async function () { + notifications = await prepResponse.getNotifications() + notificationsIterator = notifications.notifications() + await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', { + method: 'PUT', + headers: { + 'Content-Type': 'text/turtle' + }, + body: sampleFile + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Add') + expect(notification.target).to.match(/sampleContainer\/example-prep\.ttl$/) + expect(notification.object).to.match(/sampleContainer\/$/) + }) + + it('when contained resource is modified', async function () { + await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', { + method: 'PATCH', + headers: { + 'Content-Type': 'text/n3' + }, + body: `@prefix solid: . +<> a solid:InsertDeletePatch; +solid:inserts { . }.` + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Update') + expect(notification.object).to.match(/sampleContainer\/$/) + }) + + it('when contained resource is deleted', + async function () { + await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', { + method: 'DELETE' + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Remove') + expect(notification.object).to.match(/sampleContainer\/$/) + }) + + it('when resource is created by POST', + async function () { + await fetch('http://localhost:8443/sampleContainer/', { + method: 'POST', + headers: { + slug: 'example-prep.ttl', + 'content-type': 'text/turtle' + }, + body: sampleFile + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Update') + expect(notification.object).to.match(/sampleContainer\/$/) + controller.abort() + }) + }) + }) + + describe('with prep response on RDF resource', async function () { + let response + let prepResponse + + it('should set headers correctly', async function () { + response = await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', { + headers: { + 'Accept-Events': '"prep";accept=application/ld+json', + Accept: 'text/n3' + } + }) + expect(response.status).to.equal(200) + expect(response.headers.get('Vary')).to.match(/Accept-Events/) + const eventsHeader = parseDictionary(response.headers.get('Events')) + expect(eventsHeader.get('protocol')?.[0]).to.equal('prep') + expect(eventsHeader.get('status')?.[0]).to.equal(200) + expect(eventsHeader.get('expires')?.[0]).to.be.a('string') + expect(response.headers.get('Content-Type')).to.match(/^multipart\/mixed/) + }) + + it('should send a representation as the first part, matching the content size on disk', + async function () { + prepResponse = prepFetch(response) + const representation = await prepResponse.getRepresentation() + expect(representation.headers.get('Content-Type')).to.match(/text\/n3/) + const blob = await representation.blob() + expect(function (done) { + const size = fs.statSync(path.join(__dirname, + '../resources/sampleContainer/example-prep.ttl')).size + if (blob.size !== size) { + return done(new Error('files are not of the same size')) + } + }) + }) + + describe('should send notifications in the second part', async function () { + let notifications + let notificationsIterator + + it('when modified with PATCH', async function () { + notifications = await prepResponse.getNotifications() + notificationsIterator = notifications.notifications() + await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', { + method: 'PATCH', + headers: { + 'content-type': 'text/n3' + }, + body: `@prefix solid: . +<> a solid:InsertDeletePatch; +solid:inserts { . }.` + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification).to.haveOwnProperty('state') + expect(notification.type).to.equal('Update') + expect(notification.object).to.match(/sampleContainer\/example-prep\.ttl$/) + }) + + it('when removed with DELETE, it should also close the connection', + async function () { + await fetch('http://localhost:8443/sampleContainer/example-prep.ttl', { + method: 'DELETE' + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification).to.haveOwnProperty('state') + expect(notification.type).to.equal('Delete') + expect(notification.object).to.match(/sampleContainer\/example-prep\.ttl$/) + const { done } = await notificationsIterator.next() + expect(done).to.equal(true) + }) + }) + }) +}) From b0e7eb017dc0077928fff5ea422348e9f5d9caeb Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Thu, 10 Oct 2024 22:43:12 +0530 Subject: [PATCH 11/24] feat: Add Flag to disable PREP PREP can be disabled when the server is started with the `--no-prep` flag. --- README.md | 11 ++++++----- bin/lib/options.js | 6 ++++++ lib/create-app.js | 10 +++++++--- lib/handlers/delete.js | 3 ++- lib/handlers/get.js | 5 +++-- lib/handlers/patch.js | 3 ++- lib/handlers/post.js | 5 +++-- lib/handlers/put.js | 3 ++- lib/ldp-middleware.js | 17 ++++++++++++----- test/integration/prep-test.js | 3 ++- 10 files changed, 45 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 24f92251e..514c44ebf 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ Your users will have a dedicated folder under `./data` at `./data/. To use Gmail you may need to configure ["Allow Less Secure Apps"](https://www.google.com/settings/security/lesssecureapps) in your Gmail account unless you are using 2FA in which case you would have to create an [Application Specific](https://security.google.com/settings/security/apppasswords) password. You also may need to unlock your account with ["Allow access to your Google account"](https://accounts.google.com/DisplayUnlockCaptcha) to use SMTP. also add to `config.json` -``` +``` "useEmail": true, "emailHost": "smtp.gmail.com", "emailPort": "465", @@ -206,6 +206,7 @@ $ solid start --help --multiuser Enable multi-user mode --idp [value] Obsolete; use --multiuser --no-live Disable live support through WebSockets + --no-prep Disable Per Resource Events --proxy [value] Obsolete; use --corsProxy --cors-proxy [value] Serve the CORS proxy on this path --suppress-data-browser Suppress provision of a data browser @@ -271,7 +272,7 @@ docker run -p 8443:8443 --name solid node-solid-server This will enable you to login to solid on https://localhost:8443 and then create a new account -but not yet use that account. After a new account is made you will need to create an entry for +but not yet use that account. After a new account is made you will need to create an entry for it in your local (/etc/)hosts file in line with the account and subdomain, i.e. -- ```pre @@ -280,16 +281,16 @@ it in your local (/etc/)hosts file in line with the account and subdomain, i.e. You can modify the config within the docker container as follows: - - Copy the `config.json` to the current directory with: + - Copy the `config.json` to the current directory with: ```bash docker cp solid:/usr/src/app/config.json . ``` - Edit the `config.json` file - - Copy the file back with + - Copy the file back with ```bash docker cp config.json solid:/usr/src/app/ ``` - - Restart the server with + - Restart the server with ```bash docker restart solid ``` diff --git a/bin/lib/options.js b/bin/lib/options.js index 630dd8199..bcb7d468a 100644 --- a/bin/lib/options.js +++ b/bin/lib/options.js @@ -143,6 +143,12 @@ module.exports = [ flag: true, default: false }, + { + name: 'no-prep', + help: 'Disable Per Resource Events', + flag: true, + default: false + }, // { // full: 'default-app', // help: 'URI to use as a default app for resources (default: https://linkeddata.github.io/warp/#/list/)' diff --git a/lib/create-app.js b/lib/create-app.js index d98d884e0..805695f3e 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -67,7 +67,10 @@ function createApp (argv = {}) { const app = express() // Add PREP support - app.use(acceptEvents, events, eventID, prep) + if (argv.prep) { + app.use(eventID) + app.use(acceptEvents, events, prep) + } initAppLocals(app, argv, ldp) initHeaders(app) @@ -123,7 +126,7 @@ function createApp (argv = {}) { } // Attach the LDP middleware - app.use('/', LdpMiddleware(corsSettings)) + app.use('/', LdpMiddleware(corsSettings, argv.prep)) // https://stackoverflow.com/questions/51741383/nodejs-express-return-405-for-un-supported-method app.use(function (req, res, next) { @@ -176,6 +179,7 @@ function initAppLocals (app, argv, ldp) { app.locals.enforceToc = argv.enforceToc app.locals.tocUri = argv.tocUri app.locals.disablePasswordChecks = argv.disablePasswordChecks + app.locals.prep = argv.prep if (argv.email && argv.email.host) { app.locals.emailService = new EmailService(argv.templates.email, argv.email) @@ -295,7 +299,7 @@ function initWebId (argv, app, ldp) { initAuthentication(app, argv) if (argv.multiuser) { - app.use(vhost('*', LdpMiddleware(corsSettings))) + app.use(vhost('*', LdpMiddleware(corsSettings, argv.prep))) } } diff --git a/lib/handlers/delete.js b/lib/handlers/delete.js index c267cd266..438bfe65c 100644 --- a/lib/handlers/delete.js +++ b/lib/handlers/delete.js @@ -6,11 +6,12 @@ async function handler (req, res, next) { debug('DELETE -- Request on' + req.originalUrl) const ldp = req.app.locals.ldp + const prep = req.app.locals.prep try { await ldp.delete(req) debug('DELETE -- Ok.') // Add event-id for notifications - res.setHeader('Event-ID', res.setEventID()) + prep && res.setHeader('Event-ID', res.setEventID()) res.sendStatus(200) next() } catch (err) { diff --git a/lib/handlers/get.js b/lib/handlers/get.js index af29a741d..3e11b3e53 100644 --- a/lib/handlers/get.js +++ b/lib/handlers/get.js @@ -23,6 +23,7 @@ const prepConfig = 'accept=("message/rfc822" "application/ld+json" "text/turtle" async function handler (req, res, next) { const ldp = req.app.locals.ldp + const prep = req.app.locals.prep const includeBody = req.method === 'GET' const negotiator = new Negotiator(req) const baseUri = ldp.resourceMapper.resolveUrl(req.hostname, req.path) @@ -138,7 +139,7 @@ async function handler (req, res, next) { res.statusCode = 206 } - if (isRdf(contentType) && !res.sendEvents({ + if (prep & isRdf(contentType) && !res.sendEvents({ config: { prep: prepConfig }, body: stream, isBodyStream: true, @@ -160,7 +161,7 @@ async function handler (req, res, next) { const headers = { 'Content-Type': possibleRDFType } - if (isRdf(contentType) && !res.sendEvents({ + if (prep && isRdf(contentType) && !res.sendEvents({ config: { prep: prepConfig }, body: data, headers diff --git a/lib/handlers/patch.js b/lib/handlers/patch.js index c98e1470e..d9469e750 100644 --- a/lib/handlers/patch.js +++ b/lib/handlers/patch.js @@ -39,6 +39,7 @@ function contentForNew (contentType) { // Handles a PATCH request async function patchHandler (req, res, next) { debug(`PATCH -- ${req.originalUrl}`) + const prep = req.app.locals.prep try { // Obtain details of the target resource const ldp = req.app.locals.ldp @@ -92,7 +93,7 @@ async function patchHandler (req, res, next) { }) // Add event-id for notifications - res.setHeader('Event-ID', res.setEventID()) + prep && res.setHeader('Event-ID', res.setEventID()) // Send the status and result to the client res.status(resourceExists ? 200 : 201) res.send(result) diff --git a/lib/handlers/post.js b/lib/handlers/post.js index cd136bd9b..dd16700d3 100644 --- a/lib/handlers/post.js +++ b/lib/handlers/post.js @@ -11,6 +11,7 @@ const getContentType = require('../utils').getContentType async function handler (req, res, next) { const ldp = req.app.locals.ldp + const prep = req.app.locals.prep const contentType = getContentType(req.headers) debug('content-type is ', contentType) // Handle SPARQL(-update?) query @@ -73,7 +74,7 @@ async function handler (req, res, next) { busboy.on('finish', function () { debug('Done storing files') // Add event-id for notifications - res.setHeader('Event-ID', res.setEventID()) + prep && res.setHeader('Event-ID', res.setEventID()) res.sendStatus(200) next() }) @@ -94,7 +95,7 @@ async function handler (req, res, next) { header.addLinks(res, links) res.set('Location', resourcePath) // Add event-id for notifications - res.setHeader('Event-ID', res.setEventID()) + prep && res.setHeader('Event-ID', res.setEventID()) res.sendStatus(201) next() }, diff --git a/lib/handlers/put.js b/lib/handlers/put.js index d6b984b66..5c2e1f677 100644 --- a/lib/handlers/put.js +++ b/lib/handlers/put.js @@ -59,6 +59,7 @@ async function checkPermission (request, resourceExists) { // TODO could be renamed as putResource (it now covers container and non-container) async function putStream (req, res, next, stream = req) { const ldp = req.app.locals.ldp + const prep = req.app.locals.prep // try { // Obtain details of the target resource let resourceExists = true @@ -78,7 +79,7 @@ async function putStream (req, res, next, stream = req) { if (!req.originalUrl.endsWith('.acl')) await checkPermission(req, resourceExists) await ldp.put(req, stream, getContentType(req.headers)) // Add event-id for notifications - res.setHeader('Event-ID', res.setEventID()) + prep && res.setHeader('Event-ID', res.setEventID()) res.sendStatus(resourceExists ? 204 : 201) return next() } catch (err) { diff --git a/lib/ldp-middleware.js b/lib/ldp-middleware.js index 53eb90c27..996286d4c 100644 --- a/lib/ldp-middleware.js +++ b/lib/ldp-middleware.js @@ -12,7 +12,7 @@ const index = require('./handlers/index') const copy = require('./handlers/copy') const notify = require('./handlers/notify') -function LdpMiddleware (corsSettings) { +function LdpMiddleware (corsSettings, prep) { const router = express.Router('/') // Add Link headers @@ -24,10 +24,17 @@ function LdpMiddleware (corsSettings) { router.copy('/*', allow('Write'), copy) router.get('/*', index, allow('Read'), header.addPermissions, get) - router.post('/*', allow('Append'), post, notify) - router.patch('/*', allow('Append'), patch, notify) - router.put('/*', allow('Append'), put, notify) - router.delete('/*', allow('Write'), del, notify) + router.post('/*', allow('Append'), post) + router.patch('/*', allow('Append'), patch) + router.put('/*', allow('Append'), put) + router.delete('/*', allow('Write'), del) + + if (prep) { + router.post('/*', notify) + router.patch('/*', notify) + router.put('/*', notify) + router.delete('/*', notify) + } return router } diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js index dceb5a2bd..6db1b03ab 100644 --- a/test/integration/prep-test.js +++ b/test/integration/prep-test.js @@ -17,7 +17,8 @@ describe('Per Resource Events Protocol', function () { dataBrowserPath: 'default', root: path.join(__dirname, '../resources'), auth: 'oidc', - webid: false + webid: false, + prep: true }) server.listen(8443, done) }) From 6a287e482db587c252a15a0144b0f28d9d2d75ee Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Sun, 13 Oct 2024 23:50:25 +0530 Subject: [PATCH 12/24] refactor: Remove Commented Code in `get.js` --- lib/handlers/get.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/handlers/get.js b/lib/handlers/get.js index 3e11b3e53..3bc7fbfbc 100644 --- a/lib/handlers/get.js +++ b/lib/handlers/get.js @@ -114,17 +114,6 @@ async function handler (req, res, next) { } // If request accepts the content-type we found - // if (stream && negotiator.mediaType([contentType])) { - // res.setHeader('Content-Type', contentType) - // if (contentRange) { - // const headers = { 'Content-Range': contentRange, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize } - // res.writeHead(206, headers) - // return stream.pipe(res) - // } else { - // return stream.pipe(res) - // } - // } - if (stream && negotiator.mediaType([contentType])) { let headers = { 'Content-Type': contentType From a6067e345be9fee138fd160aa1851d98e296d386 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Sat, 19 Oct 2024 23:08:06 +0530 Subject: [PATCH 13/24] chore: Bump PREP Dependencies PREP dependencies have been updated to the latest versions that use the newest HTTP Structured Fields specification RFC9651. --- package-lock.json | 70 +++++++++++++++++++++++------------------------ package.json | 6 ++-- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 455826a3b..7b846e71e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,10 +24,10 @@ "cors": "^2.8.5", "debug": "^4.3.4", "express": "^4.18.3", - "express-accept-events": "^0.2.2", + "express-accept-events": "^0.3.0", "express-handlebars": "^5.3.5", - "express-negotiate-events": "^0.2.1", - "express-prep": "^0.5.4", + "express-negotiate-events": "^0.3.0", + "express-prep": "^0.6.1", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", @@ -10761,15 +10761,15 @@ } }, "node_modules/express-accept-events": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/express-accept-events/-/express-accept-events-0.2.2.tgz", - "integrity": "sha512-P9hVug6sMVZAFqzYK0vc/J18/PRXyaKCEBbvVYemEJEhb68MNNffiI/KZq3iDaWlHWYR+35BxM5n1FqFqHyKHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/express-accept-events/-/express-accept-events-0.3.0.tgz", + "integrity": "sha512-6ZWFlaZYo+Vsbm1sFoJgtaYy6xkHjcfdLKgs7I8ZLSJhBqtbzH09aIj4UkmL9zuV5kUTsyfegL9Dp5XKyb+VSg==", "license": "MPL-2.0", "dependencies": { "debug": "^4.3.5", "no-try": "^4.0.0", - "structured-field-utils": "1.1.0-nested-sf.0", - "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + "structured-field-utils": "1.2.0-nested-sf.0", + "structured-headers": "npm:@cxres/structured-headers@2.0.0-nesting.0" } }, "node_modules/express-accept-events/node_modules/debug": { @@ -10791,9 +10791,9 @@ }, "node_modules/express-accept-events/node_modules/structured-headers": { "name": "@cxres/structured-headers", - "version": "2.0.0-alpha.1-nesting.0", - "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", - "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "version": "2.0.0-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-nesting.0.tgz", + "integrity": "sha512-zW8AF/CXaxGe0B1KCj/QEY88Hqxh6xZ9i98UHqCFZZa/QgYGYJD9Z40/h+UZsrYi/ZW/VQVQhObB5Zegd/MDZQ==", "license": "MIT", "engines": { "node": ">=18", @@ -10814,13 +10814,13 @@ } }, "node_modules/express-negotiate-events": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/express-negotiate-events/-/express-negotiate-events-0.2.1.tgz", - "integrity": "sha512-ehSjuJ8MCPXKb+UsoNEWzosr9qOQPgzJng1xVi5UDSdsCCuN8YgseULxxMxwqnTMfVpceQ0F1Mpgdz+mQyRrJg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/express-negotiate-events/-/express-negotiate-events-0.3.0.tgz", + "integrity": "sha512-IPAukv2hDgdj95C30qhMJlvNJbnBBtN5Hwvrl/z05qB85bAh+7yMR5jXiMZMmUwaD0K6L3+U6kTYWT7AlWc11Q==", "license": "MPL-2.0", "dependencies": { "debug": "^4.3.5", - "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + "structured-headers": "npm:@cxres/structured-headers@2.0.0-nesting.0" } }, "node_modules/express-negotiate-events/node_modules/debug": { @@ -10842,9 +10842,9 @@ }, "node_modules/express-negotiate-events/node_modules/structured-headers": { "name": "@cxres/structured-headers", - "version": "2.0.0-alpha.1-nesting.0", - "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", - "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "version": "2.0.0-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-nesting.0.tgz", + "integrity": "sha512-zW8AF/CXaxGe0B1KCj/QEY88Hqxh6xZ9i98UHqCFZZa/QgYGYJD9Z40/h+UZsrYi/ZW/VQVQhObB5Zegd/MDZQ==", "license": "MIT", "engines": { "node": ">=18", @@ -10852,9 +10852,9 @@ } }, "node_modules/express-prep": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.5.4.tgz", - "integrity": "sha512-2PyQfwDt0+T6uXLceSpHWHKWrf0fqmK9quWTEkcCezUhG7+v1E0EozivNCPJ8A4LGUAdyAz1hWiN3+hmX/tQJg==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.6.1.tgz", + "integrity": "sha512-+SEXlmsuiBFpS1SDmkz4bioaMhVi6yBC1zs+BpWNnOoxwH4v0sXwa4oxdfyqun6aGuiik+6EKg+967o41CIj7A==", "license": "MPL-2.0", "dependencies": { "crypto-random-string": "^5.0.0", @@ -10862,12 +10862,12 @@ "dedent": "^1.5.3", "lodash": "^4.17.21", "no-try": "^4.0.0", - "structured-field-utils": "1.1.0-nested-sf.0", - "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + "structured-field-utils": "1.2.0-nested-sf.0", + "structured-headers": "npm:@cxres/structured-headers@2.0.0-nesting.0" }, "peerDependencies": { - "express-accept-events": "^0.2.2", - "express-negotiate-events": "^0.2.1" + "express-accept-events": "^0.3.0", + "express-negotiate-events": "^0.3.0" }, "peerDependenciesMeta": { "express-negotiate-events": { @@ -10909,9 +10909,9 @@ }, "node_modules/express-prep/node_modules/structured-headers": { "name": "@cxres/structured-headers", - "version": "2.0.0-alpha.1-nesting.0", - "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", - "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "version": "2.0.0-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-nesting.0.tgz", + "integrity": "sha512-zW8AF/CXaxGe0B1KCj/QEY88Hqxh6xZ9i98UHqCFZZa/QgYGYJD9Z40/h+UZsrYi/ZW/VQVQhObB5Zegd/MDZQ==", "license": "MIT", "engines": { "node": ">=18", @@ -22943,19 +22943,19 @@ "peer": true }, "node_modules/structured-field-utils": { - "version": "1.1.0-nested-sf.0", - "resolved": "https://registry.npmjs.org/structured-field-utils/-/structured-field-utils-1.1.0-nested-sf.0.tgz", - "integrity": "sha512-fISHrNmYK9c1PZFxQdMPG19d3uVv1lXwiW9MkKEjjU8SWkpfPWMXmYt7Pw3lt7TshOjPauO8EgTgcu85qMga9g==", + "version": "1.2.0-nested-sf.0", + "resolved": "https://registry.npmjs.org/structured-field-utils/-/structured-field-utils-1.2.0-nested-sf.0.tgz", + "integrity": "sha512-fK/2PzGf152UCgjpWBesQuUanOQ+f08r4LMe7GugIzs7KfnNyDspDZTaWixf36TO+Df5eq4z1Z0EB3t4Wc7yNQ==", "license": "MPL-2.0", "dependencies": { - "structured-headers": "npm:@cxres/structured-headers@2.0.0-alpha.1-nesting.0" + "structured-headers": "npm:@cxres/structured-headers@2.0.0-nesting.0" } }, "node_modules/structured-field-utils/node_modules/structured-headers": { "name": "@cxres/structured-headers", - "version": "2.0.0-alpha.1-nesting.0", - "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-alpha.1-nesting.0.tgz", - "integrity": "sha512-MYHRF2oS3Zvumh3swXs//GDGNe6L+sYnwplitC8Ns8CFQro+WKCidDrgk+suLq2Wm6e0ohIvEWqB8VM1mqbR8g==", + "version": "2.0.0-nesting.0", + "resolved": "https://registry.npmjs.org/@cxres/structured-headers/-/structured-headers-2.0.0-nesting.0.tgz", + "integrity": "sha512-zW8AF/CXaxGe0B1KCj/QEY88Hqxh6xZ9i98UHqCFZZa/QgYGYJD9Z40/h+UZsrYi/ZW/VQVQhObB5Zegd/MDZQ==", "license": "MIT", "engines": { "node": ">=18", diff --git a/package.json b/package.json index 67448b579..8919f3c71 100644 --- a/package.json +++ b/package.json @@ -74,10 +74,10 @@ "cors": "^2.8.5", "debug": "^4.3.4", "express": "^4.18.3", - "express-accept-events": "^0.2.2", + "express-accept-events": "^0.3.0", "express-handlebars": "^5.3.5", - "express-negotiate-events": "^0.2.1", - "express-prep": "^0.5.4", + "express-negotiate-events": "^0.3.0", + "express-prep": "^0.6.1", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", From 568b58a117c19ddb1821f7d4ffefd8fd6ce54675 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 17:33:19 +0530 Subject: [PATCH 14/24] fix: Send Parent Notifications on Root Notifications on the parent container are now sent when the parent is root. --- lib/handlers/notify.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index be16a48f4..97364be3b 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -14,8 +14,7 @@ const ALLOWED_RDF_MIME_TYPES = [ function getParent (path) { if (path === '' || path === '/') return const parent = libPath.dirname(path) - if (parent === '/') return - return `${parent}/` + return parent === '/' ? '/' : `${parent}/` } function getActivity (method) { From 3fa2be9374e07c18af232ec451a62ba9a6dd03ee Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 17:50:01 +0530 Subject: [PATCH 15/24] fix: Activity is `as:Add` on Container POST Notification emitted when a resource is created as a result of POST on a container has activity type set to `as:Add`. --- lib/handlers/notify.js | 15 +++++++++------ test/integration/prep-test.js | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index 97364be3b..4296b37ad 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -17,10 +17,13 @@ function getParent (path) { return parent === '/' ? '/' : `${parent}/` } -function getActivity (method) { +function getActivity (method, path) { if (method === 'DELETE') { return 'Delete' } + if (method === 'POST' && path.endsWith('/')) { + return 'Add' + } return 'Update' } @@ -37,10 +40,10 @@ function getParentActivity (method, status) { function handler (req, res, next) { const { trigger, defaultNotification } = res.events.prep - const { method } = req + const { method, path } = req const { statusCode } = res const eventID = res.getHeader('event-id') - const fullUrl = new URL(req.path, `${req.protocol}://${req.hostname}/`) + const fullUrl = new URL(path, `${req.protocol}://${req.hostname}/`) // Date is a hack since node does not seem to provide access to send date. // Date needs to be shared with parent notification @@ -56,10 +59,10 @@ function handler (req, res, next) { negotiatedFields ) { const mediaType = negotiatedFields['content-type'] - + const activity = getActivity(method, path) if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ - activity: getActivity(method), + activity, eventID, object: String(fullUrl), date: eventDate, @@ -81,7 +84,7 @@ function handler (req, res, next) { // Write a notification to parent container // POST in Solid creates a child resource - const parent = getParent(req.path) + const parent = getParent(path) if (parent && method !== 'POST') { try { const parentID = res.setEventID(parent) diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js index 6db1b03ab..179a21255 100644 --- a/test/integration/prep-test.js +++ b/test/integration/prep-test.js @@ -142,7 +142,7 @@ solid:inserts { . }.` expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) const notification = await value.json() expect(notification).to.haveOwnProperty('published') - expect(notification.type).to.equal('Update') + expect(notification.type).to.equal('Add') expect(notification.object).to.match(/sampleContainer\/$/) controller.abort() }) From 6f4b22bf320b074913155f9e4808208e6fe7310a Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 18:00:28 +0530 Subject: [PATCH 16/24] fix: Set `target` on Container POST Notification emitted when a resource is created as a result of POST on a container sets the `target` property to the `Location` header, consistent with the requirements of `as:Add`. --- lib/handlers/notify.js | 4 ++++ test/integration/prep-test.js | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index 4296b37ad..84ae43852 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -60,11 +60,15 @@ function handler (req, res, next) { ) { const mediaType = negotiatedFields['content-type'] const activity = getActivity(method, path) + const target = activity === 'Add' + ? res.getHeader('location') + : undefined if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ activity, eventID, object: String(fullUrl), + target, date: eventDate, // We use eTag as a proxy for state for now state: res.getHeader('ETag'), diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js index 179a21255..43fe7d207 100644 --- a/test/integration/prep-test.js +++ b/test/integration/prep-test.js @@ -144,6 +144,7 @@ solid:inserts { . }.` expect(notification).to.haveOwnProperty('published') expect(notification.type).to.equal('Add') expect(notification.object).to.match(/sampleContainer\/$/) + expect(notification.target).to.match(/sampleContainer\/.*example-prep.ttl$/) controller.abort() }) }) From 2a277ff757633a383b5331d23b288f2e46e6dd6c Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 18:12:25 +0530 Subject: [PATCH 17/24] fix: Set `origin` when Contained Resource is Removed Notification emitted on the container when a resource in it is removed, sets the `origin` property to the location of the removed resource, consistent with the requirements of `as:Remove`. --- lib/handlers/notify.js | 6 ++++-- test/integration/prep-test.js | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index 84ae43852..6adedf97e 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -99,13 +99,15 @@ function handler (req, res, next) { negotiatedFields ) { const mediaType = negotiatedFields['content-type'] + const activity = getParentActivity(method, statusCode) + const target = activity !== 'Update' ? String(fullUrl) : undefined if (ALLOWED_RDF_MIME_TYPES.includes(mediaType?.[0])) { return `${headerTemplate(negotiatedFields)}\r\n${solidRDFTemplate({ - activity: getParentActivity(method, statusCode), + activity, eventID: parentID, date: eventDate, object: String(parentUrl), - target: statusCode === 201 ? String(fullUrl) : undefined, + target, eTag: undefined, mediaType })}` diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js index 43fe7d207..0e4348ea5 100644 --- a/test/integration/prep-test.js +++ b/test/integration/prep-test.js @@ -126,6 +126,7 @@ solid:inserts { . }.` expect(notification).to.haveOwnProperty('published') expect(notification.type).to.equal('Remove') expect(notification.object).to.match(/sampleContainer\/$/) + expect(notification.origin).to.match(/sampleContainer\/.*example-prep.ttl$/) }) it('when resource is created by POST', From d08a88f07e91f837b2f6474e17f39f2b4c309fae Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 18:43:20 +0530 Subject: [PATCH 18/24] test: Notification when Creating a Container with POST Added a test to check that a PREP notification is correctly emitted when a container is created with POST. --- test/integration/prep-test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js index 0e4348ea5..bb85b5416 100644 --- a/test/integration/prep-test.js +++ b/test/integration/prep-test.js @@ -24,6 +24,7 @@ describe('Per Resource Events Protocol', function () { }) after(() => { + fs.rmSync(path.join(samplePath, 'example-post'), { recursive: true }) server.close() }) @@ -129,6 +130,25 @@ solid:inserts { . }.` expect(notification.origin).to.match(/sampleContainer\/.*example-prep.ttl$/) }) + it('when a container is created by POST', + async function () { + await fetch('http://localhost:8443/sampleContainer/', { + method: 'POST', + headers: { + slug: 'example-post', + link: '; rel="type"', + 'content-type': 'text/turtle' + } + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Add') + expect(notification.object).to.match(/sampleContainer\/$/) + expect(notification.target).to.match(/sampleContainer\/.*example-post\/$/) + }) + it('when resource is created by POST', async function () { await fetch('http://localhost:8443/sampleContainer/', { From 33997ae5ff2487b7f03273d93eab63fa553935f8 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 19:17:45 +0530 Subject: [PATCH 19/24] chore: Bump Express PREP This fixes "undefined" notifications being emitted when the requested notification format is not Solid PREP RDF. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b846e71e..0d1b732ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "express-accept-events": "^0.3.0", "express-handlebars": "^5.3.5", "express-negotiate-events": "^0.3.0", - "express-prep": "^0.6.1", + "express-prep": "^0.6.2", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", @@ -10852,9 +10852,9 @@ } }, "node_modules/express-prep": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.6.1.tgz", - "integrity": "sha512-+SEXlmsuiBFpS1SDmkz4bioaMhVi6yBC1zs+BpWNnOoxwH4v0sXwa4oxdfyqun6aGuiik+6EKg+967o41CIj7A==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.6.2.tgz", + "integrity": "sha512-qkEO386C6kK2BFESRozSmnsoH+zOivR/W1fSZ0YoNjJvzy+MiHnEEci/DF3TVsePfpTjt8T04qxkdsWMIPVM6w==", "license": "MPL-2.0", "dependencies": { "crypto-random-string": "^5.0.0", diff --git a/package.json b/package.json index 8919f3c71..8978c2717 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "express-accept-events": "^0.3.0", "express-handlebars": "^5.3.5", "express-negotiate-events": "^0.3.0", - "express-prep": "^0.6.1", + "express-prep": "^0.6.2", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", From 1f6c16f40968fec2f71b4210632213425d989b02 Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 19:24:19 +0530 Subject: [PATCH 20/24] fix: Do Not Overwrite `req.url` on Container PUT `req.url` is not overwritten anymore when PUT is used to create a container. This allows subsequent middlewares to have access to the original request URL. --- lib/ldp.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/ldp.js b/lib/ldp.js index e3c261f6a..5b5a729ce 100644 --- a/lib/ldp.js +++ b/lib/ldp.js @@ -243,15 +243,11 @@ class LDP { throw error(413, 'User has exceeded their storage quota') } // Set url using folder/.meta. This is Hack to find folder path - if (container) { - if (typeof url !== 'string') { - url.url = url.url + suffixMeta - } else { - url = url + suffixMeta - } - contentType = 'text/turtle' - } - const { path } = await this.resourceMapper.mapUrlToFile({ url, contentType, createIfNotExists: true }) + const { path } = await this.resourceMapper.mapUrlToFile({ + url: container ? (url.url || url) + suffixMeta : url, + contentType, + createIfNotExists: true + }) // debug.handlers(container + ' item ' + (url.url || url) + ' ' + contentType + ' ' + path) // check if file exists, and in that case that it has the same extension if (!container) { await this.checkFileExtension(url, path) } From c933a7430ec85788b4cbeee6ce2685fb20c5cb2d Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 19:41:57 +0530 Subject: [PATCH 21/24] test: Notification when Creating/Deleting Container in Container Added tests to check that PREP notifications are correctly emitted on a container, when a container inside it is created or deleted. --- test/integration/prep-test.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/integration/prep-test.js b/test/integration/prep-test.js index bb85b5416..7dea73ed6 100644 --- a/test/integration/prep-test.js +++ b/test/integration/prep-test.js @@ -130,6 +130,35 @@ solid:inserts { . }.` expect(notification.origin).to.match(/sampleContainer\/.*example-prep.ttl$/) }) + it('when a contained container is created', async function () { + await fetch('http://localhost:8443/sampleContainer/example-prep/', { + method: 'PUT', + headers: { + 'Content-Type': 'text/turtle' + } + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Add') + expect(notification.target).to.match(/sampleContainer\/example-prep\/$/) + expect(notification.object).to.match(/sampleContainer\/$/) + }) + + it('when a contained container is deleted', async function () { + await fetch('http://localhost:8443/sampleContainer/example-prep/', { + method: 'DELETE' + }) + const { value } = await notificationsIterator.next() + expect(value.headers.get('content-type')).to.match(/application\/ld\+json/) + const notification = await value.json() + expect(notification).to.haveOwnProperty('published') + expect(notification.type).to.equal('Remove') + expect(notification.origin).to.match(/sampleContainer\/example-prep\/$/) + expect(notification.object).to.match(/sampleContainer\/$/) + }) + it('when a container is created by POST', async function () { await fetch('http://localhost:8443/sampleContainer/', { From 2da77b01f71a5b4c2023f7376fad3c26e5afed8a Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Wed, 23 Oct 2024 20:40:44 +0530 Subject: [PATCH 22/24] refactor: Add Debugging for Failed Notifications Added debugging statements for failed notifications. Also clarified that notification errors should not be passed on to Express for error handling, since the mutation that triggered the notification was already successful. --- lib/debug.js | 1 + lib/handlers/notify.js | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/debug.js b/lib/debug.js index 306ee2bfb..7f16654ee 100644 --- a/lib/debug.js +++ b/lib/debug.js @@ -15,3 +15,4 @@ exports.accounts = debug('solid:accounts') exports.email = debug('solid:email') exports.ldp = debug('solid:ldp') exports.fs = debug('solid:fs') +exports.prep = debug('solid:prep') diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index 6adedf97e..fe758da12 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -4,6 +4,7 @@ const libPath = require('path/posix') const headerTemplate = require('express-prep/templates').header const solidRDFTemplate = require('../rdf-notification-template') +const debug = require('../debug').prep const ALLOWED_RDF_MIME_TYPES = [ 'application/ld+json', @@ -82,7 +83,11 @@ function handler (req, res, next) { } }) } catch (error) { - // Failed notification message + debug(`Failed to trigger notification on route ${fullUrl}`) + // No special handling is necessary since the resource mutation was + // already successful. The purpose of this block is to prevent Express + // from triggering error handling middleware when notifications fail. + // An error notification might be sent in the future. } } @@ -90,9 +95,9 @@ function handler (req, res, next) { // POST in Solid creates a child resource const parent = getParent(path) if (parent && method !== 'POST') { + const parentID = res.setEventID(parent) + const parentUrl = new URL(parent, fullUrl) try { - const parentID = res.setEventID(parent) - const parentUrl = new URL(parent, fullUrl) trigger({ path: parent, generateNotification ( @@ -115,7 +120,11 @@ function handler (req, res, next) { } }) } catch (error) { - // Failed notification message + debug(`Failed to trigger notification on parent route ${parentUrl}`) + // No special handling is necessary since the resource mutation was + // already successful. The purpose of this block is to prevent Express + // from triggering error handling middleware when notifications fail. + // An error notification might be sent in the future. } } From 44dc0bce781699c2b38991fea7a0a44994cc2f6f Mon Sep 17 00:00:00 2001 From: Rahul Gupta Date: Thu, 24 Oct 2024 04:56:47 +0530 Subject: [PATCH 23/24] fix: Correctly Set Location on non-RDF Notifications When the negotiated media-type for notifications is not RDF, the `location` property is correctly set to the `Location` header in the generated notifications. Express-PREP now handles this by default. --- lib/handlers/notify.js | 4 +--- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/handlers/notify.js b/lib/handlers/notify.js index fe758da12..42090cb3a 100644 --- a/lib/handlers/notify.js +++ b/lib/handlers/notify.js @@ -76,9 +76,7 @@ function handler (req, res, next) { mediaType })}` } else { - return defaultNotification({ - ...(res.method === 'POST') && { location: res.getHeader('Content-Location') } - }) + return defaultNotification() } } }) diff --git a/package-lock.json b/package-lock.json index 0d1b732ff..923222542 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "express-accept-events": "^0.3.0", "express-handlebars": "^5.3.5", "express-negotiate-events": "^0.3.0", - "express-prep": "^0.6.2", + "express-prep": "^0.6.3", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", @@ -10852,9 +10852,9 @@ } }, "node_modules/express-prep": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.6.2.tgz", - "integrity": "sha512-qkEO386C6kK2BFESRozSmnsoH+zOivR/W1fSZ0YoNjJvzy+MiHnEEci/DF3TVsePfpTjt8T04qxkdsWMIPVM6w==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/express-prep/-/express-prep-0.6.3.tgz", + "integrity": "sha512-RIdkQHCi9VqE/vC1AHTMV1aggd408WLlG0z9uPqv5Tj+AzZuHj0ycEgGTgFnvafYzJCbhs208+wV2MrbFlCajQ==", "license": "MPL-2.0", "dependencies": { "crypto-random-string": "^5.0.0", diff --git a/package.json b/package.json index 8978c2717..f3ea42dba 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "express-accept-events": "^0.3.0", "express-handlebars": "^5.3.5", "express-negotiate-events": "^0.3.0", - "express-prep": "^0.6.2", + "express-prep": "^0.6.3", "express-session": "^1.18.0", "extend": "^3.0.2", "from2": "^2.3.0", From 29f3e081b35e00c4d4157fd319a718672625149c Mon Sep 17 00:00:00 2001 From: bourgeoa Date: Mon, 4 Nov 2024 17:05:00 +0100 Subject: [PATCH 24/24] rewrite create container to keep req --- lib/ldp.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/ldp.js b/lib/ldp.js index 5b5a729ce..c1647956b 100644 --- a/lib/ldp.js +++ b/lib/ldp.js @@ -242,12 +242,15 @@ class LDP { if (isOverQuota) { throw error(413, 'User has exceeded their storage quota') } - // Set url using folder/.meta. This is Hack to find folder path - const { path } = await this.resourceMapper.mapUrlToFile({ - url: container ? (url.url || url) + suffixMeta : url, + // Set url using folder/.meta + let { path } = await this.resourceMapper.mapUrlToFile({ + url, contentType, - createIfNotExists: true + createIfNotExists: true, + searchIndex: false }) + + if (container) { path += suffixMeta } // debug.handlers(container + ' item ' + (url.url || url) + ' ' + contentType + ' ' + path) // check if file exists, and in that case that it has the same extension if (!container) { await this.checkFileExtension(url, path) } @@ -260,7 +263,6 @@ class LDP { // clearAclCache() } // Directory created, now write the file - if (container) return return withLock(path, () => new Promise((resolve, reject) => { // HACK: the middleware in webid-oidc.js uses body-parser, thus ending the stream of data // for JSON bodies. So, the stream needs to be reset