From 5d6d9df030b1c0d381b79638516fc0f6f2f60c27 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Mon, 4 May 2026 15:08:46 +0200 Subject: [PATCH 01/28] feat(screenshots): add Cypress screenshot automation infrastructure Adds a complete screenshot capture pipeline for the Nextcloud documentation: - cypress.config.ts: starts a clean stable33 container via @nextcloud/cypress/docker, installs docs-relevant apps (activity, calendar, contacts, talk, viewer, etc.), fully isolated from the dev environment - cypress/e2e/helpers.ts: docScreenshot() and docElementScreenshot() helpers - cypress/support/: e2e.ts and commands.ts support files - scripts/inventory.py: parses all RST image/figure directives across all three manuals, resolves paths, derives cypress_name and cypress_spec per entry, outputs screenshot-inventory.json (552 directives found, 0 missing) - scripts/sync.sh: reads inventory to copy Cypress output to correct doc image paths, runs pngquant optimisation, prints coverage report - scripts/optimize.sh: standalone pngquant batch compressor - Makefile: screenshot-install, screenshot-inventory, screenshots, screenshots-dry - .nvmrc: pin to Node 20 LTS - .gitignore: exclude node_modules, cypress/snapshots, generated inventory AI-Assisted-By: claude-sonnet-4-6 Signed-off-by: Anna Larch --- .gitignore | 7 + .nvmrc | 1 + Makefile | 19 + cypress.config.ts | 72 ++ cypress/e2e/helpers.ts | 37 + cypress/support/commands.ts | 6 + cypress/support/e2e.ts | 5 + package-lock.json | 2250 +++++++++++++++++++++++++++++++++++ package.json | 20 + scripts/inventory.py | 216 ++++ scripts/optimize.sh | 28 + scripts/sync.sh | 100 ++ tsconfig.cypress.json | 14 + 13 files changed, 2775 insertions(+) create mode 100644 .nvmrc create mode 100644 cypress.config.ts create mode 100644 cypress/e2e/helpers.ts create mode 100644 cypress/support/commands.ts create mode 100644 cypress/support/e2e.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100755 scripts/inventory.py create mode 100755 scripts/optimize.sh create mode 100755 scripts/sync.sh create mode 100644 tsconfig.cypress.json diff --git a/.gitignore b/.gitignore index f330e9f8bc6..ffca2f8208d 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,10 @@ Pipfile.lock # ack-grep .ackrc + +# Screenshot automation +node_modules/ +cypress/snapshots/ +cypress/videos/ +cypress/downloads/ +screenshot-inventory.json diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000000..209e3ef4b62 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/Makefile b/Makefile index e5c59eea2be..fbfdbaeff6b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,24 @@ all: html pdf +# Screenshot automation +# --------------------- +# Prerequisites: Node.js >=20, Docker (for the Nextcloud stable33 container) +# Optional: pngquant (for automatic PNG compression after sync) + +screenshot-inventory: + python3 scripts/inventory.py + +screenshots: screenshot-inventory + npm run screenshots + bash scripts/sync.sh + +screenshots-dry: screenshot-inventory + npm run screenshots + bash scripts/sync.sh --dry-run + +screenshot-install: + npm install + html: admin-manual-html user-manual-html developer-manual-html pdf: admin-manual-pdf user-manual-pdf diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000000..b6626c6e468 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,72 @@ +import { + configureNextcloud, + startNextcloud, + stopNextcloud, + waitOnNextcloud, +} from '@nextcloud/cypress/docker' +import { defineConfig } from 'cypress' + +// Apps to enable in the screenshot environment. +// These must be available on the stable33 server image. +const SCREENSHOT_APPS = [ + 'activity', + 'calendar', + 'contacts', + 'deck', + 'notes', + 'photos', + 'spreed', + 'tasks', + 'theming', + 'viewer', +] + +export default defineConfig({ + // 16:9, matches most documentation screenshot widths + viewportWidth: 1280, + viewportHeight: 720, + + requestTimeout: 20000, + defaultCommandTimeout: 10000, + + retries: { + runMode: 1, + openMode: 0, + }, + + video: false, + + screenshotsFolder: 'cypress/snapshots', + trashAssetsBeforeRuns: true, + + e2e: { + async setupNodeEvents(on, config) { + on('before:browser:launch', (browser, launchOptions) => { + if (browser.family === 'chromium' && browser.name !== 'electron') { + launchOptions.preferences.default['browser.enable_spellchecking'] = false + return launchOptions + } + if (browser.family === 'firefox') { + launchOptions.preferences['layout.spellcheckDefault'] = 0 + return launchOptions + } + if (browser.name === 'electron') { + launchOptions.preferences.spellcheck = false + return launchOptions + } + }) + + on('after:run', () => { + stopNextcloud() + }) + + // Start a clean stable33 container — separate from the dev environment + const ip = await startNextcloud('stable33', false) + config.baseUrl = `http://${ip}/index.php` + await waitOnNextcloud(ip) + await configureNextcloud(SCREENSHOT_APPS) + + return config + }, + }, +}) diff --git a/cypress/e2e/helpers.ts b/cypress/e2e/helpers.ts new file mode 100644 index 00000000000..99a35e0d12d --- /dev/null +++ b/cypress/e2e/helpers.ts @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +/** + * Take a named screenshot for documentation. + * + * Name should mirror the destination path relative to the manual root, + * e.g. 'user/files/sharing-dialog' → synced to user_manual/files/images/sharing-dialog.png + * + * The sync script (scripts/sync.sh) reads screenshot-inventory.json to map + * these names to their RST image directive targets. + */ +export function docScreenshot(name: string, options: Partial = {}): void { + // Let animations, loaders, and toasts settle before capturing + cy.wait(500) + cy.screenshot(name, { + capture: 'viewport', + overwrite: true, + ...options, + }) +} + +/** + * Take a screenshot of a specific element only. + * Useful for dialogs, sidebars, or settings panels. + */ +export function docElementScreenshot( + selector: string, + name: string, + options: Partial = {}, +): void { + cy.wait(500) + cy.get(selector).should('be.visible').screenshot(name, { + overwrite: true, + ...options, + }) +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 00000000000..fd1d5f11a06 --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Custom commands for documentation screenshots. +// @nextcloud/cypress commands (cy.login, cy.createRandomUser, etc.) are +// loaded via e2e.ts → '@nextcloud/cypress/support'. diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000000..df24c6748c4 --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import '@nextcloud/cypress/support' +import './commands' diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..5edab987f89 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2250 @@ +{ + "name": "nextcloud-documentation-screenshots", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nextcloud-documentation-screenshots", + "license": "AGPL-3.0-or-later", + "devDependencies": { + "@nextcloud/cypress": "^1.1.0", + "@types/node": "^22.0.0", + "cypress": "^14.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", + "integrity": "sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~4.0.4", + "http-signature": "~1.4.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.14.1", + "safe-buffer": "^5.1.2", + "tough-cookie": "^5.0.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@nextcloud/cypress": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@nextcloud/cypress/-/cypress-1.1.0.tgz", + "integrity": "sha512-+KtAe3+xitPc9UQOnxzLyrlTcYDqDBYiGhS4wd7UJW0m+Qs/+A6Legdl4afqfe9jX/+g3Nb/oLT1+NzxelQRCQ==", + "deprecated": "Please use v1.1.1 or above", + "dev": true, + "license": "AGPL-3.0-or-later", + "engines": { + "node": "^16.0.0", + "npm": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "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" + } + ], + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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" + } + ], + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cypress": { + "version": "14.5.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.4.tgz", + "integrity": "sha512-0Dhm4qc9VatOcI1GiFGVt8osgpPdqJLHzRwcAB5MSD/CAAts3oybvPUPawHyvJZUd8osADqZe/xzMsZ8sDTjXw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@cypress/request": "^3.0.9", + "@cypress/xvfb": "^1.2.4", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.7.1", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "ci-info": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-table3": "0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "hasha": "5.2.2", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.7.1", + "supports-color": "^8.1.1", + "tmp": "~0.2.3", + "tree-kill": "1.2.2", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-signature": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.18.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/is-fullwidth-code-point": { + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "> 0.8" + } + }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense" + }, + "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, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000000..e869f71fbd6 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "nextcloud-documentation-screenshots", + "private": true, + "description": "Screenshot automation for Nextcloud documentation", + "license": "AGPL-3.0-or-later", + "engines": { + "node": ">=20" + }, + "scripts": { + "screenshots": "cypress run --e2e", + "screenshots:open": "cypress open --e2e", + "inventory": "python3 scripts/inventory.py" + }, + "devDependencies": { + "@nextcloud/cypress": "^1.1.0", + "@types/node": "^22.0.0", + "cypress": "^14.0.0", + "typescript": "^5.0.0" + } +} diff --git a/scripts/inventory.py b/scripts/inventory.py new file mode 100755 index 00000000000..a2f0a720b98 --- /dev/null +++ b/scripts/inventory.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +""" +Parse all RST files across the three Nextcloud documentation manuals and +produce screenshot-inventory.json — a catalogue of every image directive with +its source RST file, resolved path, and whether a Cypress spec covers it. + +Usage: + python3 scripts/inventory.py [--output PATH] + +Output fields per entry: + rst_file — RST file that contains the directive (repo-relative) + directive — 'image' or 'figure' + image_path — resolved path to the image file (repo-relative) + alt — :alt: text if present + width — :width: value if present + exists — whether the image file exists on disk + automatable — True if a Cypress spec claims this screenshot name + cypress_name — the name passed to docScreenshot() (derived from path) + cypress_spec — which spec file would produce it (derived by convention) +""" + +import argparse +import json +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + +MANUALS = { + 'user': REPO_ROOT / 'user_manual', + 'admin': REPO_ROOT / 'admin_manual', + 'developer': REPO_ROOT / 'developer_manual', +} + +CYPRESS_SPECS_DIR = REPO_ROOT / 'cypress' / 'e2e' + +# Matches .. image:: path or .. figure:: path (with optional leading whitespace) +DIRECTIVE_RE = re.compile( + r'^(?P[ \t]*)\.\.[ \t]+(?Pimage|figure)::[ \t]+(?P\S+)', + re.MULTILINE, +) +# Matches directive options like :alt:, :width:, :scale:, :class:, :figclass: +OPTION_RE = re.compile( + r'^[ \t]+:(?Palt|width|scale|class|figclass):[ \t]*(?P.+)', +) + + +def resolve_image_path(rst_file: Path, raw_path: str) -> Path | None: + """Resolve a raw image path from an RST directive to a repo-relative path.""" + # Strip leading/trailing whitespace + raw_path = raw_path.strip() + + # Wildcard paths (e.g. ../images/navigation-collapsible.*) — keep as-is + if '*' in raw_path or '?' in raw_path: + base = (rst_file.parent / raw_path).resolve() + # Return the path with the wildcard still in it, repo-relative + try: + return base.relative_to(REPO_ROOT) + except ValueError: + return Path(raw_path) + + candidate = (rst_file.parent / raw_path).resolve() + try: + return candidate.relative_to(REPO_ROOT) + except ValueError: + return Path(raw_path) + + +def cypress_name_from_path(image_path: Path) -> str: + """ + Derive the docScreenshot() name from the image path. + + user_manual/files/images/sharing.png → user/files/sharing + admin_manual/images/install.png → admin/install + developer_manual/basics/images/x.png → developer/basics/x + """ + parts = image_path.parts + # Find which manual this belongs to + for prefix, short in (('user_manual', 'user'), ('admin_manual', 'admin'), ('developer_manual', 'developer')): + if parts[0] == prefix: + # Drop the manual prefix and any 'images' directory segments + rest = [p for p in parts[1:] if p != 'images'] + # Drop the extension + if rest: + rest[-1] = Path(rest[-1]).stem + return '/'.join([short] + rest) + return str(image_path.with_suffix('')) + + +MANUAL_SHORT = { + 'user_manual': 'user', + 'admin_manual': 'admin', + 'developer_manual': 'developer', +} + + +def cypress_spec_from_rst(rst_file: Path) -> str: + """ + Derive the expected spec file path from the RST file that references the image. + + user_manual/activity.rst → cypress/e2e/user/activity.cy.ts + user_manual/files/access_webgui.rst → cypress/e2e/user/files.cy.ts + admin_manual/configuration_server/x.rst → cypress/e2e/admin/configuration_server.cy.ts + """ + parts = rst_file.relative_to(REPO_ROOT).parts + manual_short = MANUAL_SHORT.get(parts[0], parts[0]) + if len(parts) == 2: + section = parts[1].removesuffix('.rst') + else: + section = parts[1] + return f'cypress/e2e/{manual_short}/{section}.cy.ts' + + +def collect_claimed_names() -> set[str]: + """Scan existing Cypress spec files for docScreenshot() calls and return the names.""" + claimed = set() + if not CYPRESS_SPECS_DIR.exists(): + return claimed + pattern = re.compile(r"docScreenshot\(['\"]([^'\"]+)['\"]") + for spec in CYPRESS_SPECS_DIR.rglob('*.cy.ts'): + for match in pattern.finditer(spec.read_text(encoding='utf-8')): + claimed.add(match.group(1)) + return claimed + + +def parse_rst(rst_file: Path) -> list[dict]: + """Extract all image/figure directives from an RST file.""" + text = rst_file.read_text(encoding='utf-8', errors='replace') + entries = [] + + for m in DIRECTIVE_RE.finditer(text): + directive = m.group('directive') + raw_path = m.group('path') + image_path = resolve_image_path(rst_file, raw_path) + + # Collect options from lines immediately following the directive + options: dict[str, str] = {} + rest = text[m.end():] + for line in rest.splitlines(): + if not line.strip(): + continue + opt = OPTION_RE.match(line) + if opt: + options[opt.group('key')] = opt.group('value').strip() + elif line.strip() and not line.startswith(' ') and not line.startswith('\t'): + break + + entries.append({ + 'rst_file': str(rst_file.relative_to(REPO_ROOT)), + 'directive': directive, + 'image_path': str(image_path) if image_path else raw_path, + 'alt': options.get('alt', ''), + 'width': options.get('width', ''), + 'raw_path': raw_path, + }) + + return entries + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', default='screenshot-inventory.json', + help='Output JSON file path (default: screenshot-inventory.json)') + args = parser.parse_args() + + claimed_names = collect_claimed_names() + inventory = [] + seen_paths: set[str] = set() + + for manual_key, manual_dir in MANUALS.items(): + if not manual_dir.exists(): + print(f'Warning: manual directory not found: {manual_dir}', file=sys.stderr) + continue + for rst_file in sorted(manual_dir.rglob('*.rst')): + for entry in parse_rst(rst_file): + image_path = Path(entry['image_path']) + is_wildcard = '*' in entry['image_path'] or '?' in entry['image_path'] + + cypress_name = cypress_name_from_path(image_path) + cypress_spec = cypress_spec_from_rst(rst_file) + + full_path = REPO_ROOT / image_path + exists = full_path.exists() if not is_wildcard else any(REPO_ROOT.glob(entry['image_path'])) + + entry.update({ + 'exists': exists, + 'is_wildcard': is_wildcard, + 'cypress_name': cypress_name, + 'cypress_spec': cypress_spec, + 'automatable': cypress_name in claimed_names, + }) + del entry['raw_path'] + inventory.append(entry) + seen_paths.add(entry['image_path']) + + output_path = REPO_ROOT / args.output + output_path.write_text(json.dumps(inventory, indent=2), encoding='utf-8') + + # Summary + total = len(inventory) + existing = sum(1 for e in inventory if e['exists']) + missing = total - existing + automated = sum(1 for e in inventory if e['automatable']) + wildcards = sum(1 for e in inventory if e['is_wildcard']) + + print(f'Screenshot inventory written to {args.output}') + print(f' {total} image directives found') + print(f' {existing} image files exist on disk') + print(f' {missing} image files missing from disk') + print(f' {wildcards} wildcard paths') + print(f' {automated} covered by Cypress specs ({automated/total*100:.1f}%)') + + +if __name__ == '__main__': + main() diff --git a/scripts/optimize.sh b/scripts/optimize.sh new file mode 100755 index 00000000000..35df6673623 --- /dev/null +++ b/scripts/optimize.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Run pngquant on all PNG files in a directory (recursively). +# Usage: bash scripts/optimize.sh + +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +if ! command -v pngquant &>/dev/null; then + echo "pngquant not found. Install with: sudo apt install pngquant" >&2 + exit 1 +fi + +DIR="$1" +count=0 + +while IFS= read -r -d '' f; do + before=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f") + pngquant --quality=70-85 --force --ext .png "$f" 2>/dev/null || continue + after=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f") + echo " $(basename "$f"): ${before} → ${after} bytes" + ((count++)) || true +done < <(find "$DIR" -name '*.png' -print0) + +echo "Optimized $count PNG files in $DIR" diff --git a/scripts/sync.sh b/scripts/sync.sh new file mode 100755 index 00000000000..fa41344760e --- /dev/null +++ b/scripts/sync.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Sync Cypress screenshot output to documentation image directories. +# +# Reads screenshot-inventory.json to map cypress/snapshots/.png to the +# correct documentation target path. Runs pngquant optimisation on every +# copied file. Prints a coverage report at the end. +# +# Usage: bash scripts/sync.sh [--dry-run] + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SNAPSHOTS_DIR="$REPO_ROOT/cypress/snapshots" +INVENTORY="$REPO_ROOT/screenshot-inventory.json" +DRY_RUN=false + +for arg in "$@"; do + [[ "$arg" == "--dry-run" ]] && DRY_RUN=true +done + +if [[ ! -f "$INVENTORY" ]]; then + echo "Error: $INVENTORY not found. Run 'make screenshot-inventory' first." >&2 + exit 1 +fi + +if ! command -v python3 &>/dev/null; then + echo "Error: python3 is required." >&2 + exit 1 +fi + +HAS_PNGQUANT=true +command -v pngquant &>/dev/null || HAS_PNGQUANT=false + +copied=0 +skipped=0 +orphaned=0 +missing_inventory=0 + +echo "Syncing screenshots to documentation…" + +# Build a lookup: cypress_name → image_path from inventory +declare -A name_to_path +while IFS= read -r line; do + cypress_name=$(echo "$line" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['cypress_name'])") + image_path=$(echo "$line" | python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(d['image_path'])") + name_to_path["$cypress_name"]="$image_path" +done < <(python3 -c " +import json, sys +inventory = json.loads(open('$INVENTORY').read()) +for entry in inventory: + print(json.dumps({'cypress_name': entry['cypress_name'], 'image_path': entry['image_path']})) +") + +# Process each screenshot Cypress produced +while IFS= read -r -d '' snapshot; do + # Derive the cypress_name from the file path relative to SNAPSHOTS_DIR + rel="${snapshot#$SNAPSHOTS_DIR/}" + name="${rel%.png}" + + if [[ -v "name_to_path[$name]" ]]; then + target="$REPO_ROOT/${name_to_path[$name]}" + target_dir="$(dirname "$target")" + + if [[ "$DRY_RUN" == "true" ]]; then + echo " [dry] $name → ${name_to_path[$name]}" + else + mkdir -p "$target_dir" + cp "$snapshot" "$target" + if [[ "$HAS_PNGQUANT" == "true" ]]; then + pngquant --quality=70-85 --force --ext .png "$target" 2>/dev/null || true + fi + echo " ✓ $name" + fi + ((copied++)) || true + else + echo " ⚠ orphaned screenshot (no inventory entry): $name" + ((orphaned++)) || true + fi +done < <(find "$SNAPSHOTS_DIR" -name '*.png' -print0 2>/dev/null) + +# Report inventory entries not covered by any screenshot this run +while IFS= read -r name; do + snapshot="$SNAPSHOTS_DIR/${name}.png" + if [[ ! -f "$snapshot" ]]; then + ((missing_inventory++)) || true + fi +done < <(python3 -c " +import json +inventory = json.loads(open('$INVENTORY').read()) +for e in inventory: + if not e.get('is_wildcard'): + print(e['cypress_name']) +") + +echo "" +echo "Sync complete:" +echo " ✓ $copied screenshots copied" +[[ $orphaned -gt 0 ]] && echo " ⚠ $orphaned orphaned Cypress screenshots (spec exists, no RST directive)" +[[ $missing_inventory -gt 0 ]] && echo " — $missing_inventory inventory entries not captured this run" +[[ "$HAS_PNGQUANT" == "false" ]] && echo " ℹ pngquant not found — install it for automatic compression" diff --git a/tsconfig.cypress.json b/tsconfig.cypress.json new file mode 100644 index 00000000000..b202bbf76c8 --- /dev/null +++ b/tsconfig.cypress.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "types": ["cypress", "node"] + }, + "include": [ + "cypress/**/*.ts", + "cypress.config.ts" + ] +} From f6792f61c99f0cf9b0fb6cedc4333ec46fba7e2a Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Mon, 4 May 2026 17:17:43 +0200 Subject: [PATCH 02/28] feat(screenshots): add proof-of-concept Files spec (12/12 passing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cypress/e2e/user/files.cy.ts: 12 screenshot tests covering access_webgui.rst, sharing.rst and quota.rst sections of the user manual - Corrected NC33 selectors from server source inspection: [data-cy-upload-picker] for upload menu, [data-cy-files-navigation] for nav, [data-cy-files-list-row-actions] with button[aria-label="Actions"] for sidebar, [data-cy-sidebar] for the details panel - Added 'comments' to SCREENSHOT_APPS; comments integrates into Activity tab when the activity app is enabled - Renamed tsconfig.cypress.json → tsconfig.json (Cypress 13 requires this name) - Refined cypress/support/{e2e,commands}.ts imports and exception handler AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 29 +- cypress/e2e/user/files.cy.ts | 152 +++++ cypress/support/commands.ts | 2 +- cypress/support/e2e.ts | 8 +- package-lock.json | 819 ++++++++++++++++++++++--- package.json | 6 +- tsconfig.cypress.json => tsconfig.json | 0 7 files changed, 927 insertions(+), 89 deletions(-) create mode 100644 cypress/e2e/user/files.cy.ts rename tsconfig.cypress.json => tsconfig.json (100%) diff --git a/cypress.config.ts b/cypress.config.ts index b6626c6e468..360f8d54ff1 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -6,18 +6,17 @@ import { } from '@nextcloud/cypress/docker' import { defineConfig } from 'cypress' -// Apps to enable in the screenshot environment. -// These must be available on the stable33 server image. +// Port exposed to the host for the screenshot container. +// Choose something unlikely to conflict with the dev environment. +const SCREENSHOT_PORT = 8093 + +// Apps to enable. Must be in @nextcloud/cypress VENDOR_APPS (activity, viewer, +// notifications, text) or bundled in the server image. Anything else triggers +// occ app:install which requires app-store access and is slow. const SCREENSHOT_APPS = [ 'activity', - 'calendar', - 'contacts', - 'deck', - 'notes', - 'photos', - 'spreed', - 'tasks', - 'theming', + 'comments', + 'notifications', 'viewer', ] @@ -60,10 +59,12 @@ export default defineConfig({ stopNextcloud() }) - // Start a clean stable33 container — separate from the dev environment - const ip = await startNextcloud('stable33', false) - config.baseUrl = `http://${ip}/index.php` - await waitOnNextcloud(ip) + // Start a clean stable33 container, isolated from the dev environment. + // Use exposePort so we can reach it at localhost:PORT regardless of + // Docker network topology (avoids the stats-vs-inspect IP detection bug). + await startNextcloud('stable33', false, { exposePort: SCREENSHOT_PORT }) + config.baseUrl = `http://localhost:${SCREENSHOT_PORT}/index.php` + await waitOnNextcloud(`localhost:${SCREENSHOT_PORT}`) await configureNextcloud(SCREENSHOT_APPS) return config diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts new file mode 100644 index 00000000000..f38a80c870d --- /dev/null +++ b/cypress/e2e/user/files.cy.ts @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { User } from '@nextcloud/cypress' +import { docScreenshot, docElementScreenshot } from '../helpers' + +// Use the default admin user from the CI container +const admin = new User('admin', 'admin') + +describe('Documentation screenshots — Files', { testIsolation: false }, () => { + + before(() => { + cy.login(admin) + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + }) + + beforeEach(() => { + cy.login(admin) + }) + + // ------------------------------------------------------------------------- + // access_webgui.rst + // ------------------------------------------------------------------------- + + it('Files — main view (users-files)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-content]').should('be.visible') + cy.get('[data-cy-files-list]').should('be.visible') + docScreenshot('user/users-files') + }) + + it('Files — new file/upload menu (files_page-1)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // UploadPicker container is [data-cy-upload-picker]; its button opens an NcActions menu + cy.get('[data-cy-upload-picker] button').first().click() + cy.get('[role="menuitem"]').first().should('be.visible') + docScreenshot('user/files_page-1') + }) + + it('Files — file row with actions menu (files_page-3)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // Hover to reveal inline actions + cy.get('[data-cy-files-list-row]').first().trigger('mouseover') + cy.get('[data-cy-files-list-row-actions]').first().should('be.visible') + docScreenshot('user/files_page-3') + }) + + it('Files — details sidebar (files_page-4)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // Open Actions menu for first file, then click Details + cy.get('[data-cy-files-list-row]').first() + .find('button[aria-label="Actions"]').click({ force: true }) + cy.get('[data-cy-files-list-row-action="details"]').first() + .click() + cy.get('[data-cy-sidebar]').should('be.visible') + docScreenshot('user/files_page-4') + }) + + it('Files — left navigation panel (files_page-5)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-navigation]').should('be.visible') + docElementScreenshot('[data-cy-files-navigation]', 'user/files_page-5') + }) + + it('Files — breadcrumbs inside a folder (files_page-6)', () => { + cy.visit('/apps/files/files?dir=/Documents') + cy.get('[data-cy-files-content]').should('be.visible') + cy.get('.files-list__header').should('be.visible') + docScreenshot('user/files_page-6') + }) + + it('Files — search / filter (files_page-7)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-navigation]').should('be.visible') + // Open the search field in the navigation + cy.get('.app-navigation-search input, [data-cy-app-navigation-search] input').should('be.visible').type('Document') + cy.wait(500) + docScreenshot('user/files_page-7') + }) + + it('Files — grid view (files_page-8)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // Grid view toggle button + cy.get('.files-list__header-grid-button').click() + cy.get('.files-list--grid, [class*="grid"]').should('exist') + docScreenshot('user/files_page-8') + // Reset to list view so subsequent tests don't inherit grid mode + cy.get('.files-list__header-grid-button').click() + }) + + it('Files — comment in sidebar (file_menu_comments_2)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // Open the Actions menu for the first file, then click Details + cy.get('[data-cy-files-list-row]').first() + .find('button[aria-label="Actions"]').click({ force: true }) + cy.get('[data-cy-files-list-row-action="details"]').first() + .click() + cy.get('[data-cy-sidebar]').should('be.visible') + // With activity app enabled, comments integrate into the Activity tab + cy.contains('[role="tab"]', 'Activity').click() + cy.get('[role="tabpanel"]').should('be.visible') + docScreenshot('user/file_menu_comments_2') + }) + + // ------------------------------------------------------------------------- + // sharing.rst + // ------------------------------------------------------------------------- + + it('Files — sharing panel (sharing_internal)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + cy.get('[data-cy-files-list-row]').first() + .find('button[aria-label="Actions"]').click({ force: true }) + cy.get('[data-cy-files-list-row-action="details"]').first() + .click() + cy.get('[data-cy-sidebar]').should('be.visible') + cy.contains('[role="tab"]', 'Sharing').click() + cy.get('[role="tabpanel"]').should('be.visible') + docScreenshot('user/sharing_internal') + }) + + it('Files — public link share (sharing_public_file)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + cy.get('[data-cy-files-list-row]').first() + .find('button[aria-label="Actions"]').click({ force: true }) + cy.get('[data-cy-files-list-row-action="details"]').first() + .click() + cy.get('[data-cy-sidebar]').should('be.visible') + cy.contains('[role="tab"]', 'Sharing').click() + cy.get('[role="tabpanel"]').should('be.visible') + cy.get('button[aria-label="Create a new share link"]').click() + cy.get('.sharing-entry.sharing-entry--share').should('be.visible') + docScreenshot('user/sharing_public_file') + }) + + // ------------------------------------------------------------------------- + // quota.rst + // ------------------------------------------------------------------------- + + it('Files — quota display (quota1)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible') + docElementScreenshot('[data-cy-files-navigation-settings-quota]', 'user/quota1') + }) +}) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index fd1d5f11a06..679f211f112 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -3,4 +3,4 @@ // Custom commands for documentation screenshots. // @nextcloud/cypress commands (cy.login, cy.createRandomUser, etc.) are -// loaded via e2e.ts → '@nextcloud/cypress/support'. +// registered via addCommands() in e2e.ts. diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index df24c6748c4..7873e92ed0a 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -1,5 +1,11 @@ // SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors // SPDX-License-Identifier: AGPL-3.0-or-later -import '@nextcloud/cypress/support' +import { addCommands } from '@nextcloud/cypress' import './commands' + +addCommands() + +// Ignore all uncaught exceptions from the application — we're capturing +// screenshots, not testing JS correctness, so app-level errors are irrelevant. +Cypress.on('uncaught:exception', () => false) diff --git a/package-lock.json b/package-lock.json index 5edab987f89..f2aa1b9c00c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,33 @@ "name": "nextcloud-documentation-screenshots", "license": "AGPL-3.0-or-later", "devDependencies": { - "@nextcloud/cypress": "^1.1.0", - "@types/node": "^22.0.0", - "cypress": "^14.0.0", + "@nextcloud/cypress": "1.0.0-beta.15", + "@types/node": "^20.0.0", + "cypress": "^13.9.0", "typescript": "^5.0.0" }, "engines": { "node": ">=20" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cypress/request": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz", @@ -67,22 +85,240 @@ "ms": "^2.1.1" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@hapi/address": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz", + "integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/formula": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz", + "integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/hoek": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz", + "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/pinpoint": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz", + "integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/tlds": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz", + "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@hapi/topo": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz", + "integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^11.0.2" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@nextcloud/cypress": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@nextcloud/cypress/-/cypress-1.1.0.tgz", - "integrity": "sha512-+KtAe3+xitPc9UQOnxzLyrlTcYDqDBYiGhS4wd7UJW0m+Qs/+A6Legdl4afqfe9jX/+g3Nb/oLT1+NzxelQRCQ==", - "deprecated": "Please use v1.1.1 or above", + "version": "1.0.0-beta.15", + "resolved": "https://registry.npmjs.org/@nextcloud/cypress/-/cypress-1.0.0-beta.15.tgz", + "integrity": "sha512-0pTweoFmw9x0J2R1Ou+ycIw8aGuJGTrbudaorl4vFXUEDEIo4GAx136RzH1TzYkRn1zPxDloFi2qmHh54NMdng==", "dev": true, "license": "AGPL-3.0-or-later", + "dependencies": { + "dockerode": "^4.0.2", + "fast-xml-parser": "^5.2.2", + "wait-on": "^8.0.0" + }, "engines": { - "node": "^16.0.0", - "npm": "^7.0.0 || ^8.0.0" + "node": "^20.0.0", + "npm": "^10.0.0" + }, + "peerDependencies": { + "cypress": "^13.9.0" + } + }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", "dependencies": { @@ -154,19 +390,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -285,6 +508,28 @@ "dev": true, "license": "MIT" }, + "node_modules/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -316,6 +561,18 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -365,6 +622,16 @@ "node": "*" } }, + "node_modules/buildcheck": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", + "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cachedir": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", @@ -453,6 +720,13 @@ "node": ">= 0.8.0" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/ci-info": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", @@ -493,9 +767,9 @@ } }, "node_modules/cli-table3": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", - "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "license": "MIT", "dependencies": { @@ -505,7 +779,7 @@ "node": "10.* || >= 12.*" }, "optionalDependencies": { - "colors": "1.4.0" + "@colors/colors": "1.5.0" } }, "node_modules/cli-truncate": { @@ -525,6 +799,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -552,17 +841,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -603,6 +881,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -619,14 +912,14 @@ } }, "node_modules/cypress": { - "version": "14.5.4", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.5.4.tgz", - "integrity": "sha512-0Dhm4qc9VatOcI1GiFGVt8osgpPdqJLHzRwcAB5MSD/CAAts3oybvPUPawHyvJZUd8osADqZe/xzMsZ8sDTjXw==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.9", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -637,9 +930,9 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", - "ci-info": "^4.1.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", - "cli-table3": "0.6.1", + "cli-table3": "~0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", @@ -652,7 +945,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "hasha": "5.2.2", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -664,7 +956,7 @@ "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.7.1", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", "tree-kill": "1.2.2", @@ -675,7 +967,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, "node_modules/dashdash": { @@ -726,6 +1018,56 @@ "node": ">=0.4.0" } }, + "node_modules/docker-modem": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.7.tgz", + "integrity": "sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.15.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.12.tgz", + "integrity": "sha512-/bCZd6KlGcjZO8Buqmi/vXuqEGVEZ0PNjx/biBNqJD3MhK9DmdiAuKxqfNhflgDESDIiBz3qF+0e55+CpnrUcw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@grpc/grpc-js": "^1.11.1", + "@grpc/proto-loader": "^0.7.13", + "docker-modem": "^5.0.7", + "protobufjs": "^7.3.2", + "tar-fs": "^2.1.4", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/dockerode/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -832,6 +1174,16 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -924,6 +1276,44 @@ ], "license": "MIT" }, + "node_modules/fast-xml-builder": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.7.tgz", + "integrity": "sha512-Yh7/7rQuMXICNr0oMYDR2yHP6oUvmQsTToFeOWj/kIDhAwQ+c4Ol/lbcwOmEM5OHYQmh6S6EQSQ1sljCKP36bQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -950,6 +1340,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -977,6 +1388,13 @@ "node": ">= 6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -1003,6 +1421,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1153,23 +1581,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hasown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", @@ -1239,6 +1650,13 @@ "node": ">=8" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ini": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", @@ -1333,6 +1751,25 @@ "dev": true, "license": "MIT" }, + "node_modules/joi": { + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz", + "integrity": "sha512-rF5MAmps5esSlhCA+N1b6IYHDw9j/btzGaqfgie522jS02Ju/HXBxamlXVlKEHAxoMKQL77HWI8jlqWsFuekZA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/address": "^5.1.1", + "@hapi/formula": "^3.0.2", + "@hapi/hoek": "^11.0.7", + "@hapi/pinpoint": "^2.0.1", + "@hapi/tlds": "^1.1.1", + "@hapi/topo": "^6.0.2", + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -1428,6 +1865,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -1504,6 +1948,13 @@ "node": ">=8" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1564,6 +2015,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1571,6 +2029,14 @@ "dev": true, "license": "MIT" }, + "node_modules/nan": { + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", + "integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -1646,6 +2112,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -1703,6 +2185,31 @@ "node": ">= 0.6.0" } }, + "node_modules/protobufjs": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", + "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", @@ -1737,6 +2244,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", @@ -1747,6 +2269,16 @@ "throttleit": "^1.0.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -1940,6 +2472,31 @@ "node": ">=8" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ssh2": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", + "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.23.0" + } + }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -1966,6 +2523,16 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -2004,6 +2571,19 @@ "node": ">=6" } }, + "node_modules/strnum": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", + "integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -2020,6 +2600,36 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", @@ -2118,13 +2728,16 @@ "license": "Unlicense" }, "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==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typescript": { @@ -2168,6 +2781,13 @@ "node": ">=8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -2194,6 +2814,26 @@ "extsprintf": "^1.2.0" } }, + "node_modules/wait-on": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.5.tgz", + "integrity": "sha512-J3WlS0txVHkhLRb2FsmRg3dkMTCV1+M6Xra3Ho7HzZDHpE7DCOnoSoCJsZotrmW3uRMhvIJGSKUKrh/MeF4iag==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.12.1", + "joi": "^18.0.1", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.2" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2235,6 +2875,45 @@ "dev": true, "license": "ISC" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/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/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index e869f71fbd6..7be2331cc74 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "inventory": "python3 scripts/inventory.py" }, "devDependencies": { - "@nextcloud/cypress": "^1.1.0", - "@types/node": "^22.0.0", - "cypress": "^14.0.0", + "@nextcloud/cypress": "1.0.0-beta.15", + "@types/node": "^20.0.0", + "cypress": "^13.9.0", "typescript": "^5.0.0" } } diff --git a/tsconfig.cypress.json b/tsconfig.json similarity index 100% rename from tsconfig.cypress.json rename to tsconfig.json From 79a91febacfd041e9d620890b146abaa1314314a Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Mon, 4 May 2026 18:36:24 +0200 Subject: [PATCH 03/28] =?UTF-8?q?feat(screenshots):=20add=20MariaDB?= =?UTF-8?q?=E2=86=92SQLite=20converter=20and=20seed=20data=20injection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add scripts/mysql2sqlite.py to convert a Nextcloud MariaDB dump to a SQLite database suitable for the Cypress CI container (handles type mapping, AUTO_INCREMENT removal, COMMENT stripping, prefix index lengths, MySQL escape sequences, and INSERT OR IGNORE for constraint conflicts) - Wire seed data injection into cypress.config.ts: after startNextcloud, the setup converts ~/Downloads/tp/tp.sql (or $SEED_DATA_PATH/tp.sql) on first run, injects the SQLite into the container, creates admin:admin, re-enables required apps, and copies per-user avatars - Add suppressFocusRings() helper to strip focus outlines before every screenshot (helpers.ts) - Fix files.cy.ts: create Documents folder via WebDAV in before() hook so breadcrumb screenshot shows content; dismiss toasts before the public-link sharing screenshot; add comments app to SCREENSHOT_APPS AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 106 ++++++++++ cypress/e2e/helpers.ts | 12 ++ cypress/e2e/user/files.cy.ts | 10 + scripts/mysql2sqlite.py | 365 +++++++++++++++++++++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 scripts/mysql2sqlite.py diff --git a/cypress.config.ts b/cypress.config.ts index 360f8d54ff1..527e4bf79b2 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,6 +5,9 @@ import { waitOnNextcloud, } from '@nextcloud/cypress/docker' import { defineConfig } from 'cypress' +import { execSync } from 'child_process' +import { existsSync, readdirSync, statSync } from 'fs' +import path from 'path' // Port exposed to the host for the screenshot container. // Choose something unlikely to conflict with the dev environment. @@ -20,6 +23,104 @@ const SCREENSHOT_APPS = [ 'viewer', ] +// Optional path to seed data directory containing tp.sql and avatar/. +// Set SEED_DATA_PATH env var or place data at ~/Downloads/tp. +const SEED_DATA_PATH = process.env.SEED_DATA_PATH + ?? path.join(process.env.HOME ?? '', 'Downloads/tp') + +// Converted SQLite is cached here so we only convert once per session. +const SEED_SQLITE_CACHE = path.join(require('os').tmpdir(), 'nc-screenshots-seed.sqlite') + +// Docker container name is derived from the project directory name by @nextcloud/cypress. +const CONTAINER_NAME = `nextcloud-cypress-tests_${path.basename(process.cwd())}` + +function containerExec(cmd: string, opts: { user?: string, env?: Record } = {}): string { + const userFlag = opts.user ? `-u ${opts.user}` : '' + const envFlags = opts.env + ? Object.entries(opts.env).map(([k, v]) => `-e ${k}=${v}`).join(' ') + : '' + return execSync(`docker exec ${envFlags} ${userFlag} ${CONTAINER_NAME} ${cmd}`, { encoding: 'utf8' }) +} + +async function injectSeedData(): Promise { + if (!existsSync(SEED_DATA_PATH)) { + console.log(`[seed] No seed data at ${SEED_DATA_PATH} — skipping`) + return + } + + const sqlFile = path.join(SEED_DATA_PATH, 'tp.sql') + if (!existsSync(sqlFile)) { + console.log(`[seed] tp.sql not found in ${SEED_DATA_PATH} — skipping`) + return + } + + // Convert dump to SQLite (cached across runs until the cache file is removed) + if (!existsSync(SEED_SQLITE_CACHE)) { + console.log('[seed] Converting MariaDB dump to SQLite (one-time, ~30 s)…') + execSync( + `python3 ${path.join(__dirname, 'scripts/mysql2sqlite.py')} ${sqlFile} ${SEED_SQLITE_CACHE}`, + { stdio: 'inherit' }, + ) + } + + // Find the SQLite database file inside the container + let dbPath: string + try { + dbPath = containerExec('find /var/www/html/data -maxdepth 1 -name "*.db"').trim() + } catch { + dbPath = '/var/www/html/data/nextcloud.db' + } + if (!dbPath) dbPath = '/var/www/html/data/nextcloud.db' + + console.log(`[seed] Injecting database into container (${dbPath})…`) + execSync(`docker cp ${SEED_SQLITE_CACHE} ${CONTAINER_NAME}:/tmp/nc-seed.sqlite`) + containerExec(`bash -c "cp /tmp/nc-seed.sqlite ${dbPath} && chown www-data:root ${dbPath} && chmod 640 ${dbPath}"`) + + // Ensure admin:admin exists (it won't be in the dump) + console.log('[seed] Creating admin user…') + try { + containerExec('php occ user:add admin --password-from-env --display-name Admin', { + user: 'www-data', + env: { OC_PASS: 'admin' }, + }) + } catch { + // User might already exist if the dump happens to have one + } + // Grant admin rights + try { + containerExec('php occ group:adduser admin admin', { user: 'www-data' }) + } catch { /* ignore if already in group */ } + + // Re-enable apps (the dump's oc_appconfig may have different app state) + for (const app of SCREENSHOT_APPS) { + try { + containerExec(`php occ app:enable ${app}`, { user: 'www-data' }) + } catch { /* already enabled */ } + } + + // Refresh the data fingerprint so NC accepts the new database + containerExec('php occ maintenance:data-fingerprint', { user: 'www-data' }) + + // Copy avatars into the container's data directory + const avatarDir = path.join(SEED_DATA_PATH, 'avatar') + if (existsSync(avatarDir)) { + console.log('[seed] Copying avatars…') + for (const username of readdirSync(avatarDir)) { + const userAvatarPath = path.join(avatarDir, username) + if (!statSync(userAvatarPath).isDirectory()) continue + try { + containerExec(`mkdir -p /var/www/html/data/${username}/avatars`) + execSync(`docker cp ${userAvatarPath}/. ${CONTAINER_NAME}:/var/www/html/data/${username}/avatars/`) + containerExec(`chown -R www-data:root /var/www/html/data/${username}/avatars/`) + } catch (e) { + console.warn(`[seed] Could not copy avatar for ${username}: ${e}`) + } + } + } + + console.log('[seed] Seed data injection complete') +} + export default defineConfig({ // 16:9, matches most documentation screenshot widths viewportWidth: 1280, @@ -65,6 +166,11 @@ export default defineConfig({ await startNextcloud('stable33', false, { exposePort: SCREENSHOT_PORT }) config.baseUrl = `http://localhost:${SCREENSHOT_PORT}/index.php` await waitOnNextcloud(`localhost:${SCREENSHOT_PORT}`) + + // Inject pre-seeded data before configuring apps, so app state is + // set correctly on top of the restored database. + await injectSeedData() + await configureNextcloud(SCREENSHOT_APPS) return config diff --git a/cypress/e2e/helpers.ts b/cypress/e2e/helpers.ts index 99a35e0d12d..1345290b9e2 100644 --- a/cypress/e2e/helpers.ts +++ b/cypress/e2e/helpers.ts @@ -10,7 +10,18 @@ * The sync script (scripts/sync.sh) reads screenshot-inventory.json to map * these names to their RST image directive targets. */ +/** Inject CSS to strip focus outlines before capturing. */ +function suppressFocusRings(): void { + cy.document().then((doc) => { + const style = doc.createElement('style') + style.setAttribute('data-doc-screenshot', '') + style.textContent = '*:focus, *:focus-visible { outline: none !important; }' + doc.head.appendChild(style) + }) +} + export function docScreenshot(name: string, options: Partial = {}): void { + suppressFocusRings() // Let animations, loaders, and toasts settle before capturing cy.wait(500) cy.screenshot(name, { @@ -29,6 +40,7 @@ export function docElementScreenshot( name: string, options: Partial = {}, ): void { + suppressFocusRings() cy.wait(500) cy.get(selector).should('be.visible').screenshot(name, { overwrite: true, diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index f38a80c870d..a2a6822ebbf 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -11,6 +11,13 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => before(() => { cy.login(admin) + // Create a Documents folder so the breadcrumb screenshot has something to show + cy.request({ + method: 'MKCOL', + url: '/remote.php/dav/files/admin/Documents', + auth: { user: 'admin', pass: 'admin' }, + failOnStatusCode: false, // 405 if it already exists — that's fine + }) cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') }) @@ -137,6 +144,9 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => cy.get('[role="tabpanel"]').should('be.visible') cy.get('button[aria-label="Create a new share link"]').click() cy.get('.sharing-entry.sharing-entry--share').should('be.visible') + // Dismiss toasts so they don't overlap the sharing panel + cy.get('button.toast-close').click({ multiple: true, force: true }) + cy.get('.toastify').should('not.exist') docScreenshot('user/sharing_public_file') }) diff --git a/scripts/mysql2sqlite.py b/scripts/mysql2sqlite.py new file mode 100644 index 00000000000..d25e4ca9bc6 --- /dev/null +++ b/scripts/mysql2sqlite.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later +""" +Convert a Nextcloud MariaDB/MySQL dump to a SQLite database file. + +Usage: python3 scripts/mysql2sqlite.py +""" + +import re +import sqlite3 +import sys + + +# --------------------------------------------------------------------------- # +# Patterns to skip entirely # +# --------------------------------------------------------------------------- # + +SKIP_PATTERNS = re.compile( + r'^(' + r'/\*.*?\*/\s*;?' # inline comments like /*!...*/; + r'|SET\s+@' # SET @OLD_AUTOCOMMIT etc. + r'|SET\s+@@' # SET @@AUTOCOMMIT + r'|SET\s+NAMES' # SET NAMES utf8mb4 + r'|SET\s+TIME_ZONE' + r'|LOCK\s+TABLES' + r'|UNLOCK\s+TABLES' + r'|/\*M!' # MariaDB-specific pragmas + r'|--' # SQL comments + r'|^\s*$' # blank lines + r')', + re.IGNORECASE, +) + +# --------------------------------------------------------------------------- # +# Type mapping # +# --------------------------------------------------------------------------- # + +TYPE_MAP = [ + (re.compile(r'\b(tiny|small|medium|big)?int\s*\(\d+\)\s*(unsigned)?', re.I), 'INTEGER '), + (re.compile(r'\bbigint\b(\s*\(\d+\))?(\s*unsigned)?', re.I), 'INTEGER '), + (re.compile(r'\btinyint\b(\s*\(\d+\))?', re.I), 'INTEGER '), + (re.compile(r'\bsmallint\b(\s*\(\d+\))?', re.I), 'INTEGER '), + (re.compile(r'\bmediumint\b(\s*\(\d+\))?', re.I), 'INTEGER '), + (re.compile(r'\bvarchar\s*\(\d+\)', re.I), 'TEXT '), + (re.compile(r'\bchar\s*\(\d+\)', re.I), 'TEXT '), + (re.compile(r'\b(long|medium|tiny)?text\b', re.I), 'TEXT '), + (re.compile(r'\bblob\b', re.I), 'BLOB '), + (re.compile(r'\b(long|medium|tiny)?blob\b', re.I), 'BLOB '), + (re.compile(r'\bdatetime\b', re.I), 'TEXT '), + (re.compile(r'\btimestamp\b', re.I), 'TEXT '), + (re.compile(r'\bdate\b', re.I), 'TEXT '), + (re.compile(r'\btime\b', re.I), 'TEXT '), + (re.compile(r'\bdouble\b(\s*\(\d+,\d+\))?', re.I), 'REAL '), + (re.compile(r'\bfloat\b(\s*\(\d+,\d+\))?', re.I), 'REAL '), + (re.compile(r'\bdecimal\b(\s*\(\d+,\d+\))?', re.I), 'REAL '), + (re.compile(r'\benum\s*\([^)]+\)', re.I), 'TEXT '), + (re.compile(r'\bset\s*\([^)]+\)', re.I), 'TEXT '), + (re.compile(r'\bjson\b', re.I), 'TEXT '), +] + + +def map_type(col_def: str) -> str: + for pattern, replacement in TYPE_MAP: + col_def = pattern.sub(replacement, col_def) + return re.sub(r'\s+', ' ', col_def).strip() + + +def unquote_identifier(name: str) -> str: + """Strip backtick quoting from an identifier.""" + return name.strip().strip('`') + + +def quote_identifier(name: str) -> str: + return f'"{unquote_identifier(name)}"' + + +def transform_identifiers(sql: str) -> str: + """Replace `backtick` quoted identifiers with "double-quote" ones.""" + return re.sub(r'`([^`]+)`', lambda m: f'"{m.group(1)}"', sql) + + +def strip_index_prefixes(cols: str) -> str: + """Remove MySQL prefix-length suffixes like col(128) from index column lists.""" + return re.sub(r'(\w+"?)\s*\(\d+\)', r'\1', cols) + + +# --------------------------------------------------------------------------- # +# CREATE TABLE processing # +# --------------------------------------------------------------------------- # + +def process_create_table(block: str) -> list[str]: + """ + Convert a MySQL CREATE TABLE block to SQLite DDL statements. + Returns a list of SQL strings to execute (CREATE TABLE + CREATE INDEX stmts). + """ + # Extract table name + m = re.match(r'CREATE\s+TABLE\s+`([^`]+)`\s*\(', block, re.I) + if not m: + return [] + table = m.group(1) + + # Extract the body between the outer parentheses + start = block.index('(') + 1 + # Find matching closing paren (the one before ENGINE= or end) + body = block[start:] + # Strip the trailing ) ENGINE=... or just ) + body = re.sub(r'\)\s*(ENGINE|DEFAULT\s+CHARSET|AUTO_INCREMENT)[^;]*;?$', '', body, flags=re.I | re.DOTALL) + body = body.rstrip().rstrip(')') + + col_lines = [] + index_stmts = [] + has_explicit_pk = False + + for raw_line in body.split('\n'): + line = raw_line.strip().rstrip(',').strip() + if not line: + continue + + upper = line.upper() + + # Non-unique index → CREATE INDEX after the table + if re.match(r'KEY\s+`', line, re.I) and not re.match(r'(UNIQUE|PRIMARY)\s+KEY', line, re.I): + idx_m = re.match(r'KEY\s+`([^`]+)`\s*\((.+)\)', line, re.I) + if idx_m: + idx_name = idx_m.group(1) + idx_cols = strip_index_prefixes(transform_identifiers(idx_m.group(2))) + index_stmts.append( + f'CREATE INDEX IF NOT EXISTS "{table}_{idx_name}" ON "{table}" ({idx_cols});' + ) + continue + + # UNIQUE KEY → inline UNIQUE constraint + if re.match(r'UNIQUE\s+KEY', line, re.I): + uk_m = re.match(r'UNIQUE\s+KEY\s+`([^`]+)`\s*\((.+)\)', line, re.I) + if uk_m: + uk_cols = strip_index_prefixes(transform_identifiers(uk_m.group(2))) + col_lines.append(f' UNIQUE ({uk_cols})') + continue + + # PRIMARY KEY + if re.match(r'PRIMARY\s+KEY', line, re.I): + has_explicit_pk = True + pk_m = re.match(r'PRIMARY\s+KEY\s*\((.+)\)', line, re.I) + if pk_m: + pk_cols = transform_identifiers(pk_m.group(1)) + col_lines.append(f' PRIMARY KEY ({pk_cols})') + continue + + # Column definition + col_m = re.match(r'`([^`]+)`\s+(.*)', line) + if col_m: + col_name = col_m.group(1) + col_rest = col_m.group(2) + + # Remove MySQL-only column modifiers SQLite doesn't understand + col_rest = re.sub(r'\s*\bAUTO_INCREMENT\b\s*', ' ', col_rest, flags=re.I) + col_rest = re.sub(r"\s+COMMENT\s+'(?:[^'\\]|\\.)*'", '', col_rest, flags=re.I) + col_rest = col_rest.strip() + + # Map types + col_rest = map_type(col_rest) + + col_lines.append(f' "{col_name}" {col_rest}') + + if not col_lines: + return [] + + ddl = f'CREATE TABLE IF NOT EXISTS "{table}" (\n' + ',\n'.join(col_lines) + '\n);' + return [ddl] + index_stmts + + +# --------------------------------------------------------------------------- # +# INSERT processing # +# --------------------------------------------------------------------------- # + +def convert_mysql_string_escapes(sql: str) -> str: + """ + Convert MySQL string escape sequences to SQLite equivalents. + MySQL uses backslash escaping inside strings; SQLite does not. + The critical case is \\' (escaped single quote) → '' (SQLite convention). + We also handle \\\\ → \\ to avoid double-stripping. + This runs a single pass through the SQL, only modifying content + inside single-quoted literals. + """ + result = [] + i = 0 + n = len(sql) + in_string = False + + while i < n: + ch = sql[i] + + if not in_string: + result.append(ch) + if ch == "'": + in_string = True + i += 1 + continue + + # Inside a single-quoted string + if ch == '\\' and i + 1 < n: + next_ch = sql[i + 1] + if next_ch == "'": + # \' → '' (escaped quote in MySQL → doubled quote in SQLite) + result.append("''") + i += 2 + continue + elif next_ch == '\\': + # \\ → keep as \\ + result.append('\\\\') + i += 2 + continue + else: + # \n, \t, \r, \0 etc. — pass through as-is (SQLite treats them as + # literal backslash + char, which is fine for screenshot data) + result.append(ch) + i += 1 + continue + + if ch == "'": + result.append(ch) + # Peek: '' is an escaped quote in SQLite — leave it, don't end string + if i + 1 < n and sql[i + 1] == "'": + result.append("'") + i += 2 + continue + in_string = False + i += 1 + continue + + result.append(ch) + i += 1 + + return ''.join(result) + + +def transform_insert(stmt: str) -> str: + stmt = transform_identifiers(stmt) + stmt = convert_mysql_string_escapes(stmt) + return stmt + + +# --------------------------------------------------------------------------- # +# Main conversion loop # +# --------------------------------------------------------------------------- # + +def read_statements(sql_text: str): + """ + Yield complete SQL statements from the dump text. + Handles string literals and multi-line statements. + """ + buf = [] + in_string = False + string_char = None + escaped = False + + i = 0 + while i < len(sql_text): + ch = sql_text[i] + + if escaped: + buf.append(ch) + escaped = False + i += 1 + continue + + if ch == '\\' and in_string: + buf.append(ch) + escaped = True + i += 1 + continue + + if in_string: + buf.append(ch) + if ch == string_char: + in_string = False + i += 1 + continue + + if ch in ("'", '"'): + in_string = True + string_char = ch + buf.append(ch) + i += 1 + continue + + if ch == ';': + buf.append(ch) + stmt = ''.join(buf).strip() + if stmt and stmt != ';': + yield stmt + buf = [] + i += 1 + continue + + buf.append(ch) + i += 1 + + +def convert(input_path: str, output_path: str) -> None: + print(f'Reading {input_path}…') + with open(input_path, encoding='utf-8', errors='replace') as f: + sql_text = f.read() + + print('Parsing statements…') + stmts = list(read_statements(sql_text)) + print(f' Found {len(stmts)} statements') + + print(f'Opening {output_path}…') + con = sqlite3.connect(output_path) + con.execute('PRAGMA journal_mode=WAL') + con.execute('PRAGMA foreign_keys=OFF') + con.execute('PRAGMA synchronous=OFF') + + errors = 0 + skipped = 0 + tables = 0 + inserts = 0 + + for stmt in stmts: + first_line = stmt.split('\n')[0].strip() + + if SKIP_PATTERNS.match(first_line): + skipped += 1 + continue + + upper = first_line.upper() + + if upper.startswith('CREATE TABLE'): + sqlite_stmts = process_create_table(stmt) + for s in sqlite_stmts: + try: + con.execute(s) + except sqlite3.Error as e: + print(f' WARNING (CREATE TABLE): {e}\n SQL: {s[:120]}', file=sys.stderr) + errors += 1 + if sqlite_stmts: + tables += 1 + + elif upper.startswith('INSERT INTO'): + s = transform_insert(stmt) + # INSERT OR IGNORE so rows that violate UNIQUE or CHECK constraints + # are silently skipped rather than aborting the whole statement. + s = re.sub(r'^INSERT\s+INTO\b', 'INSERT OR IGNORE INTO', s, count=1, flags=re.I) + try: + con.execute(s) + inserts += 1 + except sqlite3.Error as e: + print(f' WARNING (INSERT): {e}\n SQL: {s[:120]}', file=sys.stderr) + errors += 1 + + # Everything else (SET, LOCK, comments embedded in statements, etc.) → skip + + con.commit() + con.close() + + print(f'Done: {tables} tables, {inserts} inserts, {errors} errors, {skipped} skipped') + + +if __name__ == '__main__': + if len(sys.argv) != 3: + print(f'Usage: {sys.argv[0]} ', file=sys.stderr) + sys.exit(1) + convert(sys.argv[1], sys.argv[2]) From ac2fd63cb47b4bf256f03bcfd6d3b6f65d6af55c Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Mon, 4 May 2026 20:57:19 +0200 Subject: [PATCH 04/28] fix(screenshots): make seed DB compatible with NC 33 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four issues prevented the seeded MariaDB dump from working in the NC 33 Cypress container: 1. **CHECK constraint on json_valid()** — MariaDB adds `CHECK (json_valid(...))` for JSON columns. SQLite 3.38+ evaluates `json_valid(NULL) = 0`, so every NC 33 INSERT into oc_share that omitted the `attributes` column failed the constraint, returning 403 "Failed to create share". Fix: strip inline CHECK constraints in mysql2sqlite.py (CHECK is always the last token on a column line). 2. **NC 34 app versions** — the seed's oc_appconfig had installed_version values for optional apps (notifications, viewer, etc.) from NC 34. NC 33 detected these as "newer than code" and entered upgrade-required mode (503). Fix: in the Python DB patch, delete all oc_appconfig entries not present in the fresh NC 33 DB; NC 33 re-populates them when enabling apps. 3. **WAL not checkpointed** — modifications to the seed SQLite cache by the Python patch remained in the WAL file. Reading only the main file gave NC 33 an incomplete DB (malformed error). Fix: run `PRAGMA wal_checkpoint(FULL)` and `PRAGMA journal_mode=DELETE` before closing the patched connection. 4. **Sidebar state restoration** — NC's Vue router restores the previously-open file sidebar between tests (testIsolation: false). When the sidebar was already open, "Details" was absent from the Actions menu. Fix: check sidebar visibility before clicking Actions and close it with Escape if needed. Also fixes `files:scan` flag (`--user` → positional argument) and adds `occ config:app:set core shareapi_allow_links yes` to force link sharing regardless of any NC 34 DB artefacts. AI-Assisted-By: claude-sonnet-4-6 Signed-off-by: Anna Larch --- cypress.config.ts | 137 ++++++++++++++++++++++++----------- cypress/e2e/user/files.cy.ts | 14 +++- scripts/mysql2sqlite.py | 5 ++ 3 files changed, 113 insertions(+), 43 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 527e4bf79b2..c6a839bd6cc 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,9 +5,10 @@ import { waitOnNextcloud, } from '@nextcloud/cypress/docker' import { defineConfig } from 'cypress' -import { execSync } from 'child_process' -import { existsSync, readdirSync, statSync } from 'fs' -import path from 'path' +import { execFileSync, execSync } from 'child_process' +import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs' +import * as os from 'os' +import * as path from 'path' // Port exposed to the host for the screenshot container. // Choose something unlikely to conflict with the dev environment. @@ -29,7 +30,7 @@ const SEED_DATA_PATH = process.env.SEED_DATA_PATH ?? path.join(process.env.HOME ?? '', 'Downloads/tp') // Converted SQLite is cached here so we only convert once per session. -const SEED_SQLITE_CACHE = path.join(require('os').tmpdir(), 'nc-screenshots-seed.sqlite') +const SEED_SQLITE_CACHE = path.join(os.tmpdir(), 'nc-screenshots-seed.sqlite') // Docker container name is derived from the project directory name by @nextcloud/cypress. const CONTAINER_NAME = `nextcloud-cypress-tests_${path.basename(process.cwd())}` @@ -63,58 +64,108 @@ async function injectSeedData(): Promise { ) } - // Find the SQLite database file inside the container + // Find the SQLite database file inside the container. + // Use split('\n')[0] in case find returns multiple matches. let dbPath: string try { - dbPath = containerExec('find /var/www/html/data -maxdepth 1 -name "*.db"').trim() + const found = containerExec('find /var/www/html/data -maxdepth 1 -name "*.db" 2>/dev/null').trim() + dbPath = found.split('\n')[0].trim() || '/var/www/html/data/nextcloud.db' } catch { dbPath = '/var/www/html/data/nextcloud.db' } - if (!dbPath) dbPath = '/var/www/html/data/nextcloud.db' - + console.log(`[seed] Container DB path: ${dbPath}`) + + // Before replacing the DB, extract the fresh container DB so we can + // copy the admin user (with its NC-generated password hash) into the seed. + // This avoids any dependency on `occ user:add` working after DB replacement. + // docker cp cannot read from tmpfs mounts (which is what the NC CI container + // uses for /var/www/html/data). Stream via shell redirection instead. + console.log('[seed] Extracting admin credentials from fresh container DB…') + const FRESH_DB_HOST = path.join(os.tmpdir(), 'nc-fresh-admin.db') + execSync(`docker exec ${CONTAINER_NAME} cat ${dbPath} > ${FRESH_DB_HOST}`, { shell: '/bin/bash' }) + + // Patch the seed SQLite with admin user + group membership from the fresh DB, + // so login works without needing occ user:add after DB replacement. + // Run a tiny Python script so we don't have to shell-escape SQL. + execSync(`python3 - <<'PYEOF' +import sqlite3, sys +fresh = sqlite3.connect(${JSON.stringify(FRESH_DB_HOST)}) +seed = sqlite3.connect(${JSON.stringify(SEED_SQLITE_CACHE)}) +def copy_rows(table, where_col, where_val): + rows = fresh.execute(f"SELECT * FROM {table} WHERE {where_col}=?", (where_val,)).fetchall() + if not rows: + return + cols = [d[0] for d in fresh.execute(f"SELECT * FROM {table} LIMIT 0").description] + ph = ",".join("?" for _ in cols) + for row in rows: + seed.execute(f"INSERT OR REPLACE INTO {table} VALUES ({ph})", row) +copy_rows("oc_users", "uid", "admin") +copy_rows("oc_group_user", "uid", "admin") +copy_rows("oc_groups", "gid", "admin") +rows = fresh.execute("SELECT * FROM oc_users WHERE uid='admin'").fetchall() +if not rows: + sys.exit("no admin row in fresh DB") +# Replace ALL oc_appconfig to avoid NC 33 entering "upgrade required" mode. +# Strategy: +# - Apps present in fresh NC 33 DB → use NC 33 values +# - Apps only in seed (NC 34 optional apps) → delete entirely so NC 33 +# can install them fresh without version conflicts +fresh_apps = {r[0] for r in fresh.execute("SELECT DISTINCT appid FROM oc_appconfig").fetchall()} +seed_apps = {r[0] for r in seed.execute("SELECT DISTINCT appid FROM oc_appconfig").fetchall()} +cols = [d[0] for d in fresh.execute("SELECT * FROM oc_appconfig LIMIT 0").description] +ph = ",".join("?" for _ in cols) +for app in seed_apps: + seed.execute("DELETE FROM oc_appconfig WHERE appid=?", (app,)) +for app in fresh_apps: + for row in fresh.execute("SELECT * FROM oc_appconfig WHERE appid=?", (app,)).fetchall(): + seed.execute(f"INSERT OR REPLACE INTO oc_appconfig VALUES ({ph})", row) +seed.commit() +# Checkpoint WAL into main file so readFileSync gets a complete, self-contained DB +seed.execute("PRAGMA wal_checkpoint(FULL)") +seed.execute("PRAGMA journal_mode=DELETE") +seed.commit() +fresh.close(); seed.close() +PYEOF`, { stdio: 'inherit' }) + + // Pipe the seed SQLite into the container via stdin — docker cp can't write + // to tmpfs mounts, but `docker exec -i sh -c 'cat > file'` works fine. console.log(`[seed] Injecting database into container (${dbPath})…`) - execSync(`docker cp ${SEED_SQLITE_CACHE} ${CONTAINER_NAME}:/tmp/nc-seed.sqlite`) - containerExec(`bash -c "cp /tmp/nc-seed.sqlite ${dbPath} && chown www-data:root ${dbPath} && chmod 640 ${dbPath}"`) - - // Ensure admin:admin exists (it won't be in the dump) - console.log('[seed] Creating admin user…') - try { - containerExec('php occ user:add admin --password-from-env --display-name Admin', { - user: 'www-data', - env: { OC_PASS: 'admin' }, - }) - } catch { - // User might already exist if the dump happens to have one - } - // Grant admin rights - try { - containerExec('php occ group:adduser admin admin', { user: 'www-data' }) - } catch { /* ignore if already in group */ } + execFileSync('docker', ['exec', '-i', CONTAINER_NAME, 'sh', '-c', `cat > ${dbPath} && chown www-data:root ${dbPath} && chmod 640 ${dbPath}`], { input: readFileSync(SEED_SQLITE_CACHE) }) - // Re-enable apps (the dump's oc_appconfig may have different app state) + // Re-enable apps (the dump's oc_appconfig may differ) for (const app of SCREENSHOT_APPS) { try { - containerExec(`php occ app:enable ${app}`, { user: 'www-data' }) + containerExec(`php /var/www/html/occ app:enable ${app}`, { user: 'www-data' }) } catch { /* already enabled */ } } - // Refresh the data fingerprint so NC accepts the new database - containerExec('php occ maintenance:data-fingerprint', { user: 'www-data' }) + // Refresh the data fingerprint so NC accepts the restored database + containerExec('php /var/www/html/occ maintenance:data-fingerprint', { user: 'www-data' }) + + // Force sharing config — seeded DB is from NC 34 and may have app config + // values that NC 33 misinterprets, causing share link creation to return 403 + containerExec('php /var/www/html/occ config:app:set core shareapi_allow_links --value yes', { user: 'www-data' }) + containerExec('php /var/www/html/occ config:app:set core shareapi_enabled --value yes', { user: 'www-data' }) + + // Re-scan admin's files so oc_filecache has fresh entries with correct permissions. + // Without this, the seeded DB may leave admin's filecache in an inconsistent state + // that causes the sharing API to return 403. + console.log('[seed] Scanning admin files…') + containerExec('php /var/www/html/occ files:scan admin', { user: 'www-data' }) - // Copy avatars into the container's data directory + // Copy avatars into the container's data directory. + // NC stores custom avatars directly as data/{userid}/avatar.{size}.png — no subdirectory. + // Pipe tar via shell to avoid buffering 15MB in Node.js (execFileSync ENOBUFS). const avatarDir = path.join(SEED_DATA_PATH, 'avatar') if (existsSync(avatarDir)) { console.log('[seed] Copying avatars…') - for (const username of readdirSync(avatarDir)) { - const userAvatarPath = path.join(avatarDir, username) - if (!statSync(userAvatarPath).isDirectory()) continue - try { - containerExec(`mkdir -p /var/www/html/data/${username}/avatars`) - execSync(`docker cp ${userAvatarPath}/. ${CONTAINER_NAME}:/var/www/html/data/${username}/avatars/`) - containerExec(`chown -R www-data:root /var/www/html/data/${username}/avatars/`) - } catch (e) { - console.warn(`[seed] Could not copy avatar for ${username}: ${e}`) - } + try { + execSync( + `tar cf - -C "${avatarDir}" . | docker exec -i ${CONTAINER_NAME} sh -c "tar xf - -C /var/www/html/data/ && chown -R www-data:root /var/www/html/data/"`, + { shell: '/bin/bash' }, + ) + } catch (e) { + console.warn('[seed] Avatar copy failed:', String(e).split('\n')[0]) } } @@ -169,7 +220,11 @@ export default defineConfig({ // Inject pre-seeded data before configuring apps, so app state is // set correctly on top of the restored database. - await injectSeedData() + try { + await injectSeedData() + } catch (e) { + console.error('[seed] Seed injection failed, continuing with fresh DB:', e) + } await configureNextcloud(SCREENSHOT_APPS) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index a2a6822ebbf..d0dc138c770 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -133,10 +133,20 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => }) it('Files — public link share (sharing_public_file)', () => { - cy.visit('/apps/files') + cy.visit('/apps/files/files?dir=/') cy.get('[data-cy-files-list]').should('be.visible') + // NC's router may restore a previous sidebar state. Close it first so + // "Details" is available in the Actions menu. + cy.get('[data-cy-sidebar]').then($sidebar => { + if ($sidebar.is(':visible')) { + cy.get('body').type('{esc}') + cy.get('[data-cy-sidebar]').should('not.be.visible') + } + }) cy.get('[data-cy-files-list-row]').first() - .find('button[aria-label="Actions"]').click({ force: true }) + .find('button[aria-label="Actions"]') + .should('be.visible') + .click() cy.get('[data-cy-files-list-row-action="details"]').first() .click() cy.get('[data-cy-sidebar]').should('be.visible') diff --git a/scripts/mysql2sqlite.py b/scripts/mysql2sqlite.py index d25e4ca9bc6..6f1893b880f 100644 --- a/scripts/mysql2sqlite.py +++ b/scripts/mysql2sqlite.py @@ -156,6 +156,11 @@ def process_create_table(block: str) -> list[str]: # Remove MySQL-only column modifiers SQLite doesn't understand col_rest = re.sub(r'\s*\bAUTO_INCREMENT\b\s*', ' ', col_rest, flags=re.I) col_rest = re.sub(r"\s+COMMENT\s+'(?:[^'\\]|\\.)*'", '', col_rest, flags=re.I) + # Strip inline CHECK constraints — MariaDB adds CHECK (json_valid(...)) for + # JSON columns. SQLite 3.38+ evaluates json_valid(NULL)=0 which causes every + # INSERT that omits the column to fail the constraint check. + # CHECK is always last in a MySQL column definition, so strip to end-of-string. + col_rest = re.sub(r'\s+CHECK\s*\(.*', '', col_rest, flags=re.I | re.DOTALL) col_rest = col_rest.strip() # Map types From a3f5395516608b3d98c8618505f6c94534ad39cd Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 11:33:07 +0200 Subject: [PATCH 05/28] refactor(screenshots): replace seed DB injection with OCS/WebDAV provisioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the MariaDB→SQLite injection pipeline entirely. Users and files are now created at test time via occ user:add (cy.task) and WebDAV PUT/MKCOL, making the setup version-agnostic and compatible with any NC release. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 176 +++-------------------------------- cypress/e2e/user/files.cy.ts | 36 ++++--- 2 files changed, 37 insertions(+), 175 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index c6a839bd6cc..1c63f6aac62 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,18 +5,22 @@ import { waitOnNextcloud, } from '@nextcloud/cypress/docker' import { defineConfig } from 'cypress' -import { execFileSync, execSync } from 'child_process' -import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs' -import * as os from 'os' +import { execSync } from 'child_process' import * as path from 'path' // Port exposed to the host for the screenshot container. -// Choose something unlikely to conflict with the dev environment. const SCREENSHOT_PORT = 8093 // Apps to enable. Must be in @nextcloud/cypress VENDOR_APPS (activity, viewer, // notifications, text) or bundled in the server image. Anything else triggers // occ app:install which requires app-store access and is slow. +const CONTAINER_NAME = `nextcloud-cypress-tests_${path.basename(process.cwd())}` + +function occ(cmd: string, env: Record = {}): string { + const envFlags = Object.entries(env).map(([k, v]) => `-e ${k}=${v}`).join(' ') + return execSync(`docker exec -u www-data ${envFlags} ${CONTAINER_NAME} php /var/www/html/occ ${cmd}`, { encoding: 'utf8' }) +} + const SCREENSHOT_APPS = [ 'activity', 'comments', @@ -24,154 +28,6 @@ const SCREENSHOT_APPS = [ 'viewer', ] -// Optional path to seed data directory containing tp.sql and avatar/. -// Set SEED_DATA_PATH env var or place data at ~/Downloads/tp. -const SEED_DATA_PATH = process.env.SEED_DATA_PATH - ?? path.join(process.env.HOME ?? '', 'Downloads/tp') - -// Converted SQLite is cached here so we only convert once per session. -const SEED_SQLITE_CACHE = path.join(os.tmpdir(), 'nc-screenshots-seed.sqlite') - -// Docker container name is derived from the project directory name by @nextcloud/cypress. -const CONTAINER_NAME = `nextcloud-cypress-tests_${path.basename(process.cwd())}` - -function containerExec(cmd: string, opts: { user?: string, env?: Record } = {}): string { - const userFlag = opts.user ? `-u ${opts.user}` : '' - const envFlags = opts.env - ? Object.entries(opts.env).map(([k, v]) => `-e ${k}=${v}`).join(' ') - : '' - return execSync(`docker exec ${envFlags} ${userFlag} ${CONTAINER_NAME} ${cmd}`, { encoding: 'utf8' }) -} - -async function injectSeedData(): Promise { - if (!existsSync(SEED_DATA_PATH)) { - console.log(`[seed] No seed data at ${SEED_DATA_PATH} — skipping`) - return - } - - const sqlFile = path.join(SEED_DATA_PATH, 'tp.sql') - if (!existsSync(sqlFile)) { - console.log(`[seed] tp.sql not found in ${SEED_DATA_PATH} — skipping`) - return - } - - // Convert dump to SQLite (cached across runs until the cache file is removed) - if (!existsSync(SEED_SQLITE_CACHE)) { - console.log('[seed] Converting MariaDB dump to SQLite (one-time, ~30 s)…') - execSync( - `python3 ${path.join(__dirname, 'scripts/mysql2sqlite.py')} ${sqlFile} ${SEED_SQLITE_CACHE}`, - { stdio: 'inherit' }, - ) - } - - // Find the SQLite database file inside the container. - // Use split('\n')[0] in case find returns multiple matches. - let dbPath: string - try { - const found = containerExec('find /var/www/html/data -maxdepth 1 -name "*.db" 2>/dev/null').trim() - dbPath = found.split('\n')[0].trim() || '/var/www/html/data/nextcloud.db' - } catch { - dbPath = '/var/www/html/data/nextcloud.db' - } - console.log(`[seed] Container DB path: ${dbPath}`) - - // Before replacing the DB, extract the fresh container DB so we can - // copy the admin user (with its NC-generated password hash) into the seed. - // This avoids any dependency on `occ user:add` working after DB replacement. - // docker cp cannot read from tmpfs mounts (which is what the NC CI container - // uses for /var/www/html/data). Stream via shell redirection instead. - console.log('[seed] Extracting admin credentials from fresh container DB…') - const FRESH_DB_HOST = path.join(os.tmpdir(), 'nc-fresh-admin.db') - execSync(`docker exec ${CONTAINER_NAME} cat ${dbPath} > ${FRESH_DB_HOST}`, { shell: '/bin/bash' }) - - // Patch the seed SQLite with admin user + group membership from the fresh DB, - // so login works without needing occ user:add after DB replacement. - // Run a tiny Python script so we don't have to shell-escape SQL. - execSync(`python3 - <<'PYEOF' -import sqlite3, sys -fresh = sqlite3.connect(${JSON.stringify(FRESH_DB_HOST)}) -seed = sqlite3.connect(${JSON.stringify(SEED_SQLITE_CACHE)}) -def copy_rows(table, where_col, where_val): - rows = fresh.execute(f"SELECT * FROM {table} WHERE {where_col}=?", (where_val,)).fetchall() - if not rows: - return - cols = [d[0] for d in fresh.execute(f"SELECT * FROM {table} LIMIT 0").description] - ph = ",".join("?" for _ in cols) - for row in rows: - seed.execute(f"INSERT OR REPLACE INTO {table} VALUES ({ph})", row) -copy_rows("oc_users", "uid", "admin") -copy_rows("oc_group_user", "uid", "admin") -copy_rows("oc_groups", "gid", "admin") -rows = fresh.execute("SELECT * FROM oc_users WHERE uid='admin'").fetchall() -if not rows: - sys.exit("no admin row in fresh DB") -# Replace ALL oc_appconfig to avoid NC 33 entering "upgrade required" mode. -# Strategy: -# - Apps present in fresh NC 33 DB → use NC 33 values -# - Apps only in seed (NC 34 optional apps) → delete entirely so NC 33 -# can install them fresh without version conflicts -fresh_apps = {r[0] for r in fresh.execute("SELECT DISTINCT appid FROM oc_appconfig").fetchall()} -seed_apps = {r[0] for r in seed.execute("SELECT DISTINCT appid FROM oc_appconfig").fetchall()} -cols = [d[0] for d in fresh.execute("SELECT * FROM oc_appconfig LIMIT 0").description] -ph = ",".join("?" for _ in cols) -for app in seed_apps: - seed.execute("DELETE FROM oc_appconfig WHERE appid=?", (app,)) -for app in fresh_apps: - for row in fresh.execute("SELECT * FROM oc_appconfig WHERE appid=?", (app,)).fetchall(): - seed.execute(f"INSERT OR REPLACE INTO oc_appconfig VALUES ({ph})", row) -seed.commit() -# Checkpoint WAL into main file so readFileSync gets a complete, self-contained DB -seed.execute("PRAGMA wal_checkpoint(FULL)") -seed.execute("PRAGMA journal_mode=DELETE") -seed.commit() -fresh.close(); seed.close() -PYEOF`, { stdio: 'inherit' }) - - // Pipe the seed SQLite into the container via stdin — docker cp can't write - // to tmpfs mounts, but `docker exec -i sh -c 'cat > file'` works fine. - console.log(`[seed] Injecting database into container (${dbPath})…`) - execFileSync('docker', ['exec', '-i', CONTAINER_NAME, 'sh', '-c', `cat > ${dbPath} && chown www-data:root ${dbPath} && chmod 640 ${dbPath}`], { input: readFileSync(SEED_SQLITE_CACHE) }) - - // Re-enable apps (the dump's oc_appconfig may differ) - for (const app of SCREENSHOT_APPS) { - try { - containerExec(`php /var/www/html/occ app:enable ${app}`, { user: 'www-data' }) - } catch { /* already enabled */ } - } - - // Refresh the data fingerprint so NC accepts the restored database - containerExec('php /var/www/html/occ maintenance:data-fingerprint', { user: 'www-data' }) - - // Force sharing config — seeded DB is from NC 34 and may have app config - // values that NC 33 misinterprets, causing share link creation to return 403 - containerExec('php /var/www/html/occ config:app:set core shareapi_allow_links --value yes', { user: 'www-data' }) - containerExec('php /var/www/html/occ config:app:set core shareapi_enabled --value yes', { user: 'www-data' }) - - // Re-scan admin's files so oc_filecache has fresh entries with correct permissions. - // Without this, the seeded DB may leave admin's filecache in an inconsistent state - // that causes the sharing API to return 403. - console.log('[seed] Scanning admin files…') - containerExec('php /var/www/html/occ files:scan admin', { user: 'www-data' }) - - // Copy avatars into the container's data directory. - // NC stores custom avatars directly as data/{userid}/avatar.{size}.png — no subdirectory. - // Pipe tar via shell to avoid buffering 15MB in Node.js (execFileSync ENOBUFS). - const avatarDir = path.join(SEED_DATA_PATH, 'avatar') - if (existsSync(avatarDir)) { - console.log('[seed] Copying avatars…') - try { - execSync( - `tar cf - -C "${avatarDir}" . | docker exec -i ${CONTAINER_NAME} sh -c "tar xf - -C /var/www/html/data/ && chown -R www-data:root /var/www/html/data/"`, - { shell: '/bin/bash' }, - ) - } catch (e) { - console.warn('[seed] Avatar copy failed:', String(e).split('\n')[0]) - } - } - - console.log('[seed] Seed data injection complete') -} - export default defineConfig({ // 16:9, matches most documentation screenshot widths viewportWidth: 1280, @@ -207,25 +63,17 @@ export default defineConfig({ } }) + on('task', { + occ: ({ cmd, env = {} }: { cmd: string, env?: Record }) => occ(cmd, env), + }) + on('after:run', () => { stopNextcloud() }) - // Start a clean stable33 container, isolated from the dev environment. - // Use exposePort so we can reach it at localhost:PORT regardless of - // Docker network topology (avoids the stats-vs-inspect IP detection bug). await startNextcloud('stable33', false, { exposePort: SCREENSHOT_PORT }) config.baseUrl = `http://localhost:${SCREENSHOT_PORT}/index.php` await waitOnNextcloud(`localhost:${SCREENSHOT_PORT}`) - - // Inject pre-seeded data before configuring apps, so app state is - // set correctly on top of the restored database. - try { - await injectSeedData() - } catch (e) { - console.error('[seed] Seed injection failed, continuing with fresh DB:', e) - } - await configureNextcloud(SCREENSHOT_APPS) return config diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index d0dc138c770..eaf7d86e383 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -4,26 +4,40 @@ import { User } from '@nextcloud/cypress' import { docScreenshot, docElementScreenshot } from '../helpers' -// Use the default admin user from the CI container -const admin = new User('admin', 'admin') +const user = new User('christine', 'christine') +const userAuth = { user: 'christine', pass: 'christine' } + +function provisionUser() { + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) +} + +function provisionFiles() { + const base = '/remote.php/dav/files/christine' + const mkdir = (p: string) => cy.request({ method: 'MKCOL', url: `${base}/${p}`, auth: userAuth, failOnStatusCode: false }) + const touch = (p: string) => cy.request({ method: 'PUT', url: `${base}/${p}`, auth: userAuth, body: '', failOnStatusCode: false }) + + mkdir('Documents') + mkdir('Photos') + touch('Company letter template.docx') + touch('Documents/Meeting notes.md') + touch('Example Spreadsheet.ods') + touch('Landscape.jpeg') + touch('Nextcloud Manual.pdf') + touch('Readme.md') +} describe('Documentation screenshots — Files', { testIsolation: false }, () => { before(() => { - cy.login(admin) - // Create a Documents folder so the breadcrumb screenshot has something to show - cy.request({ - method: 'MKCOL', - url: '/remote.php/dav/files/admin/Documents', - auth: { user: 'admin', pass: 'admin' }, - failOnStatusCode: false, // 405 if it already exists — that's fine - }) + provisionUser() + provisionFiles() + cy.login(user) cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') }) beforeEach(() => { - cy.login(admin) + cy.login(user) }) // ------------------------------------------------------------------------- From 258ab51ce2349b1716acd7a30b0ada59a1d03922 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 11:38:53 +0200 Subject: [PATCH 06/28] chore(screenshots): output to ~/Pictures/Screenshots/nextcloud-docs AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress.config.ts b/cypress.config.ts index 1c63f6aac62..2660c02c995 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -43,7 +43,7 @@ export default defineConfig({ video: false, - screenshotsFolder: 'cypress/snapshots', + screenshotsFolder: `${process.env.HOME}/Pictures/Screenshots/nextcloud-docs`, trashAssetsBeforeRuns: true, e2e: { From 25b094f4e65d0e55d21e91e77aee1d5a656ed8b9 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:00:29 +0200 Subject: [PATCH 07/28] feat(screenshots): provision realistic files for Christine user Upload Pexels wallpaper images and generated sample PDFs via Node.js WebDAV tasks (bypassing Cypress IPC Buffer serialisation). Adds uploadFile and mkdavCol tasks to cypress.config.ts, and a reportlab PDF generator script for repeatable fixture generation. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- .gitignore | 1 + cypress.config.ts | 25 +++++++++++++++++++++++++ cypress/e2e/user/files.cy.ts | 29 +++++++++++++++++++---------- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index ffca2f8208d..9078a6a8268 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ cypress/snapshots/ cypress/videos/ cypress/downloads/ screenshot-inventory.json +cypress/fixtures/pdfs/ diff --git a/cypress.config.ts b/cypress.config.ts index 2660c02c995..26047345d9b 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -6,6 +6,7 @@ import { } from '@nextcloud/cypress/docker' import { defineConfig } from 'cypress' import { execSync } from 'child_process' +import { readFileSync } from 'fs' import * as path from 'path' // Port exposed to the host for the screenshot container. @@ -65,6 +66,30 @@ export default defineConfig({ on('task', { occ: ({ cmd, env = {} }: { cmd: string, env?: Record }) => occ(cmd, env), + + // Upload a local file to WebDAV in Node.js to avoid Cypress IPC + // serialising Buffer objects as JSON (which breaks binary bodies). + async uploadFile({ src, dest, user, password }: { src: string, dest: string, user: string, password: string }) { + const content = readFileSync(src) + const credentials = Buffer.from(`${user}:${password}`).toString('base64') + const url = `http://localhost:${SCREENSHOT_PORT}/remote.php/dav/files/${user}/${dest}` + const res = await fetch(url, { + method: 'PUT', + headers: { Authorization: `Basic ${credentials}` }, + body: content, + }) + return res.status + }, + + async mkdavCol({ dest, user, password }: { dest: string, user: string, password: string }) { + const credentials = Buffer.from(`${user}:${password}`).toString('base64') + const url = `http://localhost:${SCREENSHOT_PORT}/remote.php/dav/files/${user}/${dest}` + const res = await fetch(url, { + method: 'MKCOL', + headers: { Authorization: `Basic ${credentials}` }, + }) + return res.status + }, }) on('after:run', () => { diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index eaf7d86e383..b422b19ee3a 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -5,25 +5,34 @@ import { User } from '@nextcloud/cypress' import { docScreenshot, docElementScreenshot } from '../helpers' const user = new User('christine', 'christine') -const userAuth = { user: 'christine', pass: 'christine' } function provisionUser() { cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) } +const WALLPAPERS = '/home/anna/Downloads/wallpapers' +const FIXTURES_PDFS = 'cypress/fixtures/pdfs' + function provisionFiles() { - const base = '/remote.php/dav/files/christine' - const mkdir = (p: string) => cy.request({ method: 'MKCOL', url: `${base}/${p}`, auth: userAuth, failOnStatusCode: false }) - const touch = (p: string) => cy.request({ method: 'PUT', url: `${base}/${p}`, auth: userAuth, body: '', failOnStatusCode: false }) + const mkdir = (p: string) => cy.task('mkdavCol', { dest: p, user: 'christine', password: 'christine' }) + const upload = (src: string, dest: string) => + cy.task('uploadFile', { src, dest, user: 'christine', password: 'christine' }) mkdir('Documents') mkdir('Photos') - touch('Company letter template.docx') - touch('Documents/Meeting notes.md') - touch('Example Spreadsheet.ods') - touch('Landscape.jpeg') - touch('Nextcloud Manual.pdf') - touch('Readme.md') + + // Photos folder + upload(`${WALLPAPERS}/forest-green.jpg`, 'Photos/Forest.jpg') + upload(`${WALLPAPERS}/milky-way.jpg`, 'Photos/Milky Way.jpg') + upload(`${WALLPAPERS}/city-night-purple.jpg`, 'Photos/City at night.jpg') + + // Images in root — show variety in the main file list + upload(`${WALLPAPERS}/ocean-golden.jpg`, 'Ocean sunset.jpg') + upload(`${WALLPAPERS}/snowy-mountain.jpg`, 'Snowy mountain.jpg') + + // PDFs + upload(`${FIXTURES_PDFS}/Q2 Project Proposal.pdf`, 'Q2 Project Proposal.pdf') + upload(`${FIXTURES_PDFS}/Team Meeting Notes.pdf`, 'Documents/Team Meeting Notes.pdf') } describe('Documentation screenshots — Files', { testIsolation: false }, () => { From 0eecbac59140aac3c239fe92fff07dce6d245fa5 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:04:30 +0200 Subject: [PATCH 08/28] fix(screenshots): set realistic mtimes on provisioned files via X-OC-MTime AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 10 ++++------ cypress/e2e/user/files.cy.ts | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 26047345d9b..98f392532d7 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -69,15 +69,13 @@ export default defineConfig({ // Upload a local file to WebDAV in Node.js to avoid Cypress IPC // serialising Buffer objects as JSON (which breaks binary bodies). - async uploadFile({ src, dest, user, password }: { src: string, dest: string, user: string, password: string }) { + async uploadFile({ src, dest, user, password, mtime }: { src: string, dest: string, user: string, password: string, mtime?: number }) { const content = readFileSync(src) const credentials = Buffer.from(`${user}:${password}`).toString('base64') const url = `http://localhost:${SCREENSHOT_PORT}/remote.php/dav/files/${user}/${dest}` - const res = await fetch(url, { - method: 'PUT', - headers: { Authorization: `Basic ${credentials}` }, - body: content, - }) + const headers: Record = { Authorization: `Basic ${credentials}` } + if (mtime) headers['X-OC-MTime'] = String(mtime) + const res = await fetch(url, { method: 'PUT', headers, body: content }) return res.status }, diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index b422b19ee3a..7ff7fc804b5 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -15,24 +15,26 @@ const FIXTURES_PDFS = 'cypress/fixtures/pdfs' function provisionFiles() { const mkdir = (p: string) => cy.task('mkdavCol', { dest: p, user: 'christine', password: 'christine' }) - const upload = (src: string, dest: string) => - cy.task('uploadFile', { src, dest, user: 'christine', password: 'christine' }) + // Unix timestamps for realistic modification dates + const d = (isoDate: string) => Math.floor(new Date(isoDate).getTime() / 1000) + const upload = (src: string, dest: string, mtime: number) => + cy.task('uploadFile', { src, dest, user: 'christine', password: 'christine', mtime }) mkdir('Documents') mkdir('Photos') // Photos folder - upload(`${WALLPAPERS}/forest-green.jpg`, 'Photos/Forest.jpg') - upload(`${WALLPAPERS}/milky-way.jpg`, 'Photos/Milky Way.jpg') - upload(`${WALLPAPERS}/city-night-purple.jpg`, 'Photos/City at night.jpg') + upload(`${WALLPAPERS}/forest-green.jpg`, 'Photos/Forest.jpg', d('2026-03-15')) + upload(`${WALLPAPERS}/milky-way.jpg`, 'Photos/Milky Way.jpg', d('2026-02-08')) + upload(`${WALLPAPERS}/city-night-purple.jpg`, 'Photos/City at night.jpg', d('2026-01-22')) - // Images in root — show variety in the main file list - upload(`${WALLPAPERS}/ocean-golden.jpg`, 'Ocean sunset.jpg') - upload(`${WALLPAPERS}/snowy-mountain.jpg`, 'Snowy mountain.jpg') + // Images in root + upload(`${WALLPAPERS}/ocean-golden.jpg`, 'Ocean sunset.jpg', d('2026-04-10')) + upload(`${WALLPAPERS}/snowy-mountain.jpg`, 'Snowy mountain.jpg', d('2025-12-28')) // PDFs - upload(`${FIXTURES_PDFS}/Q2 Project Proposal.pdf`, 'Q2 Project Proposal.pdf') - upload(`${FIXTURES_PDFS}/Team Meeting Notes.pdf`, 'Documents/Team Meeting Notes.pdf') + upload(`${FIXTURES_PDFS}/Q2 Project Proposal.pdf`, 'Q2 Project Proposal.pdf', d('2026-04-14')) + upload(`${FIXTURES_PDFS}/Team Meeting Notes.pdf`, 'Documents/Team Meeting Notes.pdf', d('2026-04-28')) } describe('Documentation screenshots — Files', { testIsolation: false }, () => { From e603cb44d8cc76b102521422e3d6bf9041b11d8a Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:24:23 +0200 Subject: [PATCH 09/28] feat(screenshots): compress screenshots with pngquant after each run Adds a pngquant pass in the after:run hook so every screenshot is automatically compressed (70-85 quality, ~65% size reduction) without any manual step. Degrades gracefully with a warning if pngquant is not installed. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cypress.config.ts b/cypress.config.ts index 98f392532d7..dc0d6e7cb90 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -91,6 +91,15 @@ export default defineConfig({ }) on('after:run', () => { + try { + execSync( + `find "${process.env.HOME}/Pictures/Screenshots/nextcloud-docs" -name '*.png'` + + ` -exec pngquant --quality=70-85 --force --ext .png --strip {} \\;`, + { stdio: 'inherit' }, + ) + } catch { + console.warn('pngquant not found or failed — screenshots not compressed') + } stopNextcloud() }) From 0dc81d7027f7904e879ee1abb00e423c8f5a27f2 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:26:06 +0200 Subject: [PATCH 10/28] fixup! feat(screenshots): compress screenshots with pngquant after each run Signed-off-by: Anna Larch --- cypress.config.ts | 7 +- package-lock.json | 2606 +++++++++++++++++++++++++++++++++++++++++---- package.json | 1 + 3 files changed, 2430 insertions(+), 184 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index dc0d6e7cb90..dc0ad66a774 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -90,15 +90,16 @@ export default defineConfig({ }, }) - on('after:run', () => { + on('after:run', async () => { try { + const { default: pngquantBin } = await import('pngquant-bin') execSync( `find "${process.env.HOME}/Pictures/Screenshots/nextcloud-docs" -name '*.png'` + - ` -exec pngquant --quality=70-85 --force --ext .png --strip {} \\;`, + ` -exec "${pngquantBin}" --quality=70-85 --force --ext .png --strip {} \\;`, { stdio: 'inherit' }, ) } catch { - console.warn('pngquant not found or failed — screenshots not compressed') + console.warn('pngquant failed — screenshots not compressed') } stopNextcloud() }) diff --git a/package-lock.json b/package-lock.json index f2aa1b9c00c..693d4089b69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@nextcloud/cypress": "1.0.0-beta.15", "@types/node": "^20.0.0", "cypress": "^13.9.0", + "pngquant-bin": "^9.0.0", "typescript": "^5.0.0" }, "engines": { @@ -308,6 +309,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -437,6 +448,29 @@ ], "license": "MIT" }, + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -491,6 +525,22 @@ "node": ">= 4.0.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -561,260 +611,964 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/bin-build": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bin-build/-/bin-build-3.0.0.tgz", + "integrity": "sha512-jcUOof71/TNAI2uM5uoUaDq2ePcVBQ3R/qhxAz1rX7UfvduAL/RXD3jXzvn8cVcDJdGVkiR1shal3OH0ImpuhA==", "dev": true, "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "decompress": "^4.0.0", + "download": "^6.2.2", + "execa": "^0.7.0", + "p-map-series": "^1.0.0", + "tempfile": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/bin-build/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", "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" - } - ], "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/bin-build/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", "dev": true, "license": "MIT", + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, "engines": { - "node": "*" + "node": ">=4" } }, - "node_modules/buildcheck": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", - "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "node_modules/bin-build/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", "dev": true, - "optional": true, + "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=4" } }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "node_modules/bin-build/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/bin-build/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "path-key": "^2.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/bin-build/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-build/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "shebang-regex": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "node_modules/bin-build/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/bin-build/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "isexe": "^2.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "bin": { + "which": "bin/which" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "execa": "^0.7.0", + "executable": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "node_modules/bin-check/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8.0" + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "node_modules/bin-check/node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } }, - "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "node_modules/bin-check/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/bin-check/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "node_modules/bin-check/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "path-key": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "node_modules/bin-check/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" + "node": ">=4" } }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/bin-check/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" + "shebang-regex": "^1.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "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==", + "node_modules/bin-check/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-check/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=12" + "bin": { + "which": "bin/which" } }, - "node_modules/color-convert": { + "node_modules/bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/bin-version/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/got/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-wrapper/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-wrapper/node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buildcheck": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", + "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", @@ -874,6 +1628,37 @@ "node": ">=4.0.0" } }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1008,6 +1793,264 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/decompress-tar/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-tar/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-tar/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/decompress-tar/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-tar/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/decompress-tar/node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1068,6 +2111,49 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/download": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/download/-/download-6.2.5.tgz", + "integrity": "sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "caw": "^2.0.0", + "content-disposition": "^0.5.2", + "decompress": "^4.0.0", + "ext-name": "^5.0.0", + "file-type": "5.2.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^7.0.0", + "make-dir": "^1.0.0", + "p-event": "^1.0.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1083,6 +2169,13 @@ "node": ">= 0.4" } }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1238,6 +2331,33 @@ "node": ">=4" } }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1340,6 +2460,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -1361,6 +2529,22 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1388,6 +2572,57 @@ "node": ">= 6" } }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/from2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/from2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -1470,6 +2705,19 @@ "node": ">= 0.4" } }, + "node_modules/get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "npm-conf": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -1535,6 +2783,52 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/got/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1552,6 +2846,29 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1565,6 +2882,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/has-tostringtag": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", @@ -1594,6 +2924,13 @@ "node": ">= 0.4" } }, + "node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/http-signature": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", @@ -1640,6 +2977,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -1667,6 +3014,33 @@ "node": ">=10" } }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1694,6 +3068,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -1704,6 +3095,26 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -1717,6 +3128,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1737,6 +3164,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1751,6 +3185,20 @@ "dev": true, "license": "MIT" }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/joi": { "version": "18.1.2", "resolved": "https://registry.npmjs.org/joi/-/joi-18.1.2.tgz", @@ -1777,6 +3225,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -1820,6 +3275,16 @@ "verror": "1.10.0" } }, + "node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.0" + } + }, "node_modules/lazy-ass": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", @@ -1955,6 +3420,50 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "license": "ISC", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2005,6 +3514,16 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -2037,6 +3556,75 @@ "license": "MIT", "optional": true }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url/node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-url/node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -2050,6 +3638,16 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -2089,6 +3687,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", @@ -2096,6 +3707,49 @@ "dev": true, "license": "MIT" }, + "node_modules/p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-event": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", + "integrity": "sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-timeout": "^1.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -2112,6 +3766,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", + "integrity": "sha512-4k9LlvY6Bo/1FcIdV33wqZQES0Py+iKISU9Uc8p8AjWoZPnFKMpVIVD3s0EYn4jzLh1I+WeUZkJ0Yoa4Qfw3Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-reduce": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha512-3Tx1T3oM1xO/Y8Gj0sWyE78EIJZ+t+aEmXUdvQgvGmSMri7aPTHoovbXEreWKkL5j21Er60XAWLTzKbAKYOujQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/path-expression-matcher": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", @@ -2128,34 +3818,240 @@ "node": ">=14.0.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pngquant-bin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/pngquant-bin/-/pngquant-bin-9.0.0.tgz", + "integrity": "sha512-jlOKfIQBTNJwQn2JKK5xLmwrsi/NwVTmHRvbrknCjdWxfX1/c/+yP4Jmp9jRZWedft/vnhh+rGbvl/kUmesurg==", + "dev": true, + "hasInstallScript": true, + "license": "GPL-3.0+", + "dependencies": { + "bin-build": "^3.0.0", + "bin-wrapper": "^4.0.1", + "execa": "^8.0.1" + }, + "bin": { + "pngquant": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pngquant-bin/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/pngquant-bin/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngquant-bin/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/pngquant-bin/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngquant-bin/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngquant-bin/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngquant-bin/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngquant-bin/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pngquant-bin/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": "MIT", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "node_modules/pngquant-bin/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", "dev": true, "license": "MIT", "engines": { @@ -2185,6 +4081,20 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/protobufjs": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", @@ -2217,6 +4127,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true, + "license": "ISC" + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -2244,6 +4161,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -2279,6 +4211,16 @@ "node": ">=0.10.0" } }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -2338,6 +4280,27 @@ "dev": true, "license": "MIT" }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -2351,6 +4314,57 @@ "node": ">=10" } }, + "node_modules/semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-truncate/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2472,6 +4486,32 @@ "node": ">=8" } }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", @@ -2523,6 +4563,16 @@ "node": ">=0.10.0" } }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2561,6 +4611,26 @@ "node": ">=8" } }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -2571,6 +4641,19 @@ "node": ">=6" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strnum": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", @@ -2630,6 +4713,41 @@ "node": ">=6" } }, + "node_modules/temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tempfile": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz", + "integrity": "sha512-ZOn6nJUgvgC09+doCEF3oB+r3ag7kUvlsXEGX069QRD60p+P3uP7XG9N2/at+EyIRGSN//ZY3LyEotA1YpmjuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "temp-dir": "^1.0.0", + "uuid": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tempfile/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/throttleit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", @@ -2647,6 +4765,16 @@ "dev": true, "license": "MIT" }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tldts": { "version": "6.1.86", "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", @@ -2677,6 +4805,21 @@ "node": ">=14.14" } }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/tough-cookie": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", @@ -2700,6 +4843,19 @@ "tree-kill": "cli.js" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2740,6 +4896,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -2754,6 +4925,17 @@ "node": ">=14.17" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -2781,6 +4963,29 @@ "node": ">=8" } }, + "node_modules/url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prepend-http": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2850,6 +5055,28 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2875,6 +5102,16 @@ "dev": true, "license": "ISC" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -2885,6 +5122,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 7be2331cc74..e825c57d442 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@nextcloud/cypress": "1.0.0-beta.15", "@types/node": "^20.0.0", "cypress": "^13.9.0", + "pngquant-bin": "^9.0.0", "typescript": "^5.0.0" } } From 8b3c4ab16ba85ee27116a1914a7a0f90099dc55f Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:36:48 +0200 Subject: [PATCH 11/28] fix(screenshots): crop breadcrumb screenshot to the breadcrumb element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The full-viewport shot of the Documents folder made the breadcrumb bar too small to read. Switch to docElementScreenshot targeting [data-cy-files-content-breadcrumbs] so the screenshot clearly shows the Files → Documents trail. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 7ff7fc804b5..52336d3df45 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -100,9 +100,8 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => it('Files — breadcrumbs inside a folder (files_page-6)', () => { cy.visit('/apps/files/files?dir=/Documents') - cy.get('[data-cy-files-content]').should('be.visible') - cy.get('.files-list__header').should('be.visible') - docScreenshot('user/files_page-6') + cy.get('[data-cy-files-content-breadcrumbs]').should('be.visible') + docElementScreenshot('[data-cy-files-content-breadcrumbs]', 'user/files_page-6') }) it('Files — search / filter (files_page-7)', () => { From c16f20c3ae865c2c0e1129f1d5722528eccce769 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:43:29 +0200 Subject: [PATCH 12/28] fix(screenshots): click Actions menu for files_page-3, no hover needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NC 33 shows the ··· button permanently; hover does not reveal it. Drop the mouseover trigger and click the Actions button directly so the screenshot captures the open menu as intended. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 52336d3df45..27fa20d7a7b 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -74,9 +74,9 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => it('Files — file row with actions menu (files_page-3)', () => { cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') - // Hover to reveal inline actions - cy.get('[data-cy-files-list-row]').first().trigger('mouseover') - cy.get('[data-cy-files-list-row-actions]').first().should('be.visible') + cy.get('[data-cy-files-list-row]').first() + .find('button[aria-label="Actions"]').click() + cy.get('[data-cy-files-list-row-action]').first().should('be.visible') docScreenshot('user/files_page-3') }) From 0419c59c361b9fe646d0cd04b1eaef967773bf1e Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:46:03 +0200 Subject: [PATCH 13/28] fix(screenshots): enable files_versions app for details sidebar screenshot The Versions tab was missing from the sidebar because files_versions wasn't in the SCREENSHOT_APPS list. It's a bundled app so no app-store access is needed. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress.config.ts b/cypress.config.ts index dc0ad66a774..8d6faaa60fe 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -25,6 +25,7 @@ function occ(cmd: string, env: Record = {}): string { const SCREENSHOT_APPS = [ 'activity', 'comments', + 'files_versions', 'notifications', 'viewer', ] From b8ea8a1982c8f7ab1a089c22e07aefaea25192c4 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:48:30 +0200 Subject: [PATCH 14/28] fix(screenshots): remove focus ring from grid view button in files_page-8 After clicking the grid toggle, blur focus by clicking the file list so the button outline doesn't appear in the screenshot. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 27fa20d7a7b..a7a4568e786 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -116,9 +116,10 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => it('Files — grid view (files_page-8)', () => { cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') - // Grid view toggle button cy.get('.files-list__header-grid-button').click() cy.get('.files-list--grid, [class*="grid"]').should('exist') + // Move focus away so the button outline doesn't appear in the screenshot + cy.get('[data-cy-files-list]').click({ force: true }) docScreenshot('user/files_page-8') // Reset to list view so subsequent tests don't inherit grid mode cy.get('.files-list__header-grid-button').click() From 33a3b052b70456ee77847610780f90f97d3b55a1 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 12:57:54 +0200 Subject: [PATCH 15/28] fix(screenshots): create file version so Versions tab appears in sidebar Upload Q2 Project Proposal.pdf twice to generate a version entry, and target that file specifically in the details sidebar test so the Versions tab is populated in files_page-4. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index a7a4568e786..2d1fc09e3dd 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -35,6 +35,9 @@ function provisionFiles() { // PDFs upload(`${FIXTURES_PDFS}/Q2 Project Proposal.pdf`, 'Q2 Project Proposal.pdf', d('2026-04-14')) upload(`${FIXTURES_PDFS}/Team Meeting Notes.pdf`, 'Documents/Team Meeting Notes.pdf', d('2026-04-28')) + + // Upload Q2 proposal a second time to create a version (needed for the Versions tab in the sidebar screenshot) + upload(`${FIXTURES_PDFS}/Q2 Project Proposal.pdf`, 'Q2 Project Proposal.pdf', d('2026-04-28')) } describe('Documentation screenshots — Files', { testIsolation: false }, () => { @@ -83,8 +86,8 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => it('Files — details sidebar (files_page-4)', () => { cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') - // Open Actions menu for first file, then click Details - cy.get('[data-cy-files-list-row]').first() + // Open the Q2 proposal — it has a version, so the Versions tab will be populated + cy.get('[data-cy-files-list-row][data-cy-files-list-row-name="Q2 Project Proposal.pdf"]') .find('button[aria-label="Actions"]').click({ force: true }) cy.get('[data-cy-files-list-row-action="details"]').first() .click() From c5b5956b957bb2d70787e885cac4c2464fdcd9a2 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 13:04:06 +0200 Subject: [PATCH 16/28] =?UTF-8?q?feat(screenshots):=20add=20files=5Fpage-9?= =?UTF-8?q?=20=E2=80=94=20multi-file=20selection=20with=20bulk=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selects three rows and waits for the bulk action bar to appear, illustrating the delete/download actions described in the "Selecting files or folders" section. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 2d1fc09e3dd..568fb615419 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -143,6 +143,17 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => docScreenshot('user/file_menu_comments_2') }) + it('Files — selecting multiple files (files_page-9)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // Select the first three rows + cy.get('[data-cy-files-list-row]').eq(0).find('[data-cy-files-list-row-checkbox]').click() + cy.get('[data-cy-files-list-row]').eq(1).find('[data-cy-files-list-row-checkbox]').click() + cy.get('[data-cy-files-list-row]').eq(2).find('[data-cy-files-list-row-checkbox]').click() + cy.get('[data-cy-files-list-selection-actions]').should('be.visible') + docScreenshot('user/files_page-9') + }) + // ------------------------------------------------------------------------- // sharing.rst // ------------------------------------------------------------------------- From 7970db09ac88633755466c64f81ce4c778244f43 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 13:12:23 +0200 Subject: [PATCH 17/28] =?UTF-8?q?feat(screenshots):=20add=20files=5Fsharin?= =?UTF-8?q?g=5Fstatus=20=E2=80=94=20sharing=20badge=20and=20link=20icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provisions a user share on Documents and a public link on Ocean sunset.jpg in before(), then captures the file list showing both sharing indicators alongside unshared items. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 568fb615419..5ab72be1c09 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -45,6 +45,24 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => before(() => { provisionUser() provisionFiles() + // Share Documents folder with admin (user share → shows Shared badge) + cy.request({ + method: 'POST', + url: '/ocs/v2.php/apps/files_sharing/api/v1/shares', + auth: { user: 'christine', pass: 'christine' }, + headers: { 'OCS-APIRequest': 'true' }, + body: { path: '/Documents', shareType: 0, shareWith: 'admin' }, + form: true, + }) + // Share Ocean sunset.jpg via public link (shareType 3 → shows chain-link icon) + cy.request({ + method: 'POST', + url: '/ocs/v2.php/apps/files_sharing/api/v1/shares', + auth: { user: 'christine', pass: 'christine' }, + headers: { 'OCS-APIRequest': 'true' }, + body: { path: '/Ocean sunset.jpg', shareType: 3 }, + form: true, + }) cy.login(user) cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') @@ -154,6 +172,14 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => docScreenshot('user/files_page-9') }) + it('Files — sharing status icons (files_sharing_status)', () => { + cy.visit('/apps/files') + cy.get('[data-cy-files-list]').should('be.visible') + // Wait for share badges to render (Documents = user share, Ocean sunset.jpg = public link) + cy.get('[data-cy-files-list-row][data-cy-files-list-row-name="Documents"] [class*="share"], [data-cy-files-list-row][data-cy-files-list-row-name="Ocean sunset.jpg"] [class*="share"]').should('exist') + docScreenshot('user/files_sharing_status') + }) + // ------------------------------------------------------------------------- // sharing.rst // ------------------------------------------------------------------------- From dee39040690a4cac4e4638112c997c928eadfff4 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 13:14:53 +0200 Subject: [PATCH 18/28] =?UTF-8?q?fixup!=20feat(screenshots):=20add=20files?= =?UTF-8?q?=5Fsharing=5Fstatus=20=E2=80=94=20sharing=20badge=20and=20link?= =?UTF-8?q?=20icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 5ab72be1c09..4ed5ad7348e 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -45,10 +45,13 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => before(() => { provisionUser() provisionFiles() + // baseUrl includes /index.php — strip it to reach the OCS API root + const ocsShares = (Cypress.config('baseUrl') as string).replace('/index.php', '') + + '/ocs/v2.php/apps/files_sharing/api/v1/shares' // Share Documents folder with admin (user share → shows Shared badge) cy.request({ method: 'POST', - url: '/ocs/v2.php/apps/files_sharing/api/v1/shares', + url: ocsShares, auth: { user: 'christine', pass: 'christine' }, headers: { 'OCS-APIRequest': 'true' }, body: { path: '/Documents', shareType: 0, shareWith: 'admin' }, @@ -57,7 +60,7 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => // Share Ocean sunset.jpg via public link (shareType 3 → shows chain-link icon) cy.request({ method: 'POST', - url: '/ocs/v2.php/apps/files_sharing/api/v1/shares', + url: ocsShares, auth: { user: 'christine', pass: 'christine' }, headers: { 'OCS-APIRequest': 'true' }, body: { path: '/Ocean sunset.jpg', shareType: 3 }, From c1dcf9ad9ea20835255e34e2aa74b9217357c5e5 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 13:17:57 +0200 Subject: [PATCH 19/28] =?UTF-8?q?fixup!=20fixup!=20feat(screenshots):=20ad?= =?UTF-8?q?d=20files=5Fsharing=5Fstatus=20=E2=80=94=20sharing=20badge=20an?= =?UTF-8?q?d=20link=20icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anna Larch --- cypress/e2e/user/files.cy.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 4ed5ad7348e..21f6b66eff5 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -178,8 +178,9 @@ describe('Documentation screenshots — Files', { testIsolation: false }, () => it('Files — sharing status icons (files_sharing_status)', () => { cy.visit('/apps/files') cy.get('[data-cy-files-list]').should('be.visible') - // Wait for share badges to render (Documents = user share, Ocean sunset.jpg = public link) - cy.get('[data-cy-files-list-row][data-cy-files-list-row-name="Documents"] [class*="share"], [data-cy-files-list-row][data-cy-files-list-row-name="Ocean sunset.jpg"] [class*="share"]').should('exist') + // Shares are provisioned in before(); wait for all rows to finish loading + cy.get('[data-cy-files-list-row]').should('have.length.gt', 3) + cy.wait(500) docScreenshot('user/files_sharing_status') }) From 99c07822aafe7a88d52cdce6252775ffb3beaa59 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 14:27:09 +0200 Subject: [PATCH 20/28] feat(screenshots): add webinterface spec (login, dashboard, nav, profile menu) AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/webinterface.cy.ts | 46 +++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 cypress/e2e/user/webinterface.cy.ts diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts new file mode 100644 index 00000000000..3a2d1db6faf --- /dev/null +++ b/cypress/e2e/user/webinterface.cy.ts @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { User } from '@nextcloud/cypress' +import { docScreenshot, docElementScreenshot } from '../helpers' + +const user = new User('christine', 'christine') + +before(() => { + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) +}) + +describe('Web interface', () => { + it('Login page', () => { + cy.logout() + cy.visit('/') + cy.get('#body-login, .login-form, form[name="login"]').should('be.visible') + docScreenshot('user/login_page') + }) + + it('Dashboard', () => { + cy.login(user) + cy.visit('/apps/dashboard') + // Wait for widgets to load + cy.get('.dashboard-widget, #app-content, .app-dashboard').should('be.visible') + cy.get('.loading, .icon-loading').should('not.exist') + docScreenshot('user/webinterface_dashboard') + }) + + it('Navigation bar', () => { + cy.login(user) + cy.visit('/apps/dashboard') + cy.get('header#header').should('be.visible') + docElementScreenshot('header#header', 'user/webinterface_nav') + }) + + it('Profile menu', () => { + cy.login(user) + cy.visit('/apps/dashboard') + cy.get('header#header').should('be.visible') + // Click the profile/settings button (rightmost item in the header) + cy.get('#settings button, #user-menu button, header .user-status__status button, .user-status-menu-item button').first().click() + cy.contains('Log out').should('be.visible') + docScreenshot('user/webinterface_profile_menu') + }) +}) From 9dcdba09ec876df0126432cdd09758eed6b7545f Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 14:38:09 +0200 Subject: [PATCH 21/28] feat(screenshots): add avatar provisioning for seeded users Add uploadAvatar task to cypress.config.ts that POSTs to the NC avatar endpoint via multipart/form-data with Basic Auth + OCS-APIREQUEST to bypass CSRF. Call it from the before() hooks in files.cy.ts and webinterface.cy.ts using the portraits from ~/Downloads/tp/avatar/. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 21 +++++++++++++++++++++ cypress/e2e/user/files.cy.ts | 3 +++ cypress/e2e/user/webinterface.cy.ts | 2 ++ 3 files changed, 26 insertions(+) diff --git a/cypress.config.ts b/cypress.config.ts index 8d6faaa60fe..80367857911 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -89,6 +89,27 @@ export default defineConfig({ }) return res.status }, + + async uploadAvatar({ src, user, password }: { src: string, user: string, password: string }) { + const content = readFileSync(src) + const credentials = Buffer.from(`${user}:${password}`).toString('base64') + const boundary = `----AvatarBoundary${Date.now()}` + const body = Buffer.concat([ + Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="files[]"; filename="avatar.png"\r\nContent-Type: image/png\r\n\r\n`), + content, + Buffer.from(`\r\n--${boundary}--\r\n`), + ]) + const res = await fetch(`http://localhost:${SCREENSHOT_PORT}/index.php/avatar`, { + method: 'POST', + headers: { + Authorization: `Basic ${credentials}`, + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + 'OCS-APIREQUEST': 'true', + }, + body, + }) + return res.status + }, }) on('after:run', async () => { diff --git a/cypress/e2e/user/files.cy.ts b/cypress/e2e/user/files.cy.ts index 21f6b66eff5..726fabf367c 100644 --- a/cypress/e2e/user/files.cy.ts +++ b/cypress/e2e/user/files.cy.ts @@ -6,8 +6,11 @@ import { docScreenshot, docElementScreenshot } from '../helpers' const user = new User('christine', 'christine') +const AVATAR_DIR = '/home/anna/Downloads/tp/avatar' + function provisionUser() { cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) } const WALLPAPERS = '/home/anna/Downloads/wallpapers' diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts index 3a2d1db6faf..fc2e6ccee9d 100644 --- a/cypress/e2e/user/webinterface.cy.ts +++ b/cypress/e2e/user/webinterface.cy.ts @@ -5,9 +5,11 @@ import { User } from '@nextcloud/cypress' import { docScreenshot, docElementScreenshot } from '../helpers' const user = new User('christine', 'christine') +const AVATAR_DIR = '/home/anna/Downloads/tp/avatar' before(() => { cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) }) describe('Web interface', () => { From a2a0170c37e882f5222542aa3ec776970d608f43 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 15:10:18 +0200 Subject: [PATCH 22/28] feat(screenshots): enable groupware apps and populate dashboard for christine Add calendar, circles, deck, notes, photos, tasks to SCREENSHOT_APPS. Set christine's dashboard layout via occ user:setting to match the tech-preview instance (minus mail/spreed which aren't in the server image). AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 6 ++++++ cypress/e2e/user/webinterface.cy.ts | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 80367857911..1a6a8e4f518 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -24,9 +24,15 @@ function occ(cmd: string, env: Record = {}): string { const SCREENSHOT_APPS = [ 'activity', + 'calendar', + 'circles', 'comments', + 'deck', 'files_versions', + 'notes', 'notifications', + 'photos', + 'tasks', 'viewer', ] diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts index fc2e6ccee9d..f91317bbe30 100644 --- a/cypress/e2e/user/webinterface.cy.ts +++ b/cypress/e2e/user/webinterface.cy.ts @@ -10,6 +10,9 @@ const AVATAR_DIR = '/home/anna/Downloads/tp/avatar' before(() => { cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) + // Enable dashboard widgets matching the tech-preview layout (mail/spreed omitted — not in server image) + cy.task('occ', { cmd: 'user:setting christine dashboard layout files-favorites,calendar,deck,notes,tasks,photos-onthisday,circles' }) + cy.task('occ', { cmd: 'user:setting christine dashboard firstRun 0' }) }) describe('Web interface', () => { @@ -23,9 +26,9 @@ describe('Web interface', () => { it('Dashboard', () => { cy.login(user) cy.visit('/apps/dashboard') - // Wait for widgets to load - cy.get('.dashboard-widget, #app-content, .app-dashboard').should('be.visible') - cy.get('.loading, .icon-loading').should('not.exist') + // Wait for at least one widget to appear and spinners to clear + cy.get('.panel--header, .dashboard-widget-content', { timeout: 15000 }).should('be.visible') + cy.get('.icon-loading', { timeout: 15000 }).should('not.exist') docScreenshot('user/webinterface_dashboard') }) From b2f60808ced01bb2c13e71a0d2529b2fadfcdad1 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 17:05:19 +0200 Subject: [PATCH 23/28] fix(screenshots): bypass NC 33 browser compatibility check via CDP Electron 118 (Chrome 118) falls below NC 33's minimum of Chrome 142+, causing redirects to /index.php/unsupported and blocking all tests except the login page. The --user-agent CLI arg is silently ignored in Electron; overriding via Network.setUserAgentOverride CDP command in the global before() hook is the only reliable fix. Also clean up debug code from the profile menu test. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 12 +++++++----- cypress/e2e/user/webinterface.cy.ts | 7 +++++-- cypress/support/e2e.ts | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 1a6a8e4f518..5806c3062cf 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -25,21 +25,19 @@ function occ(cmd: string, env: Record = {}): string { const SCREENSHOT_APPS = [ 'activity', 'calendar', - 'circles', 'comments', 'deck', 'files_versions', 'notes', 'notifications', - 'photos', 'tasks', 'viewer', ] export default defineConfig({ - // 16:9, matches most documentation screenshot widths - viewportWidth: 1280, - viewportHeight: 720, + // 16:10, common laptop resolution — enough height for dashboard widgets + Customise button + viewportWidth: 1440, + viewportHeight: 900, requestTimeout: 20000, defaultCommandTimeout: 10000, @@ -67,6 +65,10 @@ export default defineConfig({ } if (browser.name === 'electron') { launchOptions.preferences.spellcheck = false + // Set the Electron window size — viewportWidth/Height alone isn't + // respected in headless Electron; the BrowserWindow must be sized explicitly. + launchOptions.preferences.width = 1440 + launchOptions.preferences.height = 900 return launchOptions } }) diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts index f91317bbe30..08231c77c7a 100644 --- a/cypress/e2e/user/webinterface.cy.ts +++ b/cypress/e2e/user/webinterface.cy.ts @@ -11,11 +11,15 @@ before(() => { cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) // Enable dashboard widgets matching the tech-preview layout (mail/spreed omitted — not in server image) - cy.task('occ', { cmd: 'user:setting christine dashboard layout files-favorites,calendar,deck,notes,tasks,photos-onthisday,circles' }) + cy.task('occ', { cmd: 'user:setting christine dashboard layout files-favorites,calendar,deck,notes,tasks' }) cy.task('occ', { cmd: 'user:setting christine dashboard firstRun 0' }) }) describe('Web interface', () => { + beforeEach(() => { + cy.viewport(1440, 900) + }) + it('Login page', () => { cy.logout() cy.visit('/') @@ -43,7 +47,6 @@ describe('Web interface', () => { cy.login(user) cy.visit('/apps/dashboard') cy.get('header#header').should('be.visible') - // Click the profile/settings button (rightmost item in the header) cy.get('#settings button, #user-menu button, header .user-status__status button, .user-status-menu-item button').first().click() cy.contains('Log out').should('be.visible') docScreenshot('user/webinterface_profile_menu') diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 7873e92ed0a..d6dcd12df36 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -9,3 +9,17 @@ addCommands() // Ignore all uncaught exceptions from the application — we're capturing // screenshots, not testing JS correctness, so app-level errors are irrelevant. Cypress.on('uncaught:exception', () => false) + +// Override the user agent via CDP so NC 33's browser-compatibility check passes. +// Electron 118 reports Chrome 118, which is below NC 33's minimum of Chrome 142+. +// The --user-agent CLI arg is ignored in Electron; CDP is the only reliable override. +before(() => { + cy.wrap( + Cypress.automation('remote:debugger:protocol', { + command: 'Network.setUserAgentOverride', + params: { + userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36', + }, + }), + ) +}) From afb8d22a72d84bb3e36f15ff18d3303bd9287539 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 17:46:33 +0200 Subject: [PATCH 24/28] fix(screenshots): switch to Chromium browser and hide scrollbars Electron 118 lacks CSS sqrt() support (Chrome 120+), which breaks the NcAvatar status indicator size calculation and renders it at the SVG's natural 24px instead of the CSS-computed 13px. Switch the default screenshots script to Chromium 147 (snap), add --window-size=1440,987 to account for Chromium's browser chrome height, and inject ::-webkit-scrollbar { display: none } via suppressFocusRings to keep screenshots clean. AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 4 ++++ cypress/e2e/helpers.ts | 8 ++++++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 5806c3062cf..4692a45d941 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -57,6 +57,10 @@ export default defineConfig({ on('before:browser:launch', (browser, launchOptions) => { if (browser.family === 'chromium' && browser.name !== 'electron') { launchOptions.preferences.default['browser.enable_spellchecking'] = false + // Force the window to the configured viewport size. + // Headless Chromium ignores viewportWidth/Height from config unless + // --window-size is passed explicitly. + launchOptions.args.push('--window-size=1440,987') return launchOptions } if (browser.family === 'firefox') { diff --git a/cypress/e2e/helpers.ts b/cypress/e2e/helpers.ts index 1345290b9e2..7d0cdb6428a 100644 --- a/cypress/e2e/helpers.ts +++ b/cypress/e2e/helpers.ts @@ -10,12 +10,16 @@ * The sync script (scripts/sync.sh) reads screenshot-inventory.json to map * these names to their RST image directive targets. */ -/** Inject CSS to strip focus outlines before capturing. */ +/** Inject CSS to strip focus outlines and scrollbars before capturing. */ function suppressFocusRings(): void { cy.document().then((doc) => { const style = doc.createElement('style') style.setAttribute('data-doc-screenshot', '') - style.textContent = '*:focus, *:focus-visible { outline: none !important; }' + style.textContent = [ + '*:focus, *:focus-visible { outline: none !important; }', + '::-webkit-scrollbar { display: none !important; }', + '* { scrollbar-width: none !important; }', + ].join('\n') doc.head.appendChild(style) }) } diff --git a/package.json b/package.json index e825c57d442..61804d507cf 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "node": ">=20" }, "scripts": { - "screenshots": "cypress run --e2e", + "screenshots": "cypress run --e2e --browser chromium", "screenshots:open": "cypress open --e2e", "inventory": "python3 scripts/inventory.py" }, From f9b01a6f988c6b76cce31d1a34e6a04b15683fa1 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 18:16:18 +0200 Subject: [PATCH 25/28] feat(screenshots): add Customize button element screenshot AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/webinterface.cy.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts index 08231c77c7a..8e9536b6620 100644 --- a/cypress/e2e/user/webinterface.cy.ts +++ b/cypress/e2e/user/webinterface.cy.ts @@ -43,6 +43,14 @@ describe('Web interface', () => { docElementScreenshot('header#header', 'user/webinterface_nav') }) + it('Customize button', () => { + cy.login(user) + cy.visit('/apps/dashboard') + cy.contains('button', 'Customize', { timeout: 15000 }).should('exist').scrollIntoView() + cy.contains('button', 'Customize').should('be.visible') + docElementScreenshot('button:contains("Customize")', 'user/webinterface_customize_btn') + }) + it('Profile menu', () => { cy.login(user) cy.visit('/apps/dashboard') From 04d0030a8e726af3ef9cb553a1859a959abb219c Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 18:29:50 +0200 Subject: [PATCH 26/28] feat(screenshots): add unified search modal screenshot AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/webinterface.cy.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts index 8e9536b6620..6ae406b04a3 100644 --- a/cypress/e2e/user/webinterface.cy.ts +++ b/cypress/e2e/user/webinterface.cy.ts @@ -51,6 +51,16 @@ describe('Web interface', () => { docElementScreenshot('button:contains("Customize")', 'user/webinterface_customize_btn') }) + it('Unified search', () => { + cy.login(user) + cy.visit('/apps/dashboard') + cy.get('header#header').should('be.visible') + cy.get('#unified-search').click() + // Wait for the modal and its filter chips to appear + cy.get('[data-cy-unified-search-filters]', { timeout: 10000 }).should('be.visible') + docScreenshot('user/webinterface_search') + }) + it('Profile menu', () => { cy.login(user) cy.visit('/apps/dashboard') From af4e940dcc27d198640e99da7ad2788ef78633ee Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 18:47:42 +0200 Subject: [PATCH 27/28] feat(screenshots): drop tasks widget to expose Customize button in dashboard AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress/e2e/user/webinterface.cy.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/cypress/e2e/user/webinterface.cy.ts b/cypress/e2e/user/webinterface.cy.ts index 6ae406b04a3..bf5ac0eee57 100644 --- a/cypress/e2e/user/webinterface.cy.ts +++ b/cypress/e2e/user/webinterface.cy.ts @@ -11,7 +11,7 @@ before(() => { cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) // Enable dashboard widgets matching the tech-preview layout (mail/spreed omitted — not in server image) - cy.task('occ', { cmd: 'user:setting christine dashboard layout files-favorites,calendar,deck,notes,tasks' }) + cy.task('occ', { cmd: 'user:setting christine dashboard layout files-favorites,calendar,deck,notes' }) cy.task('occ', { cmd: 'user:setting christine dashboard firstRun 0' }) }) @@ -43,14 +43,6 @@ describe('Web interface', () => { docElementScreenshot('header#header', 'user/webinterface_nav') }) - it('Customize button', () => { - cy.login(user) - cy.visit('/apps/dashboard') - cy.contains('button', 'Customize', { timeout: 15000 }).should('exist').scrollIntoView() - cy.contains('button', 'Customize').should('be.visible') - docElementScreenshot('button:contains("Customize")', 'user/webinterface_customize_btn') - }) - it('Unified search', () => { cy.login(user) cy.visit('/apps/dashboard') From 96aafc56b9a21f3dae59ca36e7aef025cccba319 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 5 May 2026 20:14:37 +0200 Subject: [PATCH 28/28] feat(screenshots): add Talk specs and amara_w seed user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Cypress specs for three Talk RST pages, plus the 'spreed' app to SCREENSHOT_APPS so Talk is enabled in the screenshot container. All specs provision amara_w (Amara Winterbourne) as a second user and seed a realistic 1:1 conversation from tp.db room 112. - cypress/e2e/user/talk/conversations.cy.ts — 22/25 screenshots for conversations.rst (conversation list, 1:1 with sidebar, create flow, filters, group settings, participant/ban/expiration/notification dialogs, privacy settings, archive) - cypress/e2e/user/talk/chat.cy.ts — 7/9 screenshots for chat.rst (emoji picker, smart picker, reply hover, message menu, silent mode, schedule action, scheduled messages list; AI summary skipped) - cypress/e2e/user/talk/messages.cy.ts — 13/13 screenshots for messages.rst (edit, pin, reminder, search, thread create/view/list/ notifications/followed/title-edit) AI-Assisted-By: Claude Sonnet 4.6 Signed-off-by: Anna Larch --- cypress.config.ts | 1 + cypress/e2e/user/talk/chat.cy.ts | 151 ++++++++++ cypress/e2e/user/talk/conversations.cy.ts | 334 ++++++++++++++++++++++ cypress/e2e/user/talk/messages.cy.ts | 194 +++++++++++++ 4 files changed, 680 insertions(+) create mode 100644 cypress/e2e/user/talk/chat.cy.ts create mode 100644 cypress/e2e/user/talk/conversations.cy.ts create mode 100644 cypress/e2e/user/talk/messages.cy.ts diff --git a/cypress.config.ts b/cypress.config.ts index 4692a45d941..aa69a9f6250 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -30,6 +30,7 @@ const SCREENSHOT_APPS = [ 'files_versions', 'notes', 'notifications', + 'spreed', 'tasks', 'viewer', ] diff --git a/cypress/e2e/user/talk/chat.cy.ts b/cypress/e2e/user/talk/chat.cy.ts new file mode 100644 index 00000000000..0779a081616 --- /dev/null +++ b/cypress/e2e/user/talk/chat.cy.ts @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { User } from '@nextcloud/cypress' +import { docScreenshot, docElementScreenshot } from '../../helpers' + +const christine = new User('christine', 'christine') +const amara = new User('amara_w', 'amara_w') +const AVATAR_DIR = '/home/anna/Downloads/tp/avatar' + +function talkApi(method: string, path: string, user: User, body?: Record) { + const base = (Cypress.config('baseUrl') as string).replace('/index.php', '') + return cy.request({ + method, + url: `${base}/ocs/v2.php/apps/spreed/api${path}`, + auth: { user: user.userId, pass: user.password }, + headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : undefined, + failOnStatusCode: false, + }) +} + +function say(token: string, message: string, as: User) { + return talkApi('POST', `/v1/chat/${token}`, as, { message }) +} + +let dmToken: string + +before(() => { + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Amara Winterbourne" amara_w', env: { OC_PASS: 'amara_w' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/amara_w/avatar.png`, user: 'amara_w', password: 'amara_w' }) + + // Create 1:1 and seed conversation from tp.db room 112 + const base = (Cypress.config('baseUrl') as string).replace('/index.php', '') + cy.request({ + method: 'POST', + url: `${base}/ocs/v2.php/apps/spreed/api/v4/room`, + auth: { user: 'christine', pass: 'christine' }, + headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json' }, + body: JSON.stringify({ roomType: 1, invite: 'amara_w' }), + }).then((res) => { + dmToken = res.body.ocs.data.token + say(dmToken, 'Do you have minute?', amara) + say(dmToken, "Absolutely, what's up?", christine) + say(dmToken, "The client got back to me and they're considering to join the fundraising next Thursday if we can secure a round table for them. Can you help me secure it?", amara) + say(dmToken, 'Those are some great news! Have you already gotten in touch with Marlene from the venue to see if they can add a round table to the event?', christine) + say(dmToken, "Marlene from the venue just got back to me and she said it'd be tricky to get that table so close to the event's date. She said she'll try but maybe an escalation is needed.", amara) + say(dmToken, "OK, makes sense to me. I will contact them immediately to ensure that we can accommodate the client's wishes. Thank you for looping me in!", christine) + say(dmToken, 'Wonderful, thank you!', amara) + say(dmToken, 'Happy to help!', christine) + }) +}) + +describe('Documentation screenshots — Talk: Sending messages', { testIsolation: false }, () => { + beforeEach(() => { + cy.viewport(1440, 900) + }) + + it('Emoji picker', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, [data-cy-conversations-list-item], li', 'Amara Winterbourne', { timeout: 15000 }) + .click() + cy.get('.chat-view, .messages-list, [data-cy-message-list]', { timeout: 10000 }).should('be.visible') + // Open the emoji picker via the emoji button in the input bar + cy.get('[data-cy-emoji-picker] button, button[aria-label*="emoji" i], .emoji-picker-button').first().click() + cy.get('.emoji-picker, [data-cy-emoji-picker-content], .NcEmojiPicker', { timeout: 5000 }).should('be.visible') + docElementScreenshot( + '.chat-view, #app-content', + 'user/talk/emoji-picker', + ) + }) + + it('Smart picker', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Type / to open the smart picker + cy.get('[data-cy-message-input] input, .new-message-form__input, textarea[placeholder*="message" i]') + .first().click().type('/') + cy.get('.smart-picker, [data-cy-smart-picker], .NcActionInput', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/smart-picker') + }) + + it('Reply to message (hover state)', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Hover on a message to reveal the reply arrow + cy.get('.message, [data-cy-message], .chat-message').last().as('msg') + cy.get('@msg').trigger('mouseover') + cy.get('.message__buttons, [data-cy-message-actions]', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/reply') + }) + + it('Message action menu', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Hover on a message then click the ... button + cy.get('.message, [data-cy-message], .chat-message').last().as('msg') + cy.get('@msg').trigger('mouseover') + cy.get('@msg').find('button[aria-label*="actions" i], button[aria-label*="more" i], .action-item__menutoggle').first().click() + cy.get('[role="menu"], .action-item__menutoggle + ul', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/chat-message-menu') + }) + + it('Silent message toggle', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Open the send-options dropdown to reveal silent mode + cy.get('[data-cy-send-options], button[aria-label*="send options" i], .send-button-dropdown, .send-message__options') + .first().click() + cy.contains('button, [role="menuitem"]', /silent/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/message-silent') + }) + + it('Schedule message action', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Type something in the input so the schedule button activates + cy.get('[data-cy-message-input] input, .new-message-form__input, textarea[placeholder*="message" i]') + .first().type('Will do!') + cy.get('[data-cy-send-options], button[aria-label*="send options" i], .send-message__options, .send-button-dropdown') + .first().click() + cy.contains('[role="menuitem"], button', /schedule/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/message-schedule-action') + }) + + it('Scheduled messages list', () => { + // Schedule the message (click Schedule option, then confirm in dialog) + cy.contains('[role="menuitem"], button', /schedule/i).click() + cy.get('.modal-container, [data-cy-schedule-dialog]', { timeout: 5000 }).should('be.visible') + // Confirm with default time + cy.contains('button', /confirm|schedule|send/i).last().click() + // Open the scheduled messages panel via the clock icon + cy.get('button[aria-label*="scheduled" i], [data-cy-scheduled-messages], .scheduled-messages-button', { timeout: 5000 }) + .should('be.visible').click() + cy.get('.scheduled-messages-list, [data-cy-scheduled-messages-list]', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/message-schedule-toggle') + }) +}) diff --git a/cypress/e2e/user/talk/conversations.cy.ts b/cypress/e2e/user/talk/conversations.cy.ts new file mode 100644 index 00000000000..e1c22729922 --- /dev/null +++ b/cypress/e2e/user/talk/conversations.cy.ts @@ -0,0 +1,334 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { User } from '@nextcloud/cypress' +import { docScreenshot, docElementScreenshot } from '../../helpers' + +const christine = new User('christine', 'christine') +const amara = new User('amara_w', 'amara_w') +const AVATAR_DIR = '/home/anna/Downloads/tp/avatar' + +// ── Talk OCS helpers ────────────────────────────────────────────────────────── + +function talkApi(method: string, path: string, user: User, body?: Record) { + const base = (Cypress.config('baseUrl') as string).replace('/index.php', '') + return cy.request({ + method, + url: `${base}/ocs/v2.php/apps/spreed/api${path}`, + auth: { user: user.userId, pass: user.password }, + headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : undefined, + failOnStatusCode: false, + }) +} + +function createDm(invitee: string, as: User): Cypress.Chainable { + return talkApi('POST', '/v4/room', as, { roomType: 1, invite: invitee }) + .its('body.ocs.data.token') +} + +function createGroup(name: string, as: User): Cypress.Chainable { + return talkApi('POST', '/v4/room', as, { roomType: 2, roomName: name }) + .its('body.ocs.data.token') +} + +function addParticipant(token: string, uid: string, as: User) { + return talkApi('POST', `/v4/room/${token}/participants`, as, { newParticipant: uid, source: 'users' }) +} + +function say(token: string, message: string, as: User) { + return talkApi('POST', `/v1/chat/${token}`, as, { message }) +} + +// ── Provisioning ────────────────────────────────────────────────────────────── + +before(() => { + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Amara Winterbourne" amara_w', env: { OC_PASS: 'amara_w' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/amara_w/avatar.png`, user: 'amara_w', password: 'amara_w' }) + + // 1:1 conversation with realistic messages (from tp.db room 112) + createDm('amara_w', christine).then((dmToken) => { + say(dmToken, 'Do you have minute?', amara) + say(dmToken, "Absolutely, what's up?", christine) + say(dmToken, "The client got back to me and they're considering to join the fundraising next Thursday if we can secure a round table for them. Can you help me secure it?", amara) + say(dmToken, 'Those are some great news! Have you already gotten in touch with Marlene from the venue to see if they can add a round table to the event?', christine) + say(dmToken, "Marlene from the venue just got back to me and she said it'd be tricky to get that table so close to the event's date. She said she'll try but maybe an escalation is needed.", amara) + say(dmToken, "OK, makes sense to me. I will contact them immediately to ensure that we can accommodate the client's wishes. Thank you for looping me in!", christine) + say(dmToken, 'Wonderful, thank you!', amara) + say(dmToken, 'Happy to help!', christine) + }) + + // Group conversation for moderator/settings screenshots + createGroup('Event planning', christine).then((groupToken) => { + addParticipant(groupToken, 'amara_w', christine) + say(groupToken, "Hi team! I've set up this conversation for coordinating the Q3 fundraising event.", christine) + say(groupToken, 'Great, thanks for setting this up! I have a few updates to share.', amara) + say(groupToken, "Looking forward to hearing them. Let's get started!", christine) + // Store token for later tests + cy.wrap(groupToken).as('groupToken') + }) +}) + +// ── Screenshots ─────────────────────────────────────────────────────────────── + +describe('Documentation screenshots — Talk: Conversations', { testIsolation: false }, () => { + beforeEach(() => { + cy.viewport(1440, 900) + }) + + it('Talk dashboard (conversation list)', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.get('.conversations-list, [data-cy-conversations-list], #app-content-wrapper', { timeout: 15000 }).should('be.visible') + cy.get('.icon-loading, .loading', { timeout: 10000 }).should('not.exist') + docScreenshot('user/talk/talk-dashboard') + }) + + it('Note to self', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, [data-cy-conversations-list-item], li', 'Note to self', { timeout: 15000 }) + .click() + cy.get('.chat-view, .messages-list, [data-cy-message-list]', { timeout: 10000 }).should('be.visible') + docElementScreenshot( + '.app-content, #app-content', + 'user/talk/note-to-self', + ) + }) + + it('1:1 conversation with right sidebar', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, [data-cy-conversations-list-item], li', 'Amara Winterbourne', { timeout: 15000 }) + .click() + cy.get('.chat-view, .messages-list, [data-cy-message-list]', { timeout: 10000 }).should('be.visible') + // Open the right sidebar + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i], button[aria-label*="sidebar" i]').first().click() + cy.get('.app-sidebar, [data-cy-app-sidebar]', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.app-sidebar, [data-cy-app-sidebar]', 'user/talk/one-to-one-right-sidebar') + }) + + it('1:1 extend to group', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, [data-cy-conversations-list-item], li', 'Amara Winterbourne', { timeout: 15000 }) + .click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Open sidebar if not already open + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i]').first().click() + cy.get('.app-sidebar', { timeout: 5000 }).should('be.visible') + cy.contains('button, .action-button', /add participants|extend|invite/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.app-sidebar', 'user/talk/one-to-one-extend') + }) + + it('Create new conversation button', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.get('.conversations-list, [data-cy-conversations-list]', { timeout: 15000 }).should('be.visible') + // Click the + / new-conversation button + cy.get('[data-cy-new-conversation], button[aria-label*="new conversation" i], .new-button') + .first().click() + cy.contains('Create a new conversation', { timeout: 5000 }).should('be.visible') + docElementScreenshot( + '[data-cy-search], .new-conversation-container, #app-navigation-vue', + 'user/talk/create-new-conversation', + ) + }) + + it('Creating open conversation (step 1: name + settings)', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.get('[data-cy-new-conversation], button[aria-label*="new conversation" i], .new-button') + .first().click() + cy.contains('Create a new conversation').click() + cy.get('[data-cy-conversation-name], input[placeholder*="name" i], input[id*="name" i]', { timeout: 5000 }) + .should('be.visible') + .type('Product team') + docScreenshot('user/talk/creating-open-conversation') + }) + + it('Add participants (step 2)', () => { + // Continuation of previous test — dialog should still be open + cy.contains('button', /next|add participants/i).click() + cy.get('[data-cy-participants-search], input[placeholder*="participant" i], input[placeholder*="user" i]', { timeout: 5000 }) + .should('be.visible') + .type('Amara') + cy.contains('.participant-row, .suggestion, li', 'Amara Winterbourne', { timeout: 5000 }).click() + docScreenshot('user/talk/add-participants') + }) + + it('New room (freshly created conversation)', () => { + cy.contains('button', /create|done|finish/i).click() + cy.get('.chat-view, .messages-list', { timeout: 15000 }).should('be.visible') + cy.get('.icon-loading', { timeout: 10000 }).should('not.exist') + docScreenshot('user/talk/new-room') + }) + + it('Filters menu', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.get('.conversations-list', { timeout: 15000 }).should('be.visible') + cy.get('[data-cy-filter-button], button[aria-label*="filter" i], .filter-button').first().click() + cy.get('[data-cy-filter-menu], .filter-menu, .action-item__menutoggle + ul', { timeout: 5000 }).should('be.visible') + docElementScreenshot( + '[data-cy-filter-menu], .filter-menu, #app-navigation-vue', + 'user/talk/filters-menu', + ) + }) + + it('Clear filter', () => { + // Apply unread filter and show clear button + cy.contains('[role="menuitem"], button, li', /unread messages/i).click() + cy.get('[data-cy-clear-filter], button[aria-label*="clear" i], .clear-filter', { timeout: 5000 }).should('be.visible') + docElementScreenshot('#app-navigation-vue', 'user/talk/clear-filter') + }) + + it('Group public settings', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Event planning', { timeout: 15000 }).click() + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i]').first().click() + cy.get('.app-sidebar', { timeout: 5000 }).should('be.visible') + cy.contains('[role="tab"], .tab-item, button', /participants|settings/i).click() + docElementScreenshot('.app-sidebar', 'user/talk/group-public-settings') + }) + + it('Participant menu (... on participant)', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Event planning', { timeout: 15000 }).click() + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i]').first().click() + cy.get('.app-sidebar', { timeout: 5000 }).should('be.visible') + cy.contains('[role="tab"], button', /participants/i).click() + // Hover over Amara's participant row to reveal the ... button + cy.contains('.participant-row, li', 'Amara Winterbourne').as('participantRow') + cy.get('@participantRow').trigger('mouseover') + cy.get('@participantRow').find('button[aria-label*="actions" i], .action-item, [data-cy-participant-action]').first().click() + cy.get('.dropdown-item, .action-item__menutoggle + ul, [role="menu"]', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.app-sidebar', 'user/talk/participant-menu') + }) + + it('Open conversation settings menu', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Event planning', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Open the ... menu on the conversation header + cy.get('[data-cy-conversation-header] button[aria-label*="actions" i], .conversation-header__actions button, .top-bar button[aria-haspopup]') + .first().click() + cy.get('[role="menu"], .action-item__menutoggle + ul', { timeout: 5000 }).should('be.visible') + docElementScreenshot( + '#app-content', + 'user/talk/open-settings', + ) + }) + + it('Conversation settings dialog', () => { + cy.contains('[role="menuitem"], button, li', /conversation settings/i).click() + cy.get('[data-cy-conversation-settings], .conversation-settings, .modal-container', { timeout: 10000 }).should('be.visible') + docElementScreenshot( + '[data-cy-conversation-settings], .conversation-settings, .modal-container', + 'user/talk/conversation-settings-dialog', + ) + }) + + it('Message expiration setting', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Event planning', { timeout: 15000 }).click() + cy.get('[data-cy-conversation-header] button[aria-label*="actions" i], .conversation-header__actions button, .top-bar button[aria-haspopup]') + .first().click() + cy.contains('[role="menuitem"], li', /conversation settings/i).click() + cy.get('[data-cy-conversation-settings], .conversation-settings, .modal-container', { timeout: 10000 }).should('be.visible') + cy.contains('[role="tab"], button', /moderation/i).click() + cy.contains('label, .setting-label, div', /message expiration/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot( + '[data-cy-conversation-settings], .conversation-settings, .modal-container', + 'user/talk/messages-expiration', + ) + }) + + it('Ban participant', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Event planning', { timeout: 15000 }).click() + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i]').first().click() + cy.get('.app-sidebar', { timeout: 5000 }).should('be.visible') + cy.contains('[role="tab"], button', /participants/i).click() + cy.contains('.participant-row, li', 'Amara Winterbourne').as('row') + cy.get('@row').trigger('mouseover') + cy.get('@row').find('button[aria-label*="actions" i], .action-item').first().click() + cy.contains('[role="menuitem"], button', /remove participant/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.app-sidebar', 'user/talk/ban-participant') + }) + + it('Ban participant dialog', () => { + cy.contains('[role="menuitem"], button', /remove participant/i).click() + cy.get('[data-cy-ban-dialog], .modal-container', { timeout: 5000 }).should('be.visible') + cy.contains('label, span', /ban/i).parent().find('input[type="checkbox"]').check() + docElementScreenshot( + '[data-cy-ban-dialog], .modal-container', + 'user/talk/ban-participant-dialog', + ) + }) + + it('Ban participant list', () => { + // Dismiss the dialog without banning so Amara stays in the room + cy.contains('button', /cancel/i).click() + // Open conversation settings → Moderation → Banned users + cy.get('[data-cy-conversation-header] button[aria-label*="actions" i], .top-bar button[aria-haspopup]') + .first().click() + cy.contains('[role="menuitem"], li', /conversation settings/i).click() + cy.get('[data-cy-conversation-settings], .modal-container', { timeout: 10000 }).should('be.visible') + cy.contains('[role="tab"], button', /moderation/i).click() + cy.contains('button, h3, .section-title', /banned/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot( + '[data-cy-conversation-settings], .modal-container', + 'user/talk/ban-participant-list', + ) + }) + + it('Conversation notifications setting', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('[data-cy-conversation-header] button[aria-label*="actions" i], .top-bar button[aria-haspopup]') + .first().click() + cy.contains('[role="menuitem"], li', /notification|settings/i).click() + cy.get('[data-cy-notification-settings], .modal-container, .notification-settings', { timeout: 10000 }).should('be.visible') + docElementScreenshot( + '[data-cy-notification-settings], .modal-container', + 'user/talk/conversation-notifications', + ) + }) + + it('Privacy settings (Talk personal settings)', () => { + cy.login(christine) + cy.visit('/settings/user/talk') + cy.get('.app-settings-content, [data-cy-talk-settings], .personal-settings', { timeout: 15000 }).should('be.visible') + cy.contains('h2, h3, .settings-section__name', /privacy|read marker|typing/i, { timeout: 5000 }).should('be.visible') + docScreenshot('user/talk/privacy-settings') + }) + + it('Archived conversations button', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.get('.conversations-list', { timeout: 15000 }).should('be.visible') + // Archive the group conversation + cy.contains('.conversations-list__item, li', 'Event planning').rightclick() + cy.contains('[role="menuitem"], button', /archive/i, { timeout: 5000 }).click() + // Show the archive button at the bottom of the nav + cy.get('[data-cy-archived-button], button[aria-label*="archived" i], .archived-conversations-button', { timeout: 5000 }) + .should('be.visible') + docElementScreenshot('#app-navigation-vue', 'user/talk/archived-conversations-button') + }) + + it('Archived conversations list', () => { + cy.get('[data-cy-archived-button], button[aria-label*="archived" i], .archived-conversations-button') + .click() + cy.contains('.conversations-list__item, li', 'Event planning', { timeout: 5000 }).should('be.visible') + docElementScreenshot('#app-navigation-vue', 'user/talk/archived-conversations-list') + }) +}) diff --git a/cypress/e2e/user/talk/messages.cy.ts b/cypress/e2e/user/talk/messages.cy.ts new file mode 100644 index 00000000000..6ae10bd2700 --- /dev/null +++ b/cypress/e2e/user/talk/messages.cy.ts @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { User } from '@nextcloud/cypress' +import { docScreenshot, docElementScreenshot } from '../../helpers' + +const christine = new User('christine', 'christine') +const amara = new User('amara_w', 'amara_w') +const AVATAR_DIR = '/home/anna/Downloads/tp/avatar' + +function talkApi(method: string, path: string, user: User, body?: Record) { + const base = (Cypress.config('baseUrl') as string).replace('/index.php', '') + return cy.request({ + method, + url: `${base}/ocs/v2.php/apps/spreed/api${path}`, + auth: { user: user.userId, pass: user.password }, + headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : undefined, + failOnStatusCode: false, + }) +} + +function say(token: string, message: string, as: User) { + return talkApi('POST', `/v1/chat/${token}`, as, { message }) +} + +before(() => { + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Christine" christine', env: { OC_PASS: 'christine' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/christine/avatar.png`, user: 'christine', password: 'christine' }) + cy.task('occ', { cmd: 'user:add --password-from-env --display-name="Amara Winterbourne" amara_w', env: { OC_PASS: 'amara_w' } }) + cy.task('uploadAvatar', { src: `${AVATAR_DIR}/amara_w/avatar.png`, user: 'amara_w', password: 'amara_w' }) + + // Create 1:1 and seed conversation from tp.db room 112 + const base = (Cypress.config('baseUrl') as string).replace('/index.php', '') + cy.request({ + method: 'POST', + url: `${base}/ocs/v2.php/apps/spreed/api/v4/room`, + auth: { user: 'christine', pass: 'christine' }, + headers: { 'OCS-APIRequest': 'true', 'Content-Type': 'application/json' }, + body: JSON.stringify({ roomType: 1, invite: 'amara_w' }), + }).then((res) => { + const token = res.body.ocs.data.token + say(token, 'Do you have minute?', amara) + say(token, "Absolutely, what's up?", christine) + say(token, "The client got back to me and they're considering to join the fundraising next Thursday if we can secure a round table for them. Can you help me secure it?", amara) + say(token, 'Those are some great news! Have you already gotten in touch with Marlene from the venue to see if they can add a round table to the event?', christine) + say(token, "Marlene from the venue just got back to me and she said it'd be tricky to get that table so close to the event's date. She said she'll try but maybe an escalation is needed.", amara) + say(token, "OK, makes sense to me. I will contact them immediately to ensure that we can accommodate the client's wishes. Thank you for looping me in!", christine) + say(token, 'Wonderful, thank you!', amara) + say(token, 'Happy to help!', christine) + }) +}) + +describe('Documentation screenshots — Talk: Interacting with messages', { testIsolation: false }, () => { + beforeEach(() => { + cy.viewport(1440, 900) + }) + + it('Message editing', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Hover on one of Christine's own messages and open ... menu → Edit + cy.contains('.message, [data-cy-message]', 'Happy to help').last().as('msg') + cy.get('@msg').trigger('mouseover') + cy.get('@msg').find('button[aria-label*="actions" i], button[aria-label*="more" i], .action-item__menutoggle').first().click() + cy.contains('[role="menuitem"], button', /edit/i, { timeout: 5000 }).click() + cy.get('[data-cy-message-input] input, .edit-message-form__input, .message-input textarea', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/message-editing') + }) + + it('Pin message action', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + cy.get('.message, [data-cy-message], .chat-message').last().as('msg') + cy.get('@msg').trigger('mouseover') + cy.get('@msg').find('button[aria-label*="actions" i], .action-item__menutoggle').first().click() + cy.contains('[role="menuitem"], button', /pin/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/message-pin-action') + }) + + it('Pinned message in chat', () => { + // Pin the message + cy.contains('[role="menuitem"], button', /pin/i).click() + cy.get('.pinned-messages-bar, [data-cy-pinned-messages], .message--pinned', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/message-pin-in-chat') + }) + + it('Set message reminder', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + cy.get('.message, [data-cy-message], .chat-message').last().as('msg') + cy.get('@msg').trigger('mouseover') + // Click the reminder / bell icon in quick actions + cy.get('@msg').find('button[aria-label*="reminder" i], button[aria-label*="remind" i], .message-reminder-button').first().click() + cy.get('.reminder-picker, [data-cy-reminder-submenu], .submenu', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/set-message-reminder') + }) + + it('Configure message reminder (submenu)', () => { + // The submenu should still be visible from the previous test + cy.contains('[role="menuitem"], button, li', /later today|this evening|tomorrow|custom/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/configure-message-reminder') + }) + + it('Search messages in conversation', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Open sidebar and switch to search tab + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i]').first().click() + cy.get('.app-sidebar', { timeout: 5000 }).should('be.visible') + cy.get('[data-cy-sidebar-search], button[aria-label*="search" i], .search-messages-button').first().click() + cy.get('[data-cy-search-input], .sidebar-search input, input[placeholder*="search" i]', { timeout: 5000 }).type('Marlene') + docElementScreenshot('.app-sidebar', 'user/talk/chat-search-messages') + }) + + it('Search messages results tab', () => { + cy.get('[data-cy-search-results], .search-results, .messages-search-results', { timeout: 10000 }).should('be.visible') + docElementScreenshot('.app-sidebar', 'user/talk/chat-search-messages-tab') + }) + + it('Create thread action', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + cy.get('.message, [data-cy-message], .chat-message').last().as('msg') + cy.get('@msg').trigger('mouseover') + cy.get('@msg').find('button[aria-label*="actions" i], .action-item__menutoggle').first().click() + cy.contains('[role="menuitem"], button', /thread/i, { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/thread-create-action') + }) + + it('Thread example (open thread)', () => { + cy.contains('[role="menuitem"], button', /thread/i).click() + cy.get('.thread-view, [data-cy-thread], .message-thread', { timeout: 10000 }).should('be.visible') + // Add a reply in the thread + cy.get('[data-cy-thread-input] input, .thread-input textarea, [placeholder*="reply" i]') + .first().type("I'll loop in the venue coordinator right away.{enter}") + cy.get('.thread-message, [data-cy-thread-message]', { timeout: 5000 }).should('have.length.gte', 1) + docElementScreenshot('.chat-view, #app-content', 'user/talk/thread-example') + }) + + it('Threads list in shared items', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('[data-cy-sidebar-toggle], button[aria-label*="details" i]').first().click() + cy.get('.app-sidebar', { timeout: 5000 }).should('be.visible') + cy.contains('[role="tab"], button', /shared items/i).click() + cy.contains('[role="tab"], button', /threads/i).click() + cy.get('.thread-list, [data-cy-threads-list]', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.app-sidebar', 'user/talk/threads-list-shared-items') + }) + + it('Thread notifications (subscribe)', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('.chat-view, .messages-list', { timeout: 10000 }).should('be.visible') + // Find a message with a thread reply badge and open it + cy.get('[data-cy-thread-reply-button], .message__thread-replies, .thread-indicator').first().click() + cy.get('.thread-view, [data-cy-thread]', { timeout: 5000 }).should('be.visible') + cy.get('[data-cy-thread-subscribe], button[aria-label*="subscribe" i], .thread-subscribe-button', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/thread-notifications') + }) + + it('Followed threads navigation', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.get('[data-cy-nav-threads], a[href*="threads"], .navigation-list__item--threads', { timeout: 10000 }).should('be.visible').click() + cy.get('.threads-list, [data-cy-followed-threads]', { timeout: 10000 }).should('be.visible') + docScreenshot('user/talk/threads-followed') + }) + + it('Thread edit title', () => { + cy.login(christine) + cy.visit('/apps/spreed') + cy.contains('.conversations-list__item, li', 'Amara Winterbourne', { timeout: 15000 }).click() + cy.get('[data-cy-thread-reply-button], .message__thread-replies, .thread-indicator').first().click() + cy.get('.thread-view, [data-cy-thread]', { timeout: 5000 }).should('be.visible') + cy.get('[data-cy-thread-title] button[aria-label*="edit" i], .thread-header__edit, button[aria-label*="edit title" i]') + .first().click() + cy.get('[data-cy-thread-title-input], .thread-title-edit input', { timeout: 5000 }).should('be.visible') + docElementScreenshot('.chat-view, #app-content', 'user/talk/thread-edit-title') + }) +})