diff --git a/.changeset/config.json b/.changeset/config.json index 69d633be..766f212b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -9,6 +9,5 @@ "linked": [], "access": "public", "baseBranch": "develop", - "updateInternalDependencies": "patch", - "ignore": [] + "updateInternalDependencies": "patch" } diff --git a/.changeset/gold-kings-watch.md b/.changeset/gold-kings-watch.md new file mode 100644 index 00000000..22cf81be --- /dev/null +++ b/.changeset/gold-kings-watch.md @@ -0,0 +1,10 @@ +--- +"@bluecadet/launchpad-dashboard": major +"@bluecadet/launchpad": major +"@bluecadet/launchpad-scaffold": major +"@bluecadet/launchpad-content": major +"@bluecadet/launchpad-monitor": major +"@bluecadet/launchpad-utils": major +--- + +Add Plugin API diff --git a/.docs/generate.mjs b/.docs/generate.mjs index 145aadd5..c7ed57ba 100644 --- a/.docs/generate.mjs +++ b/.docs/generate.mjs @@ -1,5 +1,5 @@ import jsdoc2md from 'jsdoc-to-markdown'; -import fs from 'fs-extra'; +import fse from 'fs-extra/esm'; import path from 'path'; import chalk from 'chalk'; import { loadConfigFromFile, LogManager } from '@bluecadet/launchpad-utils'; @@ -17,12 +17,12 @@ const logger = LogManager.getInstance(config).getLogger('docs'); */ const renderDocs = async (options) => { if (options.templatePath) { - options.template = fs.readFileSync(options.templatePath).toString(); + options.template = fse.readFileSync(options.templatePath).toString(); } logger.info(`Rendering ${chalk.yellow(options.outputPath)}`); return await jsdoc2md.render(options).then((result) => { - fs.ensureDirSync(path.dirname(options.outputPath)); - fs.writeFileSync(options.outputPath, result); + fse.ensureDirSync(path.dirname(options.outputPath)); + fse.writeFileSync(options.outputPath, result); }); } diff --git a/nodemon.json b/nodemon.json deleted file mode 100644 index efb708f7..00000000 --- a/nodemon.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "watch": ["packages/", ".docs"], - "ext": "js,mjs,json,hbs", - "ignore": [ - ".git", - "node_modules", - "./docs/", - "./packages/**/docs/*" - ], - "exec": "npm run docs" -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index eab4aec7..48e32d5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,9 @@ "jsdoc-to-markdown": "^7.1.1", "nodemon": "^2.0.20", "typescript": "^5.1.3" + }, + "engines": { + "node": ">=18" } }, "node_modules/@babel/helper-string-parser": { @@ -101,10 +104,49 @@ "resolved": "packages/scaffold", "link": true }, + "node_modules/@bluecadet/launchpad-testing": { + "resolved": "packages/testing", + "link": true + }, + "node_modules/@bluecadet/launchpad-tsconfig": { + "resolved": "packages/tsconfig", + "link": true + }, "node_modules/@bluecadet/launchpad-utils": { "resolved": "packages/utils", "link": true }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", + "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.5.0" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, "node_modules/@changesets/apply-release-plan": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.5.tgz", @@ -436,6 +478,7 @@ "version": "16.8.5", "resolved": "https://registry.npmjs.org/@contentful/rich-text-types/-/rich-text-types-16.8.5.tgz", "integrity": "sha512-q18RJuJCOuYveGiCIjE5xLCQc5lZ3L2Qgxrlg/H2YEobDFqdtmklazRi1XwEWaK3tMg6yVXBzKKkQfLB4qW14A==", + "optional": true, "engines": { "node": ">=6.0.0" } @@ -450,6 +493,23 @@ "kuler": "^2.0.0" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -571,6 +631,208 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@inquirer/confirm": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.1.tgz", + "integrity": "sha512-6ycMm7k7NUApiMGfVc32yIPp28iPKxhGRMqoNDiUjq2RyTAkbs5Fx0TdzBqhabcKvniDdAAvHCmsRjnNfTsogw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.0.1", + "@inquirer/type": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.0.1.tgz", + "integrity": "sha512-KKTgjViBQUi3AAssqjUFMnMO3CM3qwCHvePV9EW+zTKGKafFGFF01sc1yOIYjLJ7QU52G/FbzKc+c01WLzXmVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.7", + "@inquirer/type": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/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/@inquirer/figures": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.7.tgz", + "integrity": "sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.0.tgz", + "integrity": "sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@jsdoc/salty": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", @@ -583,6 +845,30 @@ "node": ">=v12.0.0" } }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.2.1.tgz", + "integrity": "sha512-gaHqbubTi29aZpVbBlECRpmdia+L5/lh2BwtIJTmtxdbecEyyX/ejAOg7eQDGNvGOUmPY7Z2Yxdy9ioyH/VJeA==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.3.tgz", + "integrity": "sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -649,6 +935,24 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.36.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.36.6.tgz", + "integrity": "sha512-issnYydStyH0wPEeU7CMwfO7kI668ffVtzKRMRS7H7BliOYuPuwEZxh9dwiXV+oeHBxT5SXT0wPwV8T7V2PJUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -684,6 +988,31 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@pm2/agent": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.4.tgz", @@ -888,77 +1217,294 @@ "node": "^14.13.1 || >=16.0.0 || >=18.0.0" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], "dev": true, - "peer": true + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sanity/block-content-to-hyperscript": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@sanity/block-content-to-hyperscript/-/block-content-to-hyperscript-2.0.10.tgz", - "integrity": "sha512-xT3iEmZkK0fvO5PDFpn9GMWGfvOopvbrRCBU48XxpFoTxRrfsHhxbRy8J0eND1HGXHUENkIKv5jbohtGd1MiVg==", - "dependencies": { - "@sanity/generate-help-url": "^0.140.0", - "@sanity/image-url": "^0.140.15", - "hyperscript": "^2.0.2", - "object-assign": "^4.1.1" - } + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@sanity/block-content-to-hyperscript/node_modules/@sanity/image-url": { - "version": "0.140.22", - "resolved": "https://registry.npmjs.org/@sanity/image-url/-/image-url-0.140.22.tgz", - "integrity": "sha512-CAmQZnj+KM7FSEYiWlIGDit072syicYuAw0w7R2ctMzHiZ4p9mE/g6dBnYqrqFUrw2J+GpJgPt+RVspKP8vdqA==", - "engines": { - "node": ">=10.0.0" - } + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@sanity/block-content-to-markdown": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@sanity/block-content-to-markdown/-/block-content-to-markdown-0.0.5.tgz", - "integrity": "sha512-wnBfusG67TU2+MXcDSNDtEdE5MMYXQ9t5hHtU+0dQ2MUHPbF6FfX0nfX0IvjfNWGpMEniCOtmJR0k71maO9zYA==", - "dependencies": { - "@sanity/block-content-to-hyperscript": "^2.0.5" - } + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@sanity/client": { - "version": "6.22.1", - "resolved": "https://registry.npmjs.org/@sanity/client/-/client-6.22.1.tgz", - "integrity": "sha512-2F/byHTbA91iS2YuJA/LmuJbmh4prqMYqhMSQ2PGFBPU2z/trBOByuuYrHAQfjmETu95n1N24t5A8XjqzjQZTQ==", - "dependencies": { - "@sanity/eventsource": "^5.0.2", - "get-it": "^8.6.5", - "rxjs": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - } + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sanity/eventsource": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-5.0.2.tgz", - "integrity": "sha512-/B9PMkUvAlUrpRq0y+NzXgRv5lYCLxZNsBJD2WXVnqZYOfByL9oQBV7KiTaARuObp5hcQYuPfOAVjgXe3hrixA==", - "dependencies": { - "@types/event-source-polyfill": "1.0.5", - "@types/eventsource": "1.1.15", - "event-source-polyfill": "1.0.31", - "eventsource": "2.0.2" - } + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sanity/generate-help-url": { - "version": "0.140.0", - "resolved": "https://registry.npmjs.org/@sanity/generate-help-url/-/generate-help-url-0.140.0.tgz", - "integrity": "sha512-H/G/WA9S22TXcXST52CIiTsHx3S2hH0gvK7LnI5w76vfKS0obnDPh8jrPg4xeNRYGPuV9MHYRlyERGpRGoo4Qw==" + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@sanity/image-url": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@sanity/image-url/-/image-url-1.0.2.tgz", - "integrity": "sha512-C4+jb2ny3ZbMgEkLd7Z3C75DsxcTEoE+axXQJsQ75ou0AKWGdVsP351hqK6mJUUxn5HCSlu3vznoh7Yljye4cQ==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz", + "integrity": "sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "peer": true + }, + "node_modules/@sanity/block-content-to-hyperscript": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@sanity/block-content-to-hyperscript/-/block-content-to-hyperscript-2.0.10.tgz", + "integrity": "sha512-xT3iEmZkK0fvO5PDFpn9GMWGfvOopvbrRCBU48XxpFoTxRrfsHhxbRy8J0eND1HGXHUENkIKv5jbohtGd1MiVg==", + "dependencies": { + "@sanity/generate-help-url": "^0.140.0", + "@sanity/image-url": "^0.140.15", + "hyperscript": "^2.0.2", + "object-assign": "^4.1.1" + } + }, + "node_modules/@sanity/block-content-to-hyperscript/node_modules/@sanity/image-url": { + "version": "0.140.22", + "resolved": "https://registry.npmjs.org/@sanity/image-url/-/image-url-0.140.22.tgz", + "integrity": "sha512-CAmQZnj+KM7FSEYiWlIGDit072syicYuAw0w7R2ctMzHiZ4p9mE/g6dBnYqrqFUrw2J+GpJgPt+RVspKP8vdqA==", "engines": { "node": ">=10.0.0" } }, + "node_modules/@sanity/block-content-to-markdown": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@sanity/block-content-to-markdown/-/block-content-to-markdown-0.0.5.tgz", + "integrity": "sha512-wnBfusG67TU2+MXcDSNDtEdE5MMYXQ9t5hHtU+0dQ2MUHPbF6FfX0nfX0IvjfNWGpMEniCOtmJR0k71maO9zYA==", + "dependencies": { + "@sanity/block-content-to-hyperscript": "^2.0.5" + } + }, + "node_modules/@sanity/client": { + "version": "6.22.1", + "resolved": "https://registry.npmjs.org/@sanity/client/-/client-6.22.1.tgz", + "integrity": "sha512-2F/byHTbA91iS2YuJA/LmuJbmh4prqMYqhMSQ2PGFBPU2z/trBOByuuYrHAQfjmETu95n1N24t5A8XjqzjQZTQ==", + "optional": true, + "dependencies": { + "@sanity/eventsource": "^5.0.2", + "get-it": "^8.6.5", + "rxjs": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sanity/eventsource": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-5.0.2.tgz", + "integrity": "sha512-/B9PMkUvAlUrpRq0y+NzXgRv5lYCLxZNsBJD2WXVnqZYOfByL9oQBV7KiTaARuObp5hcQYuPfOAVjgXe3hrixA==", + "optional": true, + "dependencies": { + "@types/event-source-polyfill": "1.0.5", + "@types/eventsource": "1.1.15", + "event-source-polyfill": "1.0.31", + "eventsource": "2.0.2" + } + }, + "node_modules/@sanity/generate-help-url": { + "version": "0.140.0", + "resolved": "https://registry.npmjs.org/@sanity/generate-help-url/-/generate-help-url-0.140.0.tgz", + "integrity": "sha512-H/G/WA9S22TXcXST52CIiTsHx3S2hH0gvK7LnI5w76vfKS0obnDPh8jrPg4xeNRYGPuV9MHYRlyERGpRGoo4Qw==" + }, "node_modules/@sindresorhus/slugify": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", @@ -1008,14 +1554,12 @@ "@types/node": "*" } }, - "node_modules/@types/cli-progress": { - "version": "3.11.6", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", - "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", "dev": true, - "dependencies": { - "@types/node": "*" - } + "license": "MIT" }, "node_modules/@types/cross-spawn": { "version": "6.0.6", @@ -1026,34 +1570,34 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/event-source-polyfill": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/event-source-polyfill/-/event-source-polyfill-1.0.5.tgz", - "integrity": "sha512-iaiDuDI2aIFft7XkcwMzDWLqo7LVDixd2sR6B4wxJut9xcp/Ev9bO4EFg4rm6S9QxATLBj5OPxdeocgmhjwKaw==" + "integrity": "sha512-iaiDuDI2aIFft7XkcwMzDWLqo7LVDixd2sR6B4wxJut9xcp/Ev9bO4EFg4rm6S9QxATLBj5OPxdeocgmhjwKaw==", + "optional": true }, "node_modules/@types/eventsource": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@types/eventsource/-/eventsource-1.1.15.tgz", - "integrity": "sha512-XQmGcbnxUNa06HR3VBVkc9+A2Vpi9ZyLJcdS5dwaQQ/4ZMWFO+5c90FnMUpbtMZwB/FChoYHwuVg8TvkECacTA==" + "integrity": "sha512-XQmGcbnxUNa06HR3VBVkc9+A2Vpi9ZyLJcdS5dwaQQ/4ZMWFO+5c90FnMUpbtMZwB/FChoYHwuVg8TvkECacTA==", + "optional": true }, "node_modules/@types/follow-redirects": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/@types/follow-redirects/-/follow-redirects-1.14.4.tgz", "integrity": "sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA==", + "optional": true, "dependencies": { "@types/node": "*" } }, - "node_modules/@types/fs-extra": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", - "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", - "dev": true, - "dependencies": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1061,21 +1605,6 @@ "dev": true, "peer": true }, - "node_modules/@types/jsonfile": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", - "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/jsonpath": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", - "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", - "dev": true - }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1102,6 +1631,7 @@ "version": "20.16.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", + "devOptional": true, "dependencies": { "undici-types": "~6.19.2" } @@ -1110,6 +1640,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/progress-stream/-/progress-stream-2.0.5.tgz", "integrity": "sha512-5YNriuEZkHlFHHepLIaxzq3atGeav1qCTGzB74HKWpo66qjfostF+rHc785YYYHeBytve8ZG3ejg42jEIfXNiQ==", + "devOptional": true, "dependencies": { "@types/node": "*" } @@ -1120,16 +1651,6 @@ "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", "dev": true }, - "node_modules/@types/rimraf": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-4.0.5.tgz", - "integrity": "sha512-DTCZoIQotB2SUJnYgrEx43cQIUYOlNZz0AZPbKU4PSLYTUdML5Gox0++z4F9kQocxStrCmRNhi4x5x/UlwtKUA==", - "deprecated": "This is a stub types definition. rimraf provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "rimraf": "*" - } - }, "node_modules/@types/sanitize-html": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.13.0.tgz", @@ -1154,12 +1675,26 @@ "@types/node": "*" } }, + "node_modules/@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tail": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@types/tail/-/tail-2.2.3.tgz", "integrity": "sha512-Hnf352egOlDR4nVTaGX0t/kmTNXHMdovF2C7PVDFtHTHJPFmIspOI1b86vEOxU7SfCq/dADS7ptbqgG/WGGxnA==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -1186,6 +1721,120 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vitest/expect": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", + "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.3", + "@vitest/utils": "2.1.3", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", + "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.3", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.11" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/spy": "2.1.3", + "msw": "^2.3.5", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", + "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", + "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.3", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", + "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.3", + "magic-string": "^0.30.11", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", + "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", + "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1200,7 +1849,8 @@ "node_modules/abortcontroller-polyfill": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" + "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==", + "optional": true }, "node_modules/acorn": { "version": "8.12.1", @@ -1238,6 +1888,7 @@ "version": "0.11.6", "resolved": "https://registry.npmjs.org/airtable/-/airtable-0.11.6.tgz", "integrity": "sha512-Na67L2TO1DflIJ1yOGhQG5ilMfL2beHpsR+NW/jhaYOa4QcoxZOtDFs08cpSd1tBMsLpz5/rrz/VMX/pGL/now==", + "optional": true, "dependencies": { "@types/node": ">=8.0.0 <15", "abort-controller": "^3.0.0", @@ -1252,7 +1903,8 @@ "node_modules/airtable/node_modules/@types/node": { "version": "14.18.63", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", - "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "optional": true }, "node_modules/ajv": { "version": "6.12.6", @@ -1312,6 +1964,35 @@ "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", @@ -1493,6 +2174,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-types": { "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", @@ -1517,7 +2208,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "optional": true }, "node_modules/auto-bind": { "version": "5.0.1", @@ -1550,6 +2242,7 @@ "version": "1.7.7", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "optional": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1571,58 +2264,11 @@ "node": ">= 0.11.8" } }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/bare-events": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", - "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", - "optional": true - }, - "node_modules/bare-fs": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", - "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", - "optional": true, - "dependencies": { - "bare-events": "^2.0.0", - "bare-path": "^2.0.0", - "bare-stream": "^2.0.0" - } - }, - "node_modules/bare-os": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", - "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", - "optional": true - }, - "node_modules/bare-path": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", - "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", - "optional": true, - "dependencies": { - "bare-os": "^2.1.0" - } - }, - "node_modules/bare-stream": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.0.tgz", - "integrity": "sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==", - "optional": true, - "dependencies": { - "b4a": "^1.6.6", - "streamx": "^2.20.0" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -1673,29 +2319,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/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==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", @@ -1744,29 +2367,6 @@ "resolved": "https://registry.npmjs.org/browser-split/-/browser-split-0.0.0.tgz", "integrity": "sha512-CNXO3AXAS1H/kOGQkPjucm1161/XoF3aVkMfujqwk85XN/D/MkQMvoB81lXyX/2rerZS+hPAYYRR3mAW05awjQ==" }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1795,6 +2395,16 @@ "semver": "^7.0.0" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cache-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/cache-point/-/cache-point-2.0.0.tgz", @@ -1857,6 +2467,23 @@ "node": ">= 10" } }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -1879,6 +2506,16 @@ "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1913,11 +2550,6 @@ "node": ">= 6" } }, - "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/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -1941,17 +2573,6 @@ "indexof": "0.0.1" } }, - "node_modules/cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "dependencies": { - "string-width": "^4.2.3" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/cli-tableau": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", @@ -1994,6 +2615,16 @@ "node": ">=8" } }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2007,20 +2638,6 @@ "node": ">=12" } }, - "node_modules/clone-regexp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-3.0.0.tgz", - "integrity": "sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==", - "dependencies": { - "is-regexp": "^3.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/collect-all": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/collect-all/-/collect-all-1.0.4.tgz", @@ -2034,18 +2651,6 @@ "node": ">=0.10.0" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2106,6 +2711,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "optional": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2249,6 +2855,7 @@ "resolved": "https://registry.npmjs.org/contentful/-/contentful-9.3.7.tgz", "integrity": "sha512-Jl33LEUXdiu1WD6tIDE5/oRYiSdqBbnzLJWDgXb4htgxbnsaNXvDP58ny7BrVT02M2Ii8TfCCfsH5cnfiV5f1A==", "hasInstallScript": true, + "optional": true, "dependencies": { "@contentful/rich-text-types": "^16.0.2", "axios": "^1.7.4", @@ -2265,6 +2872,7 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/contentful-resolve-response/-/contentful-resolve-response-1.9.0.tgz", "integrity": "sha512-LtgPx/eREpHXOX82od48zFZbFhXzYw/NfUoYK4Qf1OaKpLzmYPE4cAY4aD+rxVgnMM5JN/mQaPCsofUlJRYEUA==", + "optional": true, "dependencies": { "fast-copy": "^2.1.7" }, @@ -2276,6 +2884,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-8.3.2.tgz", "integrity": "sha512-L2LNWRXb1/5RLpLemCoP2Lzz6211xyE63GXh2nVXekvM4Dnswo+9N2D6JmWTne9zq89Izo88vOGAzzIAxb4Ukw==", + "optional": true, "dependencies": { "fast-copy": "^2.1.7", "lodash.isplainobject": "^4.0.6", @@ -2287,21 +2896,21 @@ "node": ">=18" } }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "optional": true }, "node_modules/croner": { "version": "4.1.97", @@ -2419,6 +3028,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", + "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -2429,10 +3039,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, "engines": { "node": ">=4.0.0" } @@ -2440,7 +3061,8 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/deepmerge": { "version": "4.3.1", @@ -2501,6 +3123,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "optional": true, "engines": { "node": ">=0.4.0" } @@ -2514,14 +3137,6 @@ "node": ">=8" } }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "engines": { - "node": ">=8" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2629,6 +3244,12 @@ "node": ">=10" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2639,14 +3260,6 @@ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -2807,6 +3420,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3353,6 +4005,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3364,7 +4026,8 @@ "node_modules/event-source-polyfill": { "version": "1.0.31", "resolved": "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz", - "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==" + "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==", + "optional": true }, "node_modules/event-target-shim": { "version": "5.0.1", @@ -3396,18 +4059,11 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "optional": true, "engines": { "node": ">=12.0.0" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "engines": { - "node": ">=6" - } - }, "node_modules/extendable-error": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", @@ -3453,7 +4109,8 @@ "node_modules/fast-copy": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz", - "integrity": "sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==" + "integrity": "sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==", + "optional": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -3461,11 +4118,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" - }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -3508,7 +4160,8 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fastq": { "version": "1.17.1", @@ -3571,33 +4224,6 @@ "moment": "^2.29.1" } }, - "node_modules/filename-reserved-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", - "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/filenamify": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-5.1.1.tgz", - "integrity": "sha512-M45CbrJLGACfrPOkrTp3j2EcO9OBkKUYME0eiqOCa7i2poaklU0jhlIaMlr8ijLorT0uLAzrn3qXOp5684CkfA==", - "dependencies": { - "filename-reserved-regex": "^3.0.0", - "strip-outer": "^2.0.0", - "trim-repeated": "^2.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3713,10 +4339,39 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "optional": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3726,11 +4381,6 @@ "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==" - }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -3757,7 +4407,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -3780,17 +4431,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-timeout": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-0.1.1.tgz", - "integrity": "sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/function.prototype.name": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", @@ -3850,6 +4490,7 @@ "version": "8.6.5", "resolved": "https://registry.npmjs.org/get-it/-/get-it-8.6.5.tgz", "integrity": "sha512-o1hjPwrb/icm3WJbCweTSq8mKuDfJlqwbFauI+Pdgid99at/BFaBXFBJZE+uqvHyOVARE4z680S44vrDm8SsCw==", + "optional": true, "dependencies": { "@types/follow-redirects": "^1.14.4", "@types/progress-stream": "^2.0.5", @@ -3940,22 +4581,6 @@ "node": ">= 10.0.0" } }, - "node_modules/get-urls": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/get-urls/-/get-urls-12.1.0.tgz", - "integrity": "sha512-qHO+QmPiI1bEw0Y/m+WMAAx/UoEEXLZwEx0DVaKMtlHNrKbMeV960LryIpd+E2Ykb9XkVHmVtpbCsmul3GhR0g==", - "dependencies": { - "normalize-url": "^8.0.0", - "super-regex": "^0.2.0", - "url-regex-safe": "^4.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/git-node-fs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", @@ -3966,11 +4591,6 @@ "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==" }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4078,6 +4698,16 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -4178,6 +4808,13 @@ "node": ">= 0.4" } }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-element": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/html-element/-/html-element-2.3.1.tgz", @@ -4237,6 +4874,15 @@ "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==", "dev": true }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, "node_modules/hyperscript": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hyperscript/-/hyperscript-2.0.2.tgz", @@ -4384,14 +5030,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, - "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -4570,6 +5208,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4625,24 +5270,14 @@ "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-retry-allowed": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "optional": true, "engines": { "node": ">=10" }, @@ -4771,6 +5406,21 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/js-git": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", @@ -4986,6 +5636,15 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, + "node_modules/jsep": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.9.tgz", + "integrity": "sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -5007,7 +5666,8 @@ "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==" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "optional": true }, "node_modules/json5": { "version": "1.0.2", @@ -5031,33 +5691,24 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "node_modules/jsonpath-plus": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.1.0.tgz", + "integrity": "sha512-gHfV1IYqH8uJHYVTs8BJX1XKy2/rR93+f8QQi0xhx95aCiXn1ettYAd5T+7FU6wfqyDoX/wy0pm/fL3jOKJ9Lg==", + "license": "MIT", "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" - } - }, - "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "@jsep-plugin/assignment": "^1.2.1", + "@jsep-plugin/regex": "^1.0.3", + "jsep": "^1.3.9" + }, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=18.0.0" } }, - "node_modules/jsonpath/node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5147,12 +5798,14 @@ "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "optional": true }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "optional": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -5194,6 +5847,13 @@ "node": ">= 12.0.0" } }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -5202,6 +5862,16 @@ "node": ">=12" } }, + "node_modules/magic-string": { + "version": "0.30.12", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", @@ -5257,6 +5927,113 @@ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" }, + "node_modules/memfs": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.14.0.tgz", + "integrity": "sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/memfs/node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/@jsonjoy.com/json-pack": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz", + "integrity": "sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg==", + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/memfs/node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/memfs/node_modules/tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "license": "0BSD" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5283,6 +6060,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "optional": true, "engines": { "node": ">= 0.6" } @@ -5291,6 +6069,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "optional": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5302,6 +6081,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "optional": true, "engines": { "node": ">=10" }, @@ -5325,16 +6105,18 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/mkdirp": { @@ -5348,11 +6130,6 @@ "node": ">=10" } }, - "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==" - }, "node_modules/mkdirp2": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/mkdirp2/-/mkdirp2-1.0.5.tgz", @@ -5386,6 +6163,103 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/msw": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.5.1.tgz", + "integrity": "sha512-V0BmHvFkbWGXqbyrc+XiuQ8DU3qzcb6lb8gB9Vzltp3cgHLHLCDF/KmmFo0xw58StNaRMTebw3/xpWVvU9xq9g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.0", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.36.5", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "chalk": "^4.1.2", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/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/msw/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/msw/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/msw/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -5408,11 +6282,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -5457,15 +6326,16 @@ "node": ">= 0.4.0" } }, - "node_modules/node-abi": { - "version": "3.68.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.68.0.tgz", - "integrity": "sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==", - "dependencies": { - "semver": "^7.3.5" - }, + "node_modules/neverthrow": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-8.1.1.tgz", + "integrity": "sha512-DpbZ/UDI0B+TxJB1JysXSfi1++3YK2xLBqQLTlRN0b4zxlZ2MoiB+dKKV8dMRzt1fAFjRKDknXOVJgpl+a4Amw==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.24.0" } }, "node_modules/node-addon-api": { @@ -5477,6 +6347,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "devOptional": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -5567,17 +6438,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/nssocket": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/nssocket/-/nssocket-0.6.0.tgz", @@ -5722,6 +6582,7 @@ "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, "dependencies": { "wrappy": "1" } @@ -5766,6 +6627,13 @@ "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", "dev": true }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-debounce": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-4.0.0.tgz", @@ -5841,6 +6709,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-4.1.1.tgz", "integrity": "sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==", + "optional": true, "engines": { "node": ">=10" }, @@ -5898,6 +6767,12 @@ "node": ">= 14" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/package-manager-detector": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.2.tgz", @@ -5958,33 +6833,37 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" } }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5994,6 +6873,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", @@ -6259,70 +7155,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/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==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/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==", - "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/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6358,12 +7190,14 @@ "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==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "optional": true }, "node_modules/progress-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-2.0.0.tgz", "integrity": "sha512-xJwOWR46jcXUq6EH9yYyqp+I52skPySOeHfkxOZ2IY1AiBi/sFJhbhAKHoV3OTw/omQ45KTio9215dRJ2Yxd3Q==", + "optional": true, "dependencies": { "speedometer": "~1.0.0", "through2": "~2.0.3" @@ -6406,21 +7240,19 @@ "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6453,6 +7285,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6473,33 +7312,6 @@ } ] }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -6530,6 +7342,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "optional": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6543,12 +7356,14 @@ "node_modules/readable-stream/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "optional": true }, "node_modules/readable-stream/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==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true }, "node_modules/readdirp": { "version": "3.6.0", @@ -6662,6 +7477,13 @@ "node": ">=6" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/requizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", @@ -6716,61 +7538,55 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "dev": true, + "license": "MIT", "dependencies": { - "glob": "^9.2.0" + "@types/estree": "1.0.6" }, "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, "node_modules/run-parallel": { "version": "1.2.0", @@ -6818,6 +7634,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "optional": true, "dependencies": { "tslib": "^2.1.0" } @@ -6825,7 +7642,8 @@ "node_modules/rxjs/node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "optional": true }, "node_modules/safe-array-concat": { "version": "1.1.2", @@ -6896,14 +7714,6 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, - "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", - "dependencies": { - "truncate-utf8-bytes": "^1.0.0" - } - }, "node_modules/sanitize-html": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.1.tgz", @@ -6976,33 +7786,6 @@ "node": ">= 0.4" } }, - "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/sharp/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7044,68 +7827,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "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==" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-get/node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -7307,7 +8040,8 @@ "node_modules/speedometer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.0.0.tgz", - "integrity": "sha512-lgxErLl/7A5+vgIIXsh9MbeukOaCb2axgQ+bKCdIE+ibNT4XNYGNCR1qFEGq6F+YDASXK3Fh/c5FgtZchFolxw==" + "integrity": "sha512-lgxErLl/7A5+vgIIXsh9MbeukOaCb2axgQ+bKCdIE+ibNT4XNYGNCR1qFEGq6F+YDASXK3Fh/c5FgtZchFolxw==", + "optional": true }, "node_modules/sprintf-js": { "version": "1.0.3", @@ -7323,89 +8057,29 @@ "node": "*" } }, - "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "dependencies": { - "escodegen": "^1.8.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true, + "license": "MIT" }, "node_modules/stream-connect": { "version": "1.0.2", @@ -7441,18 +8115,12 @@ "node": ">=0.10.0" } }, - "node_modules/streamx": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", - "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", - "dependencies": { - "fast-fifo": "^1.3.2", - "queue-tick": "^1.0.1", - "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" - } + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" }, "node_modules/string_decoder": { "version": "1.1.1", @@ -7480,6 +8148,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "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==", + "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/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -7543,6 +8226,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -7563,38 +8259,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-outer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-2.0.0.tgz", - "integrity": "sha512-A21Xsm1XzUkK0qK1ZrytDUvqsQWict2Cykhvi0fBQntGG5JSprESasEyV1EZ/4CiR5WB5KjzLTrP/bO37B0wPg==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==" }, - "node_modules/super-regex": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-0.2.0.tgz", - "integrity": "sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==", - "dependencies": { - "clone-regexp": "^3.0.0", - "function-timeout": "^0.1.0", - "time-span": "^5.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -7680,29 +8349,6 @@ "node": ">= 6.0.0" } }, - "node_modules/tar-fs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", - "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^2.1.1", - "bare-path": "^2.1.0" - } - }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/temp-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-path/-/temp-path-1.0.0.tgz", @@ -7746,14 +8392,6 @@ "node": ">=4" } }, - "node_modules/text-decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz", - "integrity": "sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==", - "dependencies": { - "b4a": "^1.6.4" - } - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -7769,31 +8407,54 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "optional": true, "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, - "node_modules/time-span": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", - "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", - "dependencies": { - "convert-hrtime": "^5.0.0" - }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", + "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.0.0 || >=20.0.0" } }, - "node_modules/tlds": { - "version": "1.255.0", - "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz", - "integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==", - "bin": { - "tlds": "bin.js" + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, "node_modules/tmp": { @@ -7837,22 +8498,38 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/trim-repeated": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-2.0.0.tgz", - "integrity": "sha512-QUHBFTJGdOwmp0tbOG505xAgOp/YliZP/6UgafFXYZ26WT1bvQmSMJUvkeVSASuJJHbqsFbynTvkd5W8RBTipg==", + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "escape-string-regexp": "^5.0.0" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=12" + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "devOptional": true + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -7861,14 +8538,6 @@ "node": ">= 14.0.0" } }, - "node_modules/truncate-utf8-bytes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", - "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", - "dependencies": { - "utf8-byte-length": "^1.0.1" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -7891,6 +8560,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -8084,7 +8754,8 @@ "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "devOptional": true }, "node_modules/universalify": { "version": "0.1.2", @@ -8104,35 +8775,168 @@ "punycode": "^2.1.0" } }, - "node_modules/url-regex-safe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/url-regex-safe/-/url-regex-safe-4.0.0.tgz", - "integrity": "sha512-BrnFCWKNFrFnRzKD66NtJqQepfJrUHNPvPxE5y5NSAhXBb4OlobQjt7907Jm4ItPiXaeX+dDWMkcnOd4jR9N8A==", + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "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==" + }, + "node_modules/vite": { + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "dev": true, + "license": "MIT", "dependencies": { - "ip-regex": "4.3.0", - "tlds": "^1.242.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": ">= 14" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "re2": "^1.20.1" + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { - "re2": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { "optional": true } } }, - "node_modules/utf8-byte-length": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", - "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==" + "node_modules/vite-node": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", + "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.6", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "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==" + "node_modules/vitest": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", + "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.3", + "@vitest/mocker": "2.1.3", + "@vitest/pretty-format": "^2.1.3", + "@vitest/runner": "2.1.3", + "@vitest/snapshot": "2.1.3", + "@vitest/spy": "2.1.3", + "@vitest/utils": "2.1.3", + "chai": "^5.1.1", + "debug": "^4.3.6", + "magic-string": "^0.30.11", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.3", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.3", + "@vitest/ui": "2.1.3", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } }, "node_modules/vizion": { "version": "2.2.1", @@ -8168,12 +8972,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "devOptional": true }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "devOptional": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -8230,6 +9036,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/winston": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", @@ -8344,6 +9167,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -8392,10 +9216,29 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "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==", + "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==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/ws": { "version": "7.5.10", @@ -8427,6 +9270,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "optional": true, "engines": { "node": ">=0.4" } @@ -8481,78 +9325,103 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/content": { "name": "@bluecadet/launchpad-content", - "version": "1.14.0", + "version": "1.14.1", "license": "ISC", "dependencies": { "@bluecadet/launchpad-utils": "~1.5.0", "@portabletext/to-html": "2.0.0", "@sanity/block-content-to-markdown": "^0.0.5", - "@sanity/client": "^6.4.9", - "@sanity/image-url": "^1.0.1", - "airtable": "^0.11.1", "chalk": "^5.0.0", - "cli-progress": "^3.9.1", - "contentful": "^9.0.0", - "filenamify": "^5.1.1", - "fs-extra": "^11.2.0", - "get-urls": "^12.1.0", - "jsonpath": "^1.1.1", + "glob": "^11.0.0", + "jsonpath-plus": "^10.1.0", "ky": "^1.7.2", "markdown-it": "^12.2.0", + "neverthrow": "^8.0.0", "p-queue": "^7.1.0", "qs": "^6.11.1", - "rimraf": "^4.0.5", - "sanitize-filename": "^1.6.3", - "sanitize-html": "^2.5.1", - "sharp": "^0.32.6", - "strip-json-comments": "^4.0.0" + "sanitize-html": "^2.5.1" }, "bin": { "launchpad-content": "index.js" }, "devDependencies": { - "@types/cli-progress": "^3.11.0", - "@types/jsonpath": "^0.2.0", + "@bluecadet/launchpad-testing": "0.1.0", + "@bluecadet/launchpad-tsconfig": "0.1.0", "@types/markdown-it": "^12.2.0", "@types/progress-stream": "^2.0.2", "@types/qs": "^6.9.7", - "@types/rimraf": "^4.0.5", "@types/sanitize-html": "^2.9.0", - "@types/sharp": "^0.30.5" + "@types/sharp": "^0.30.5", + "memfs": "^4.14.0", + "msw": "^2.5.1", + "vitest": "^2.1.3" + }, + "optionalDependencies": { + "@sanity/client": "^6.4.9", + "airtable": "^0.11.1", + "contentful": "^9.0.0" } }, - "packages/content/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "packages/content/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" + "balanced-match": "^1.0.0" } }, - "packages/content/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "packages/content/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "license": "ISC", "dependencies": { - "universalify": "^2.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "packages/content/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "packages/content/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">= 10.0.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "packages/dashboard": { @@ -8564,6 +9433,9 @@ }, "bin": { "launchpad-dashboard": "index.js" + }, + "devDependencies": { + "@bluecadet/launchpad-tsconfig": "0.1.0" } }, "packages/launchpad": { @@ -8577,49 +9449,19 @@ "@bluecadet/launchpad-scaffold": "~1.8.0", "@bluecadet/launchpad-utils": "~1.5.0", "auto-bind": "^5.0.1", - "chalk": "^5.0.0", - "fs-extra": "^10.0.0" + "chalk": "^5.0.0" }, "bin": { "launchpad": "index.js" }, + "devDependencies": { + "@bluecadet/launchpad-tsconfig": "0.1.0" + }, "engines": { "node": ">=17.5.0", "npm": ">=8.5.1" } }, - "packages/launchpad/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "packages/launchpad/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "packages/launchpad/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "packages/monitor": { "name": "@bluecadet/launchpad-monitor", "version": "1.8.0", @@ -8630,7 +9472,6 @@ "axon": "^2.0.3", "chalk": "^5.0.0", "cross-spawn": "^7.0.3", - "fs-extra": "^10.0.0", "node-window-manager": "^2.2.4", "p-debounce": "^4.0.0", "pm2": "^5.1.2", @@ -8641,44 +9482,13 @@ "launchpad-monitor": "index.js" }, "devDependencies": { + "@bluecadet/launchpad-tsconfig": "0.1.0", "@types/axon": "2.0", "@types/cross-spawn": "^6.0.2", "@types/semver": "^7.5.8", "@types/tail": "2.2" } }, - "packages/monitor/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "packages/monitor/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "packages/monitor/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, "packages/scaffold": { "name": "@bluecadet/launchpad-scaffold", "version": "1.8.1", @@ -8689,8 +9499,27 @@ }, "bin": { "launchpad-scaffold": "index.js" + }, + "devDependencies": { + "@bluecadet/launchpad-tsconfig": "0.1.0" + } + }, + "packages/testing": { + "name": "@bluecadet/launchpad-testing", + "version": "0.1.0", + "dependencies": { + "memfs": "^4.14.0", + "neverthrow": "^8.1.1" + }, + "devDependencies": { + "@bluecadet/launchpad-tsconfig": "0.1.0", + "@bluecadet/launchpad-utils": "*" } }, + "packages/tsconfig": { + "name": "@bluecadet/launchpad-tsconfig", + "version": "0.1.0" + }, "packages/utils": { "name": "@bluecadet/launchpad-utils", "version": "1.5.2", @@ -8701,17 +9530,17 @@ "auto-bind": "^5.0.1", "chalk": "^5.0.0", "dotenv": "^16.3.1", - "fs-extra": "^10.0.0", "logform": "^2.3.2", "moment": "^2.29.1", + "neverthrow": "^8.1.0", "strip-json-comments": "^4.0.0", "winston": "^3.4.0", "winston-daily-rotate-file": "^4.5.5", "yargs": "^17.3.1" }, "devDependencies": { + "@bluecadet/launchpad-tsconfig": "0.1.0", "@types/async": "^3.2.20", - "@types/fs-extra": "^11.0.1", "@types/yargs": "^17.0.24" } }, @@ -8724,38 +9553,6 @@ "funding": { "url": "https://dotenvx.com" } - }, - "packages/utils/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "packages/utils/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "packages/utils/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } } } } diff --git a/package.json b/package.json index 670dfb78..dc3eeb6a 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,6 @@ "description": "monorepo for @bluecadet/launchpad and friends", "scripts": { "changeset": "changeset", - "start": "npm run start -w @bluecadet/launchpad -- ", - "monitor": "npm run start -w @bluecadet/launchpad-monitor --", - "monitor-kill": "npm run pm2 kill -w @bluecadet/launchpad-monitor --", - "scaffold": "npm run start -w @bluecadet/launchpad-scaffold --", - "content": "npm run start -w @bluecadet/launchpad-content --", - "download": "npm run content --", - "test": "npm run test -w @bluecadet/launchpad -- ", - "docs": "node .docs/generate.mjs", - "watch-docs": "nodemon", "lint": "npx eslint ./packages/**/*.js", "lint:fix": "npx eslint ./packages/**/*.js --fix", "lint:fix-dry": "npx eslint ./packages/**/*.js --fix-dry-run", diff --git a/packages/content/credentials-template.json b/packages/content/credentials-template.json deleted file mode 100644 index a747e0ab..00000000 --- a/packages/content/credentials-template.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "airtableTest": { - "apiKey": "" - }, - "contentfulTest": { - "deliveryToken": "", - "previewToken": "", - "usePreviewApi": true - }, - "strapiTest": { - "identifier": "", - "password": "" - }, - "sanityTest": { - "apiToken": "" - } -} diff --git a/packages/content/index.js b/packages/content/index.js index 22f75698..effd9aa9 100755 --- a/packages/content/index.js +++ b/packages/content/index.js @@ -9,10 +9,10 @@ export * from './lib/utils/file-utils.js'; export default LaunchpadContent; /** - * @param {import('./lib/content-options.js').ContentOptions | {content: import('./lib/content-options.js').ContentOptions}} config + * @param {import('./lib/content-options.js').ConfigWithContent} config */ export const launch = async (config) => { - const content = new LaunchpadContent('content' in config ? config.content : config); + const content = new LaunchpadContent(config); await content.start(); }; diff --git a/packages/content/lib/__tests__/content-integration.test.js b/packages/content/lib/__tests__/content-integration.test.js new file mode 100644 index 00000000..ea6f9942 --- /dev/null +++ b/packages/content/lib/__tests__/content-integration.test.js @@ -0,0 +1,298 @@ +import { describe, it, expect, beforeAll, afterAll, afterEach, beforeEach } from 'vitest'; +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; +import { vol } from 'memfs'; +import path from 'path'; +import { LaunchpadContent } from '../launchpad-content.js'; +import { createMockLogger } from '@bluecadet/launchpad-testing/test-utils.js'; +import mdToHtml from '../plugins/md-to-html.js'; +import mediaDownloader from '../plugins/media-downloader.js'; +import sanityToHtml from '../plugins/sanity-to-html.js'; +import jsonSource from '../sources/json-source.js'; +import sanitySource from '../sources/sanity-source.js'; + +describe('Content Integration', () => { + const server = setupServer(); + + beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); + afterAll(() => server.close()); + beforeEach(() => vol.reset()); + afterEach(() => server.resetHandlers()); + + describe('JSON Source with Markdown and Media', () => { + it('should fetch JSON, convert markdown to HTML, and download media', async () => { + // Mock JSON endpoint with markdown content and media URLs + server.use( + http.get('https://api.example.com/content', () => { + return HttpResponse.json({ + title: 'Test Article', + content: '# Hello World\n\nThis is a paragraph', + gallery: [ + 'https://example.com/gallery1.jpg', + 'https://example.com/gallery2.jpg' + ] + }); + }), + // Mock media endpoints + http.get(/https:\/\/example\.com\/.+\.jpg/, () => { + return new HttpResponse('fake image data', { + headers: { 'Content-Type': 'image/jpeg' } + }); + }) + ); + + const content = new LaunchpadContent({ + content: { + downloadPath: '/downloads', + tempPath: '/temp', + sources: [ + jsonSource({ + id: 'blog', + files: { + 'article.json': 'https://api.example.com/content' + } + }) + ] + }, + plugins: [ + mdToHtml({ path: '$.content' }), + mediaDownloader({ + mediaPattern: /\.jpg$/, + maxConcurrent: 2 + }) + ] + }, createMockLogger()); + + const result = await content.download(); + + expect(result).toBeOk(); + + // Check if markdown was converted to HTML + const articlePath = path.join('/downloads', 'blog', 'article.json'); + expect(vol.existsSync(articlePath)).toBe(true); + const article = JSON.parse(vol.readFileSync(articlePath, 'utf8').toString()); + expect(article.content).toContain('

Hello World

'); + expect(article.content).toContain('

This is a paragraph

'); + + // Check if media was downloaded + const mediaFiles = ['gallery1.jpg', 'gallery2.jpg']; + for (const file of mediaFiles) { + const mediaPath = path.join('/downloads', 'blog', file); + expect(vol.existsSync(mediaPath)).toBe(true); + expect(vol.readFileSync(mediaPath, 'utf8')).toBe('fake image data'); + } + }); + }); + + describe('Sanity Source with HTML Conversion', () => { + it('should fetch Sanity content and convert blocks to HTML', async () => { + server.use( + http.get('https://test-project.api.sanity.io/v2021-10-21/data/query/production', ({ request }) => { + const url = new URL(request.url); + const query = url.searchParams.get('query'); + + if (query?.includes('[100..')) { + return HttpResponse.json({ + result: [], + ms: 15 + }); + } + + if (query?.includes('article')) { + return HttpResponse.json({ + result: [{ + _type: 'article', + title: 'Test Article', + content: { + _type: 'block', + children: [{ + _type: 'span', + text: 'Hello from Sanity' + }] + } + }], + ms: 15 + }); + } + + return HttpResponse.json({ result: [] }); + }) + ); + + const content = new LaunchpadContent({ + content: { + downloadPath: '/downloads', + tempPath: '/temp', + sources: [ + sanitySource({ + id: 'cms', + projectId: 'test-project', + apiToken: 'test-token', + queries: ['article'] + }) + ] + }, + plugins: [ + sanityToHtml({ path: '$..content' }) + ] + }, createMockLogger()); + + const result = await content.download(); + + expect(result).toBeOk(); + + const articlePath = path.join('/downloads', 'cms', 'article.json'); + expect(vol.existsSync(articlePath)).toBe(true); + + const article = JSON.parse(vol.readFileSync(articlePath, 'utf8').toString()); + expect(article[0].content).toBe('

Hello from Sanity

'); + }); + }); + + describe('Multiple Sources with Shared Media', () => { + it('should handle media downloads from multiple sources', async () => { + // Mock endpoints for two different content sources that reference the same media + server.use( + // Source 1: JSON API + http.get('https://api1.example.com/content', () => { + return HttpResponse.json({ + hero: 'https://example.com/shared.jpg', + content: 'Content from source 1' + }); + }), + // Source 2: Another JSON API + http.get('https://api2.example.com/content', () => { + return HttpResponse.json({ + thumbnail: 'https://example.com/shared.jpg', + content: 'Content from source 2' + }); + }), + // Shared media endpoint + http.get('https://example.com/shared.jpg', () => { + return new HttpResponse('shared image data', { + headers: { 'Content-Type': 'image/jpeg' } + }); + }) + ); + + const content = new LaunchpadContent({ + content: { + downloadPath: '/downloads', + tempPath: '/temp', + sources: [ + jsonSource({ + id: 'source1', + files: { + 'content.json': 'https://api1.example.com/content' + } + }), + jsonSource({ + id: 'source2', + files: { + 'content.json': 'https://api2.example.com/content' + } + }) + ] + }, + plugins: [ + mediaDownloader({ + mediaPattern: /\.jpg$/, + maxConcurrent: 1 // Force sequential downloads to test deduplication + }) + ] + }, createMockLogger()); + + const result = await content.download(); + + expect(result).toBeOk(); + + // Check if shared media was downloaded only once and is accessible from both sources + const mediaPath1 = path.join('/downloads', 'source1', 'shared.jpg'); + const mediaPath2 = path.join('/downloads', 'source2', 'shared.jpg'); + + expect(vol.existsSync(mediaPath1)).toBe(true); + expect(vol.existsSync(mediaPath2)).toBe(true); + expect(vol.readFileSync(mediaPath1, 'utf8')).toBe('shared image data'); + expect(vol.readFileSync(mediaPath2, 'utf8')).toBe('shared image data'); + }); + }); + + describe('Error Handling', () => { + it('should handle source failures and rollback content', async () => { + // Setup initial content + vol.mkdirSync('/downloads/test', { recursive: true }); + vol.writeFileSync('/downloads/test/existing.json', JSON.stringify({ data: 'existing' })); + + server.use( + http.get('https://api.example.com/content', () => { + return new HttpResponse(null, { status: 500 }); + }) + ); + + const content = new LaunchpadContent({ + content: { + downloadPath: '/downloads', + tempPath: '/temp', + backupPath: '/backups', + sources: [ + jsonSource({ + id: 'test', + files: { + 'content.json': 'https://api.example.com/content' + } + }) + ] + } + }, createMockLogger()); + + const result = await content.download(); + + expect(result).toBeErr(); + + // Check if existing content was preserved + expect(vol.existsSync('/downloads/test/existing.json')).toBe(true); + const preserved = JSON.parse(vol.readFileSync('/downloads/test/existing.json', 'utf8').toString()); + expect(preserved.data).toBe('existing'); + }); + + it('should handle plugin failures and rollback content', async () => { + // Setup initial content + vol.mkdirSync('/downloads/test', { recursive: true }); + vol.writeFileSync('/downloads/test/existing.json', JSON.stringify({ data: 'existing' })); + + server.use( + http.get('https://api.example.com/content', () => { + return HttpResponse.json({ + content: { invalidContent: 'not a valid Sanity block' } + }); + }) + ); + + const content = new LaunchpadContent({ + content: { + downloadPath: '/downloads', + tempPath: '/temp', + backupPath: '/backups', + sources: [ + jsonSource({ + id: 'test', + files: { + 'content.json': 'https://api.example.com/content' + } + }) + ] + }, + plugins: [ + sanityToHtml({ path: '$..content' }) // This should fail on invalid content + ] + }, createMockLogger()); + + const result = await content.download(); + + expect(result).toBeErr(); + + // expect downloads to be restored + expect(vol.existsSync('/downloads/test')).toBe(true); + }); + }); +}); diff --git a/packages/content/lib/__tests__/content-plugin-driver.test.js b/packages/content/lib/__tests__/content-plugin-driver.test.js new file mode 100644 index 00000000..647432c5 --- /dev/null +++ b/packages/content/lib/__tests__/content-plugin-driver.test.js @@ -0,0 +1,195 @@ +import { describe, it, expect, vi } from 'vitest'; +import { ContentPluginDriver, ContentError, defineContentPlugin, defineContentPluginHooks } from '../content-plugin-driver.js'; +import { DataStore } from '../utils/data-store.js'; +import PluginDriver from '@bluecadet/launchpad-utils/lib/plugin-driver.js'; +import { resolveContentOptions } from '../content-options.js'; +import { createMockLogger } from '@bluecadet/launchpad-testing/test-utils.js'; + +describe('ContentPluginDriver', () => { + const createMockContext = () => { + const dataStore = new DataStore(); + const options = resolveContentOptions({ + downloadPath: '/downloads/', + tempPath: '/temp/', + backupPath: '/backups/' + }); + + const paths = { + /** @param {string | undefined} source */ + getDownloadPath: (source) => source ? `/downloads/${source}` : '/downloads', + /** + * @param {string | undefined} source + * @param {string | undefined} plugin + */ + getTempPath: (source, plugin) => source ? `/temp/${plugin}/${source}` : `/temp/${plugin}`, + /** @param {string | undefined} source */ + getBackupPath: (source) => source ? `/backups/${source}` : '/backups' + }; + + return { dataStore, options, paths }; + }; + + describe('plugin context', () => { + it('should provide correct context to plugins', async () => { + const { dataStore, options, paths } = createMockContext(); + const baseLogger = createMockLogger(); + const driver = new PluginDriver(baseLogger); + const contentDriver = new ContentPluginDriver(driver, { + dataStore, + options, + paths + }); + + const plugin = defineContentPlugin({ + name: 'test-plugin', + hooks: defineContentPluginHooks({ + onContentFetchDone(ctx) { + expect(ctx.data).toBe(dataStore); + expect(ctx.contentOptions).toBe(options); + expect(ctx.paths.getDownloadPath).toBe(paths.getDownloadPath); + expect(ctx.paths.getBackupPath).toBe(paths.getBackupPath); + // Test plugin-specific temp path + expect(ctx.paths.getTempPath('source')).toBe('/temp/test-plugin/source'); + } + }) + }); + + contentDriver.add(plugin); + await contentDriver.runHookSequential('onContentFetchDone'); + }); + + it('should handle plugin-specific temp paths correctly', async () => { + const { dataStore, options, paths } = createMockContext(); + const baseLogger = createMockLogger(); + const driver = new PluginDriver(baseLogger); + const contentDriver = new ContentPluginDriver(driver, { + dataStore, + options, + paths + }); + + const plugin1 = defineContentPlugin({ + name: 'plugin1', + hooks: defineContentPluginHooks({ + onContentFetchDone(ctx) { + expect(ctx.paths.getTempPath('source')).toBe('/temp/plugin1/source'); + } + }) + }); + + const plugin2 = defineContentPlugin({ + name: 'plugin2', + hooks: defineContentPluginHooks({ + onContentFetchDone(ctx) { + expect(ctx.paths.getTempPath('source')).toBe('/temp/plugin2/source'); + } + }) + }); + + contentDriver.add(plugin1); + contentDriver.add(plugin2); + await contentDriver.runHookSequential('onContentFetchDone'); + }); + }); + + describe('error handling', () => { + it('should handle setup errors with ContentError', async () => { + const { dataStore, options, paths } = createMockContext(); + const baseLogger = createMockLogger(); + const driver = new PluginDriver(baseLogger); + const contentDriver = new ContentPluginDriver(driver, { + dataStore, + options, + paths + }); + + const onSetupError = vi.fn(); + const error = new Error('Setup failed'); + + const plugin = defineContentPlugin({ + name: 'error-plugin', + hooks: defineContentPluginHooks({ + onSetupError + }) + }); + + contentDriver.add(plugin); + await contentDriver.runHookSequential('onSetupError', new ContentError('Plugin setup failed', error)); + + expect(onSetupError).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + name: 'ContentError', + message: 'Plugin setup failed', + cause: error + }) + ); + }); + + it('should handle fetch errors with ContentError', async () => { + const { dataStore, options, paths } = createMockContext(); + const baseLogger = createMockLogger(); + const driver = new PluginDriver(baseLogger); + const contentDriver = new ContentPluginDriver(driver, { + dataStore, + options, + paths + }); + + const onContentFetchError = vi.fn(); + const error = new Error('Fetch failed'); + + const plugin = defineContentPlugin({ + name: 'error-plugin', + hooks: defineContentPluginHooks({ + onContentFetchError + }) + }); + + contentDriver.add(plugin); + await contentDriver.runHookSequential('onContentFetchError', new ContentError('Content fetch failed', error)); + + expect(onContentFetchError).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + name: 'ContentError', + message: 'Content fetch failed', + cause: error + }) + ); + }); + }); + + describe('plugin lifecycle', () => { + it('should call hooks in correct order', async () => { + const { dataStore, options, paths } = createMockContext(); + const baseLogger = createMockLogger(); + const driver = new PluginDriver(baseLogger); + const contentDriver = new ContentPluginDriver(driver, { + dataStore, + options, + paths + }); + + /** @type {string[]} */ + const order = []; + const plugin = defineContentPlugin({ + name: 'lifecycle-plugin', + hooks: defineContentPluginHooks({ + onContentFetchSetup() { + order.push('setup'); + }, + onContentFetchDone() { + order.push('done'); + } + }) + }); + + contentDriver.add(plugin); + await contentDriver.runHookSequential('onContentFetchSetup'); + await contentDriver.runHookSequential('onContentFetchDone'); + + expect(order).toEqual(['setup', 'done']); + }); + }); +}); diff --git a/packages/content/lib/__tests__/launchpad-content.test.js b/packages/content/lib/__tests__/launchpad-content.test.js new file mode 100644 index 00000000..bf5442b4 --- /dev/null +++ b/packages/content/lib/__tests__/launchpad-content.test.js @@ -0,0 +1,200 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { vol } from 'memfs'; +import path from 'path'; +import { LaunchpadContent } from '../launchpad-content.js'; +import { createMockLogger } from '@bluecadet/launchpad-testing/test-utils.js'; +import { ok, okAsync } from 'neverthrow'; +import { ContentError } from '../content-plugin-driver.js'; +import { resolveContentOptions } from '../content-options.js'; +import { defineSource } from '../sources/source.js'; + +describe('LaunchpadContent', () => { + beforeEach(() => { + vol.reset(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + /** + * @param {import('../content-plugin-driver.js').ContentPlugin[]} [plugins] + */ + const createBasicConfig = (plugins = []) => { + return { + content: { + downloadPath: '/downloads', + tempPath: '/temp', + backupPath: '/backups', + sources: [ + defineSource({ + id: 'test', + fetch: () => { + return ok([{ + id: 'doc1', + dataPromise: okAsync([{ + id: 'doc1', + data: 'doc1' + }]) + }]); + } + }) + ] + }, + plugins + }; + }; + + describe('constructor', () => { + it('should initialize with default options when no config provided', () => { + const content = new LaunchpadContent(undefined, createMockLogger()); + expect(content).toBeInstanceOf(LaunchpadContent); + expect(content._config).toEqual(resolveContentOptions()); + }); + + it('should initialize with provided config', () => { + const config = createBasicConfig(); + const content = new LaunchpadContent(config, createMockLogger()); + expect(content).toBeInstanceOf(LaunchpadContent); + expect(content._config).toEqual(resolveContentOptions(config.content)); + }); + }); + + describe('download', () => { + it('should process all sources and write to disk', async () => { + const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); + const result = await content.download(); + + expect(result).toBeOk(); + + const filePath = path.join('/downloads', 'test', 'doc1.json'); + expect(vol.existsSync(filePath)).toBe(true); + expect(vol.readFileSync(filePath, 'utf8')).toBe('doc1'); + }); + + it('should respect keep patterns when clearing directories', async () => { + // Setup existing files + vol.mkdirSync('/downloads/test', { recursive: true }); + vol.writeFileSync('/downloads/test/.keep', ''); + vol.writeFileSync('/downloads/test/old.json', '{}'); + + const config = { + content: { + ...createBasicConfig().content, + keep: ['.keep'] + } + }; + + const content = new LaunchpadContent(config, createMockLogger()); + const result = await content.download(); + + expect(result).toBeOk(); + + // .keep file should still exist + expect(vol.existsSync('/downloads/test/.keep')).toBe(true); + // old.json should be removed + expect(vol.existsSync('/downloads/test/old.json')).toBe(false); + }); + }); + + describe('plugin system', () => { + it('should run plugins in correct order', async () => { + /** @type {string[]} */ + const order = []; + + const plugin1 = { + name: 'plugin1', + hooks: { + onContentFetchSetup: () => { order.push('plugin1:setup'); }, + onContentFetchDone: () => { order.push('plugin1:done'); } + } + }; + const plugin2 = { + name: 'plugin2', + hooks: { + onContentFetchSetup: () => { order.push('plugin2:setup'); }, + onContentFetchDone: () => { order.push('plugin2:done'); } + } + }; + + const content = new LaunchpadContent(createBasicConfig([plugin1, plugin2]), createMockLogger()); + await content.download(); + + expect(order).toEqual([ + 'plugin1:setup', + 'plugin2:setup', + 'plugin1:done', + 'plugin2:done' + ]); + }); + + it('should handle plugin errors', async () => { + const errorPlugin = { + name: 'error-plugin', + hooks: { + onContentFetchDone: () => { + throw new Error('Plugin error'); + } + } + }; + + const content = new LaunchpadContent(createBasicConfig([errorPlugin]), createMockLogger()); + + const result = await content.download(); + + expect(result).toBeErr(); + expect(result._unsafeUnwrapErr()).toBeInstanceOf(ContentError); + }); + }); + + describe('error handling', () => { + it('should wrap filesystem errors', async () => { + // Make downloads directory read-only + vol.mkdirSync('/downloads', { recursive: true, mode: 0o444 }); + const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); + content._dataStore.createNamespaceFromMap('test', new Map([['doc1', 'doc1']])); + const result = await content._writeDataStoreToDisk(content._dataStore); + + expect(result).toBeErr(); + expect(result._unsafeUnwrapErr()).toBeInstanceOf(ContentError); + }); + + it('should handle directory clearing errors', async () => { + // Make directory read-only + vol.mkdirSync('/downloads', { recursive: true, mode: 0o777 }); + vol.writeFileSync('/downloads/test.json', 'test'); + vol.chmodSync('/downloads', 0o444); + + const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); + const result = await content._clearDir('/downloads'); + + expect(result).toBeErr(); + expect(result._unsafeUnwrapErr()).toBeInstanceOf(ContentError); + }); + }); + + describe('path handling', () => { + it('should handle download path token replacement', () => { + const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); + const path = content._getDetokenizedPath( + '/path/to/%DOWNLOAD_PATH%/file', + '/downloads' + ); + expect(path).toBe('/path/to/downloads/file'); + }); + + it('should handle timestamp token replacement', () => { + vi.useFakeTimers(); + + vi.setSystemTime('2024-01-01T00:00:00.00'); + + const content = new LaunchpadContent(createBasicConfig(), createMockLogger()); + const path = content._getDetokenizedPath( + '/path/to/%TIMESTAMP%/file', + '/downloads' + ); + expect(path).toMatch('/path/to/2024-01-02_00-00-00/file'); + vi.useRealTimers(); + }); + }); +}); diff --git a/packages/content/lib/content-options.js b/packages/content/lib/content-options.js index c9f89a36..6d29addc 100644 --- a/packages/content/lib/content-options.js +++ b/packages/content/lib/content-options.js @@ -7,37 +7,22 @@ export const DOWNLOAD_PATH_TOKEN = '%DOWNLOAD_PATH%'; export const TIMESTAMP_TOKEN = '%TIMESTAMP%'; /** - * @typedef { import('./content-sources/airtable-source.js').AirtableOptions - * | import('./content-sources/contentful-source.js').ContentfulOptions - * | import('./content-sources/json-source.js').JsonOptions - * | import('./content-sources/sanity-source.js').SanityOptions - * | import('./content-sources/strapi-source.js').StrapiOptions - * } AllSourceOptions + * @typedef {import('./sources/source.js').ContentSource + * | Promise + * | import('neverthrow').ResultAsync + * } ConfigContentSource */ /** * @typedef ContentOptions - * @property {AllSourceOptions[]} [sources] A list of content source options. This defines which content is downloaded from where. - * @property {Array>} [imageTransforms] A list of image transforms to apply to a copy of each downloaded image. - * @property {Object} [contentTransforms] A list of content transforms to apply to all donwloaded content. + * @property {ConfigContentSource[]} [sources] A list of content source options. This defines which content is downloaded from where. * @property {string} [downloadPath] The path at which to store all downloaded files. Defaults to '.downloads/'. - * @property {string} [credentialsPath] The path to the json containing credentials for all content sources. Defaults to '.credentials.json'. Deprecated in favor of `.env`/`.env.local`. * @property {string} [tempPath] Temp file directory path. Defaults to '%DOWNLOAD_PATH%/.tmp/'. * @property {string} [backupPath] Temp directory path where all downloaded content will be backed up before removal. Defaults to '%TIMESTAMP%/.tmp-backup/'. - * @property {string} [keep] Which files to keep in `dest` if `clearOldFilesOnSuccess` or `clearOldFilesOnStart` are `true`. E.g. `'*.json|*.csv|*.xml|*.git*'` + * @property {Array} [keep] Which files to keep in `dest` if `clearOldFilesOnSuccess` or `clearOldFilesOnStart` are `true`. E.g. `['*.json', '** /*.csv', '*.xml', '*.git*']` * @property {string} [strip] Strips this string from all media file paths when saving them locally * @property {boolean} [backupAndRestore] Back up files before downloading and restore originals for all sources on failure of any single source. Defaults to true. - * @property {number} [maxConcurrent] Max concurrent downloads. Defaults to 4. * @property {number} [maxTimeout] Max request timeout in ms. Defaults to 30000. - * @property {boolean} [clearOldFilesOnSuccess] Remove all existing files in dest dir when downloads succeed. Ignores files that match `keep`. Defaults to true. - * @property {boolean} [clearOldFilesOnStart] Will remove all existing files _before_ downloads starts. `false` will ensure that existing files are only deleted after a download succeeds. Defaults to false. - * @property {boolean} [ignoreCache] Will always download files regardless of whether they've been cached. Defaults to false. - * @property {boolean} [enableIfModifiedSinceCheck] Enables the HTTP if-modified-since check. Disabling this will assume that the local file is the same as the remote file if it already exists. Defaults to true. - * @property {boolean} [enableContentLengthCheck] Compares the HTTP header content-length with the local file size. Disabling this will assume that the local file is the same as the remote file if it already exists. Defaults to true. - * @property {boolean} [abortOnError] If set to `true`, errors will cause syncing to abort all remaining tasks immediately. Defaults to true. - * @property {boolean} [ignoreImageTransformCache] Set to `true` to always re-generate transformed images, even if cached versions of the original and transformed image already exist. Defaults to false. - * @property {boolean} [ignoreImageTransformErrors] Set to `false` if you want to abort a content source from downloading if any of the image transforms fail. Leaving this to `true` will allow for non-image files to fail quietly. Defaults to true. - * @property {boolean} [forceClearTempFiles] Set to `false` if you want to keep all contents of the tempPath dir before downloading. Defaults to true. * @property {string} [encodeChars] Characters to encode in the path when saving files locally. Defaults to `<>:"|?*`. Applies to both content source paths and media download paths. */ @@ -46,26 +31,14 @@ export const TIMESTAMP_TOKEN = '%TIMESTAMP%'; */ export const CONTENT_OPTION_DEFAULTS = { sources: [], - imageTransforms: [], - contentTransforms: {}, downloadPath: '.downloads/', tempPath: '%DOWNLOAD_PATH%/.tmp/', backupPath: '.tmp-backup/%TIMESTAMP%/', - keep: '', + keep: [], strip: '', backupAndRestore: true, - maxConcurrent: 4, maxTimeout: 30000, - clearOldFilesOnSuccess: true, - clearOldFilesOnStart: false, - ignoreCache: false, - enableIfModifiedSinceCheck: true, - enableContentLengthCheck: true, - abortOnError: true, - ignoreImageTransformCache: false, - ignoreImageTransformErrors: true, - forceClearTempFiles: true, - encodeChars: '<>:"|?*', + encodeChars: '<>:"|?*' }; /** @@ -84,8 +57,12 @@ export function resolveContentOptions(config) { */ /** - * @param {ContentOptions} config - * @returns {ContentOptions} + * @typedef {{content?: ContentOptions, plugins?: import('./content-plugin-driver.js').ContentPlugin[]}} ConfigWithContent + */ + +/** + * @param {ConfigWithContent} config + * @returns {ConfigWithContent} */ export function defineContentConfig(config) { return config; diff --git a/packages/content/lib/content-plugin-driver.js b/packages/content/lib/content-plugin-driver.js new file mode 100644 index 00000000..5dcaca11 --- /dev/null +++ b/packages/content/lib/content-plugin-driver.js @@ -0,0 +1,116 @@ +import { HookContextProvider } from '@bluecadet/launchpad-utils/lib/plugin-driver.js'; + +export class ContentError extends Error { + /** + * @param {string} [message] + * @param {Error} [cause] + */ + constructor(message, cause) { + super(message, { cause }); + this.name = 'ContentError'; + } +} + +/** + * @typedef ContentHookContext + * @prop {import('./utils/data-store.js').DataStore} data + * @prop {import('./content-options.js').ResolvedContentOptions} contentOptions + * @prop {object} paths + * @prop {(source?: string) => string} paths.getDownloadPath + * @prop {(source?: string, pluginName?: string) => string} paths.getTempPath + * @prop {(source?: string) => string} paths.getBackupPath + */ + +/** + * @typedef {import('@bluecadet/launchpad-utils/lib/plugin-driver.js').BaseHookContext & ContentHookContext} CombinedContentHookContext + */ + +/** + * @typedef ContentHooks + * @prop {(ctx: CombinedContentHookContext, error: ContentError) => void | PromiseLike} onSetupError Called when a content source fails to setup + * @prop {(ctx: CombinedContentHookContext) => void | PromiseLike} onContentFetchSetup Called before any content is fetched + * @prop {(ctx: CombinedContentHookContext) => void | PromiseLike} onContentFetchDone Called when all content has been fetched + * @prop {(ctx: CombinedContentHookContext, error: ContentError) => void | PromiseLike} onContentFetchError Called when a content source fails to fetch + */ + +/** + * @template {Partial} [T=Partial] + * @typedef {import('@bluecadet/launchpad-utils/lib/plugin-driver.js').Plugin} ContentPlugin + */ + +/** + * Utility function for defining a content plugin + * @template {Partial} T + * @param {ContentPlugin} plugin + * @returns {ContentPlugin} + */ +export function defineContentPlugin(plugin) { + return plugin; +} + +/** + * Due to a limitation of TS in jsdoc, to get the specific hook types when defining plugins, + * we need this helper function to wrap the hooks object. + * @template {Partial} T + * @param {T} hooks + * @returns {T} + */ +export function defineContentPluginHooks(hooks) { + return hooks; +} + +/** + * @extends {HookContextProvider} + */ +export class ContentPluginDriver extends HookContextProvider { + /** + * @type {import('./utils/data-store.js').DataStore} + */ + #dataStore; + + /** + * @type {import('./content-options.js').ResolvedContentOptions} + */ + #options; + + /** + * @prop {(source?: string) => string} getDownloadPath + * @prop {(source?: string, pluginName?: string) => string} getTempPath + * @prop {(source?: string) => string} getBackupPath + */ + #pathGetters; + + /** + * @param {import('@bluecadet/launchpad-utils/lib/plugin-driver.js').default} wrappee + * @param {object} options + * @param {import('./utils/data-store.js').DataStore} options.dataStore + * @param {import('./content-options.js').ResolvedContentOptions} options.options + * @param {object} options.paths + * @param {(source?: string) => string} options.paths.getDownloadPath + * @param {(source?: string, pluginName?: string) => string} options.paths.getTempPath + * @param {(source?: string) => string} options.paths.getBackupPath + */ + constructor(wrappee, { dataStore, options, paths }) { + super(wrappee); + this.#dataStore = dataStore; + this.#options = options; + this.#pathGetters = paths; + } + + /** + * @param {ContentPlugin>} plugin + * @override + */ + _getPluginContext(plugin) { + return { + data: this.#dataStore, + contentOptions: this.#options, + paths: { + getDownloadPath: this.#pathGetters.getDownloadPath, + getBackupPath: this.#pathGetters.getBackupPath, + // temp path is plugin-specific + getTempPath: (/** @type {string | undefined} */ source) => this.#pathGetters.getTempPath(source, plugin.name) + } + }; + } +} diff --git a/packages/content/lib/content-sources/airtable-source.js b/packages/content/lib/content-sources/airtable-source.js deleted file mode 100644 index 44e72ffd..00000000 --- a/packages/content/lib/content-sources/airtable-source.js +++ /dev/null @@ -1,329 +0,0 @@ -/** - * @module airtable-source - */ - -import Airtable from 'airtable'; -// eslint-disable-next-line no-unused-vars -import { Logger } from '@bluecadet/launchpad-utils'; -import { ContentSource } from './content-source.js'; -import { ContentResult, MediaDownload } from './content-result.js'; -import Credentials from '../credentials.js'; - -/** - * @typedef AirtableCredentials - * @property {string} apiKey Airtable API Key - */ - -/** - * @typedef BaseAirtableOptions - * @property {string} baseId Airtable base ID. See https://help.appsheet.com/en/articles/1785063-using-data-from-airtable#:~:text=To%20obtain%20the%20ID%20of,API%20page%20of%20the%20base. - * @property {string} [defaultView] The table view which to select for syncing by default. Defaults to 'Grid view'. - * @property {string[]} [tables] The tables you want to fetch from. Defaults to []. - * @property {string[]} [keyValueTables] As a convenience feature, you can store tables listed here as key/value pairs. Field names should be `"key"` and `"value"`. Defaults to []. - * @property {string} [endpointUrl] The API endpoint to use for Airtable. Defaults to 'https://api.airtable.com'. - * @property {boolean} [appendLocalAttachmentPaths] Appends the local path of attachments to the saved JSON. Defaults to true. - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'airtable'> & BaseAirtableOptions & (AirtableCredentials | {})} AirtableOptions - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'airtable'> & Required & AirtableCredentials} AirtableOptionsAssembled - */ - -/** - * @satisfies {Partial} - */ -const AIRTABLE_OPTION_DEFAULTS = { - defaultView: 'Grid view', - tables: [], - keyValueTables: [], - endpointUrl: 'https://api.airtable.com', - appendLocalAttachmentPaths: true -}; - -/** - * @extends {ContentSource} - */ -export class AirtableSource extends ContentSource { - /** - * @type {Airtable.Base} - */ - _base; - - /** - * @type {Record[]>} - */ - _rawAirtableData = {}; - - /** - * @type {Record} - */ - _simplifiedData = {}; - - /** - * - * @param {AirtableOptions} config - * @param {Logger} logger - */ - constructor(config, logger) { - super(AirtableSource._assembleConfig(config), logger); - - if (!this.config.apiKey) { - throw new Error(`No Airtable API Key for '${this.config.id}'`); - } - - Airtable.configure({ - endpointUrl: this.config.endpointUrl, - apiKey: this.config.apiKey - }); - - this._base = Airtable.base(this.config.baseId); - this._base.makeRequest(); - } - - /** - * @returns {Promise} - */ - async fetchContent() { - const result = new ContentResult(); - const tablePromises = []; - - for (const tableId of this.config.tables) { - tablePromises.push(this._getData(tableId)); - } - for (const tableId of this.config.keyValueTables) { - tablePromises.push(this._getData(tableId)); - } - - return Promise.all(tablePromises) - .then(async () => { - for (const tableId of this.config.tables) { - await this._processTable(tableId, false, result); - } - for (const tableId of this.config.keyValueTables) { - await this._processTable(tableId, true, result); - } - }) - .then(() => result); - } - - /** - * - * @param {string} tableId - * @param {boolean} isKeyValueTable - * @param {ContentResult} result - * @returns {Promise} - */ - async _processTable(tableId, isKeyValueTable = false, result = new ContentResult()) { - // Write raw Data file. - - const rawDataPath = `${tableId}.raw.json`; - result.addDataFile(rawDataPath, this._rawAirtableData[tableId]); - - /** - * @type {any} - */ - const simpData = isKeyValueTable ? {} : []; - this._simplifiedData[tableId] = []; - - // Process simplified data - for (const row of this._rawAirtableData[tableId]) { - const fields = { ...row._rawJson.fields }; - - if (isKeyValueTable) { - if (Object.keys(row).length < 2) { - this.logger.error( - `Table ${tableId} requires at least 2 columns to map it to a key-value pair.` - ); - return result; - } - - const regex = /(.*)\[([0-9]*)\]$/g; - // Default to "key" | "value" if available. - const keyField = Object.keys(fields).includes('key') ? 'key' : Object.keys(fields)[0]; - const valueField = Object.keys(fields).includes('value') ? 'value' : Object.keys(fields)[1]; - const key = fields[keyField]; - const value = fields[valueField]; - - const matches = key ? [...key.matchAll(regex)] : []; - if (matches.length > 0) { - if (!simpData[matches[0][1]]) { - simpData[matches[0][1]] = []; - } - simpData[matches[0][1]][matches[0][2]] = value; - } else if (this._isNumericStr(value)) { - simpData[key] = parseFloat(value); - } else if (this._isBoolStr(value)) { - simpData[key] = value === 'true'; - } else { - simpData[key] = value; - } - } else { - simpData.push({ id: row.id, ...fields }); - } - } - - this._simplifiedData[tableId] = simpData; - - // Gather attachments - if (!isKeyValueTable) { - result.addMediaDownloads( - this._getMediaUrls(simpData).map((url) => new MediaDownload({ url })) - ); - } - - const simpDataPath = `${tableId}.json`; - result.addDataFile(simpDataPath, this._simplifiedData[tableId]); - - return result; - } - - /** - * @param {string} str - * @returns {boolean} - */ - _isBoolStr(str) { - return str === 'true' || str === 'false'; - } - - /** - * @param {string} str - * @returns {boolean} - */ - _isNumericStr(str) { - return !isNaN(Number(str)); - } - - // Get Data. - /** - * @param {string} table - * @param {boolean} force - */ - async _getData(table, force = false) { - // If force, clear the data. - if (force) { - this._rawAirtableData[table] = []; - } - - if (this._rawAirtableData[table] && this._rawAirtableData[table].length > 0) { - // Return cached data - return Promise.resolve(this._rawAirtableData[table]); - } - // Fetch new data - return this._fetchData(table).then((tableData) => { - this._rawAirtableData[table] = tableData; - return this._rawAirtableData[table]; - }); - } - - /** - * Fetch from Airtable. - * @param {string} table table name - * @returns {Promise[]>} - */ - async _fetchData(table) { - return new Promise((resolve, reject) => { - /** - * @type {Airtable.Record[]} - */ - const rows = []; - - this._base(table) - .select({ - view: this.config.defaultView - }) - .eachPage( - (records, fetchNextPage) => { - // This function (`page`) will get called for each page of records. - records.forEach((record) => { - rows.push(record); - }); - - // To fetch the next page of records, call `fetchNextPage`. - // If there are more records, `page` will get called again. - // If there are no more records, `done` will get called. - fetchNextPage(); - }, - (err) => { - if (err) { - reject(err); - } else { - resolve(rows); - } - } - ); - }); - } - - /** - * - * @param {*} tableData - * @returns {Array} media urls - */ - _getMediaUrls(tableData) { - const urls = []; - // Loop through all records, for filename fields. - for (const row of tableData) { - for (const colId of Object.keys(row)) { - // We assume it is a file if value is an array and `filename` key exists. - if (Array.isArray(row[colId]) && row[colId][0].filename) { - for (const attachment of row[colId]) { - const url = new URL(attachment.url); - if (this.config.appendLocalAttachmentPaths) { - attachment.localPath = url.pathname; - } - urls.push(url.toString()); - } - } - } - } - return urls; - } - - /** - * @private - * @param {AirtableOptions} config - * @returns {AirtableOptionsAssembled} - */ - static _assembleConfig(config) { - const creds = Credentials.getCredentials(config.id); - - if (creds) { - if (!AirtableSource._validateCrendentials(creds)) { - throw new Error( - `Airtable credentials for source '${config.id}' are invalid.` - ); - } - - return { - ...AIRTABLE_OPTION_DEFAULTS, - ...config, - ...creds - }; - } - - if (!AirtableSource._validateCrendentials(config)) { - throw new Error( - `No airtable credentials found for source '${config.id}' in credentials file or launchpad config.` - ); - } - - return { - ...AIRTABLE_OPTION_DEFAULTS, - ...config - }; - } - - /** - * @private - * @param {unknown} creds - * @returns {creds is AirtableCredentials} - */ - static _validateCrendentials(creds) { - return (typeof creds !== 'object' || creds === null || !('apiKey' in creds)); - } -} - -export default AirtableSource; diff --git a/packages/content/lib/content-sources/content-result.js b/packages/content/lib/content-sources/content-result.js deleted file mode 100644 index 084188df..00000000 --- a/packages/content/lib/content-sources/content-result.js +++ /dev/null @@ -1,197 +0,0 @@ -import path from 'node:path'; - -export class DataFile { - /** - * The relative local path where the file should be saved. - * @type {string} - */ - localPath = ''; - /** - * The file contents to be saved. - * @type {unknown} - */ - content = ''; - - /** - * - * @param {string} localPath - * @param {unknown} content - */ - constructor(localPath, content) { - this.localPath = localPath; - this.content = content; - } - - /** - * Returns the raw content if it's already a string, otherwise returns the result of `JSON.stringify(this.content)`. - * @returns {string} - */ - getContentStr() { - if (typeof this.content === 'string') { - return this.content; - } else if (this.content) { - return JSON.stringify(this.content); - } else { - return ''; - } - } -} -export class MediaDownload { - /** - * @param {object} options - * @param {string} options.url - * @param {string} [options.localPath] - */ - constructor({ - url, - localPath = undefined, - ...rest - }) { - /** - * The url to download - * @type {string} - */ - this.url = url; - - /** - * The path of this asset relative to this source's root asset dir. - * Can optionally be overriden to save this file at another location. - */ - this.localPath = localPath || MediaDownload.buildLocalPath(this.url); - - Object.assign(this, rest); - } - - /** - * Returns a string unique to this URL and relative path. - * Helpful for checking against duplicate download tasks. - * @returns {string} - */ - getKey() { - return `${this.url}_${this.localPath}`; - } - - /** - * Given a full URL, build a unique path to save the file - * @param {string} url - * @returns {string} - */ - static buildLocalPath(url) { - // Remove protocol and domain - let urlPath = url.replace(/^[^:]+:\/\/[^\/]+/, ''); - - // Remove leading slash if present - if (urlPath.startsWith('/')) { - urlPath = urlPath.slice(1); - } - - // Use the OS path separator - const localPath = urlPath.replace(/\//g, path.sep); - - return localPath; - } -} - -export class ContentResult { - /** - * List of data files to save - * @param {Array} results - */ - static combine(results) { - const finalResult = results.reduce((previousValue, currentValue) => { - previousValue.addDataFiles(currentValue.dataFiles); - previousValue.addMediaDownloads(currentValue.mediaDownloads); - return previousValue; - }, new ContentResult()); - - return finalResult; - } - - /** - * List of data files to save - * @type {Array} - */ - dataFiles = []; - - /** - * List of media to download - * @type {Array} - */ - mediaDownloads = []; - - /** - * @param {Array} dataFiles All the data files and their contents that should be saved - * @param {Array} mediaDownloads All the media files that should be saved - */ - constructor(dataFiles = [], mediaDownloads = []) { - this.dataFiles = dataFiles; - this.mediaDownloads = mediaDownloads; - } - - /** - * - * @param {string} localPath - * @param {unknown} content - */ - addDataFile(localPath, content) { - this.dataFiles.push(new DataFile(localPath, content)); - } - - /** - * - * @param {Array} DataFiles - */ - addDataFiles(DataFiles) { - this.dataFiles.push(...DataFiles); - } - - /** - * - * @param {MediaDownload | string} urlOrDownload - */ - addMediaDownload(urlOrDownload) { - if (typeof urlOrDownload === 'string') { - urlOrDownload = new MediaDownload({ - url: urlOrDownload - }); - } - this.mediaDownloads.push(urlOrDownload); - } - - /** - * - * @param {Iterable} files - */ - addMediaDownloads(files) { - this.mediaDownloads.push(...files); - } - - /** - * - * @param {string} id - */ - collate(id) { - /** - * @type {Array} - */ - const initial = []; - - // Collect all data into 1 object. - const collatedData = this.dataFiles.reduce((previousValue, currentValue) => { - // error if the content isn't iterable - if (!Array.isArray(currentValue.content)) { - throw new Error(`Content for ${currentValue.localPath} is not iterable`); - } - - return [...previousValue, ...currentValue.content]; - }, initial); - - // Remove old datafiles. - this.dataFiles = []; - - const fileName = `${id}.json`; - this.addDataFile(fileName, collatedData); - } -} - -export default ContentResult; diff --git a/packages/content/lib/content-sources/content-source.js b/packages/content/lib/content-sources/content-source.js deleted file mode 100644 index 11a20389..00000000 --- a/packages/content/lib/content-sources/content-source.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @module content-source - */ - -import chalk from 'chalk'; -import { Logger } from '@bluecadet/launchpad-utils'; -import ContentResult from './content-result.js'; - -export const IMAGE_REGEX = /.+(\.jpg|\.jpeg|\.png)/gi; -export const VIDEO_REGEX = /.+(\.avi|\.mov|\.mp4|\.mpg|\.mpeg)/gi; -export const MEDIA_REGEX = new RegExp(`(${IMAGE_REGEX.source})|(${VIDEO_REGEX.source})`); - -/** - * @template T - * @typedef {Required<{[K in keyof T as T extends Record ? never : K]: T[K]}>} SelectOptional Select only the optional properties of a type. - * @example - * type Foo = { a: string, b?: number }; - * type OptionalFoo = SelectOptional; // { b: number } - */ - -/** - * @template {string} T - * @typedef SourceOptions - * @property {string} id Required field to identify this source. Will be used as download path. - * @property {T} type The type of content source. Used internally to determine which source class to use. - */ - -/** - * @template {SourceOptions} [C=SourceOptions] - */ -export class ContentSource { - /** @type {C} */ - config; - /** @type {Logger | Console} */ - logger; - - /** - * @param {C} config Content source options. `id` is a required field. - * @param {Logger} [logger] The logger to use for all output. Defaults to console. - * @throws {Error} Throws an error if no `config` or `config.id` is defined. - */ - constructor(config, logger) { - this.logger = logger ?? console; - this.config = config; - - if (!this.config || !this.config.id) { - throw new Error('Content source has no ID. This is a required field.'); - } - } - - /** - * Downloads data content and gathers media urls. - * - * @returns {Promise} that resolves only when all content has been downloaded successfully - */ - async fetchContent() { - throw new Error(`Downloading functionality not implemented for '${chalk.yellow(this.config.id)}'`); - } - - /** - * Removes all content and media files in the temp and dest directories (temp first, then dest). - * - * @returns {Promise} - */ - async clearContent() { - throw new Error('clearContent not implemented'); - } - - toString() { - return this.config ? this.config.id : '[ContentSource]'; - } -} - -export default ContentSource; diff --git a/packages/content/lib/content-sources/contentful-source.js b/packages/content/lib/content-sources/contentful-source.js deleted file mode 100644 index 79d1f47f..00000000 --- a/packages/content/lib/content-sources/contentful-source.js +++ /dev/null @@ -1,310 +0,0 @@ -/** - * @module contentful-source - */ - -import contentful from 'contentful'; -import ContentSource from './content-source.js'; -import ContentResult, { MediaDownload } from './content-result.js'; -import Credentials from '../credentials.js'; -import { Logger } from '@bluecadet/launchpad-utils'; - -/** - * @typedef ContentfulCredentialsAccessToken - * @property {string} accessToken LEGACY: For backwards compatibility you can only set the `"accessToken"` using your delivery or preview token and a combination of the usePreviewApi flag. - */ - -/** - * @typedef ContentfulCredentialsDeliveryToken - * @property {string} deliveryToken Content delivery token (all published content). - * @property {string} [previewToken] Content preview token (only unpublished/draft content). - */ - -/** - * @typedef ContentfulCredentialsPreviewToken - * @property {string} previewToken Content preview token (only unpublished/draft content). - */ - -/** - * @typedef {ContentfulCredentialsAccessToken | ContentfulCredentialsDeliveryToken | ContentfulCredentialsPreviewToken} ContentfulCredentials - */ - -/** - * @typedef BaseContentfulOptions - * @property {string} space Your Contentful space ID. Note that an accessToken is required in addition to this - * @property {string} [locale] Optional. Used to pull localized images. - * @property {string} [filename] Optional. The filename you want to use for where all content (entries and assets metadata) will be stored. Defaults to 'content.json' - * @property {string} [protocol] Optional. Defaults to 'https' - * @property {string} [host] Optional. Defaults to 'cdn.contentful.com' - * @property {boolean} [usePreviewApi] Optional. Set to true if you want to use the preview API instead of the production version to view draft content. Defaults to false - * @property {Array} [contentTypes] Optionally limit queries to these content types. This will also apply to linked assets. Types that link to other types will include up to 10 levels of child content. E.g. filtering by Story, might also include Chapters and Images. Uses `searchParams['sys.contentType.sys.id[in]']` under the hood. - * @property {any} [searchParams] Optional. Supports anything from https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters - * @property {any} [imageParams] Optional. Applies to all images. Defaults to empty object. **IMPORTANT:** If you change the parameters, you will have to delete all cached images since the modified date of the original image will not have changed. - */ - -/** - * @typedef {Omit} ContentfulClientParams - */ - -/** - * Configuration options for the Contentful ContentSource. - * - * Also supports all fields of the Contentful SDK's config. - * - * @see Configuration under https://contentful.github.io/contentful.js/contentful/9.1.7/ - * - * @typedef {import('./content-source.js').SourceOptions<'contentful'> & BaseContentfulOptions & ContentfulClientParams & (ContentfulCredentials | {})} ContentfulOptions - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'contentful'> & Required & ContentfulClientParams & {accessToken: string}} ContentfulOptionsAssembled - */ - -/** - * @satisfies {Partial} - */ -const CONTENTFUL_OPTIONS_DEFAULTS = { - locale: 'en-US', - filename: 'content.json', - protocol: 'https', - host: 'cdn.contentful.com', - usePreviewApi: false, - contentTypes: [], - searchParams: { - limit: 1000, // This is the max that Contentful supports, - include: 10 // @see https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/links/retrieval-of-linked-items - }, - imageParams: {} -}; - -/** - * @extends {ContentSource} - */ -class ContentfulSource extends ContentSource { - /** @type {contentful.ContentfulClientApi} */ - client; - - /** - * @param {ContentfulOptions} config - * @param {Logger} logger - */ - constructor(config, logger) { - super(ContentfulSource._assembleConfig(config), logger); - if (this.config.usePreviewApi) { - this.logger.info(`Using preview API for Contentful source '${this.config.id}'`); - } - this.client = contentful.createClient(this.config); - } - - /** - * @returns {Promise} - */ - async fetchContent() { - return this._fetchPage(this.config.searchParams) - .then((content) => { - const result = new ContentResult(); - result.addDataFile(this.config.filename, content); - result.addMediaDownloads( - this._getMediaUrls(content.assets).map(url => new MediaDownload({ url })) - ); - return result; - }) - .catch((err) => { - return Promise.reject(new Error(`Could not fetch Contentful content: ${err.message}`)); - }); - } - - /** - * Recursively fetches content using the Contentful client. - * - * @param {any} searchParams - * @param {any} result - * @returns {Promise<{entries: unknown[], assets: unknown[]}>} Object with an 'entries' and an 'assets' array. - */ - async _fetchPage(searchParams = {}, result = {}) { - result.entries = result.entries || []; - result.assets = result.assets || []; - result.numPages = result.numPages || 0; - - return ( - this.client - .getEntries(searchParams) - .then((rawPage) => { - const page = rawPage.toPlainObject(); - result.numPages++; - result.entries.push(...this._parseEntries(page)); - result.assets.push(...this._parseAssets(page)); - - if (rawPage.limit + rawPage.skip < rawPage.total) { - // Fetch next page - searchParams.skip = searchParams.skip || 0; - searchParams.skip += rawPage.limit; - return this._fetchPage(searchParams, result); - } else { - // Return combined entries + assets - return Promise.resolve(result); - } - }) - .catch((error) => { - this.logger.error(`Sync failed: ${error ? error.message || '' : ''}`); - }) - ); - } - - /** - * Returns all entries from a sync() or getEntries() response object. - * @param {*} responseObj - * @returns {Array>} Array of entry objects. - */ - _parseEntries(responseObj) { - const entries = responseObj.entries || []; - if (responseObj.items) { - for (const item of responseObj.items) { - if (item && item.sys && item.sys.type === 'Entry') { - entries.push(item); - } - } - } - return entries; - } - - /** - * Returns all entries from a sync() or getEntries() response object. - * @param {*} responseObj - * @returns {Array} Array of entry objects. - */ - _parseAssets(responseObj) { - const assets = responseObj.assets || []; - if (responseObj.includes) { - // 'includes' is an object where the key = type, and the value = list of items - for (const [key, items] of Object.entries(responseObj.includes)) { - if (key === 'Asset') { - assets.push(...items); - } - } - } - return assets; - } - - /** - * Extracts all media urls from a list of Contentful asset objects - * @param {*} assets The list of assets from the Contentful API - * @returns {Array} - */ - _getMediaUrls(assets) { - const urls = []; - - for (const asset of assets) { - if (asset.fields && asset.fields.file) { - const file = asset.fields.file; - const locale = this.config.locale; - - let url = ''; - - if (!file) { - this.logger.error(file); - } else if (locale && file[locale] && file[locale].url) { - url = file[locale].url; - } else if (file && file.url) { - url = file.url; - } - - if (!url) { - continue; - } - - if (!url.includes('http') && !url.includes('https')) { - url = `${this.config.protocol}:${url}`; - } - - if (this.config.imageParams) { - for (const [key, value] of Object.entries(this.config.imageParams)) { - url += (url.includes('?') ? '&' : '?') + `${key}=${value}`; - } - } - urls.push(url); - } - } - - return urls; - } - - /** - * @param {ContentfulOptions} config - * @returns {ContentfulOptionsAssembled} - */ - static _assembleConfig(config) { - const creds = Credentials.getCredentials(config.id); - - /** - * @type {ContentfulCredentials} - */ - let validatedCredentials; - - if (creds) { - if (!ContentfulSource._validateCrendentials(creds)) { - throw new Error(`Contentful credentials for '${config.id}' are invalid`); - } - - validatedCredentials = creds; - } else { - if (!ContentfulSource._validateCrendentials(config)) { - throw new Error(`No Contentful credentials found for '${config.id}' in credentials file or launchpad config.`); - } - - validatedCredentials = config; - } - - const assembled = { - accessToken: '', - ...CONTENTFUL_OPTIONS_DEFAULTS, - ...config - }; - - if (!('deliveryToken' in validatedCredentials) && 'previewToken' in validatedCredentials) { - // if we only have a preview token, use the preview API - assembled.usePreviewApi = true; - } - - if (assembled.usePreviewApi) { - assembled.host = 'preview.contentful.com'; - } - - if ('accessToken' in validatedCredentials) { - // if access token is set, use it - assembled.accessToken = validatedCredentials.accessToken; - } else if (assembled.usePreviewApi) { - // if usePreviewApi is true, use previewToken - if (!('previewToken' in validatedCredentials) || !validatedCredentials.previewToken) { - throw new Error(`usePreviewApi is set to true, but no previewToken is provided for '${config.id}'`); - } - assembled.accessToken = validatedCredentials.previewToken; - } else { - // otherwise just use deliveryToken - if (!('deliveryToken' in validatedCredentials) || !validatedCredentials.deliveryToken) { - throw new Error(`no deliveryToken is provided for '${config.id}'`); - } - assembled.accessToken = validatedCredentials.deliveryToken; - } - - if (assembled.contentTypes && assembled.contentTypes.length > 0) { - assembled.searchParams['sys.contentType.sys.id[in]'] = assembled.contentTypes.join(','); - } - - return assembled; - } - - /** - * @private - * @param {unknown} creds - * @returns {creds is ContentfulCredentials} - */ - static _validateCrendentials(creds) { - if (typeof creds !== 'object' || creds === null) { - return false; - } - - return 'accessToken' in creds || 'deliveryToken' in creds || 'previewToken' in creds; - } -} - -export default ContentfulSource; diff --git a/packages/content/lib/content-sources/json-source.js b/packages/content/lib/content-sources/json-source.js deleted file mode 100644 index 1f16ec99..00000000 --- a/packages/content/lib/content-sources/json-source.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * @module json-source - */ - -import JsonUtils from '../utils/json-utils.js'; -import ContentSource, { MEDIA_REGEX } from './content-source.js'; -import ContentResult, { MediaDownload } from './content-result.js'; -import ky from 'ky'; -import { Logger } from '@bluecadet/launchpad-utils'; -import chalk from 'chalk'; - -/** - * @typedef BaseJsonOptions - * @property {RegExp} [mediaPattern] Regex for media files that should be downloaded from json sources. Defaults to `/.+(\.jpg|\.jpeg|\.png)/gi|/.+(\.avi|\.mov|\.mp4|\.mpg|\.mpeg)/gi` - * @property {Record} files A mapping of json file-path -> url - * @property {number} [maxTimeout] Max request timeout in ms. Defaults to 30 seconds. - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'json'> & BaseJsonOptions} JsonOptions - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'json'> & Required} JsonOptionsAssembled - */ - -const JSON_OPTIONS_DEFAULTS = { - mediaPattern: MEDIA_REGEX, - maxTimeout: 30_000 -}; - -/** - * @extends {ContentSource} - */ -class JsonSource extends ContentSource { - /** - * - * @param {JsonOptions} config - * @param {Logger} logger - */ - constructor(config, logger) { - super({ ...JSON_OPTIONS_DEFAULTS, ...config }, logger); - } - - /** - * @returns {Promise} - */ - async fetchContent() { - return this._downloadJsons().then((result) => { - return this._scrapeMediaUrls(result); - }); - } - - /** - * @return {Promise} - */ - async _downloadJsons() { - const result = new ContentResult(); - for (const [path, url] of Object.entries(this.config.files)) { - this.logger.debug(`Downloading json ${chalk.blue(url)}`); - const response = await ky(url, { - timeout: this.config.maxTimeout - }); - const json = await response.json(); - result.addDataFile(path, json); - } - - return result; - } - - /** - * - * @param {ContentResult} result - * @returns {Promise} - */ - async _scrapeMediaUrls(result) { - for (const dataFile of result.dataFiles) { - this.logger.debug(`Scraping for media files in ${chalk.blue(dataFile.localPath)}...`); - const mediaUrls = JsonUtils.getUrls(dataFile.content, undefined, this.config.mediaPattern); - this.logger.debug(`Found ${chalk.blue(mediaUrls.size.toString())} media files in ${chalk.blue(dataFile.localPath)}`); - result.addMediaDownloads([...mediaUrls].map(url => new MediaDownload({ url }))); - } - return result; - } -} - -export default JsonSource; diff --git a/packages/content/lib/content-sources/sanity-source.js b/packages/content/lib/content-sources/sanity-source.js deleted file mode 100644 index 687b6f4d..00000000 --- a/packages/content/lib/content-sources/sanity-source.js +++ /dev/null @@ -1,304 +0,0 @@ -/** - * @module sanity-source - */ - -import chalk from 'chalk'; -import jsonpath from 'jsonpath'; -import path from 'path'; -import sanitize from 'sanitize-filename'; - -import { createClient } from '@sanity/client'; -import imageUrlBuilder from '@sanity/image-url'; - -import ContentSource from './content-source.js'; -import ContentResult, { MediaDownload } from './content-result.js'; -import Credentials from '../credentials.js'; -import { Logger } from '@bluecadet/launchpad-utils'; -import FileUtils from '../utils/file-utils.js'; - -/** - * @typedef SanityCredentials - * @property {string} apiToken API Token defined in your sanity project. - */ - -/** - * @typedef BaseSanityOptions - * @property {string} [apiVersion] API Version. Defailts to 'v2021-10-21' - * @property {string} projectId Sanity Project ID - * @property {string} [dataset] Dataset. Defaults to 'production' - * @property {boolean} [useCdn] `false` if you want to ensure fresh data - * @property {string} baseUrl The base url of your Sanity CMS (with or without trailing slash). - * @property {Array} queries An array of queries to fetch. Each query can be a string or an object with a query and an id. - * @property {number} [limit] Max number of entries per page. Defaults to 100. - * @property {number} [maxNumPages] Max number of pages. Use `-1` for all pages. Defaults to -1. - * @property {boolean} [mergePages] To combine paginated files into a single file. Defaults to false. - * @property {number} [pageNumZeroPad] How many zeros to pad each json filename index with. Defaults to 0. - * @property {boolean} [appendCroppedFilenames] If an image has a crop set within Sanity, this setting will append the cropped filename to each image object as `launchpad.croppedFilename`. Set this to `false` to disable this behavior. Defaults to true. - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'sanity'> & BaseSanityOptions & (SanityCredentials | {})} SanityOptions - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'sanity'> & Required & SanityCredentials} SanityOptionsAssembled - */ - -const SANITY_OPTION_DEFAULTS = { - apiVersion: 'v2021-10-21', - dataset: 'production', - useCdn: false, - limit: 100, - maxNumPages: -1, - mergePages: false, - pageNumZeroPad: 0, - appendCroppedFilenames: true -}; - -/** - * @extends ContentSource - */ -class SanitySource extends ContentSource { - /** - * - * @param {SanityOptions} config - * @param {Logger} logger - */ - constructor(config, logger) { - super(SanitySource._assembleConfig(config), logger); - - this._checkConfigDeprecations(this.config); - - this.client = createClient({ - projectId: this.config.projectId, - dataset: this.config.dataset, - apiVersion: this.config.apiVersion, // use current UTC date - see "specifying API version"! - token: this.config.apiToken, // or leave blank for unauthenticated usage - useCdn: this.config.useCdn // `false` if you want to ensure fresh data - }); - } - - /** - * @returns {Promise} - */ - async fetchContent() { - /** - * @type {Array.>} - */ - const queryPromises = []; - - /** - * @type {Array.>} - */ - const customQueryPromises = []; - - for (const query of this.config.queries) { - if (typeof query === 'string') { - const queryFull = '*[_type == "' + query + '" ]'; - const result = new ContentResult(); - - queryPromises.push( - this._fetchPages(query, queryFull, result, { - start: 0, - limit: this.config.limit - }) - ); - } else { - const result = new ContentResult(); - customQueryPromises.push( - this._fetchPages(query.id, query.query, result, { - start: 0, - limit: this.config.limit - }) - ); - } - } - - return Promise.all([...queryPromises, ...customQueryPromises]) - .then((values) => { - return ContentResult.combine(values); - }) - .catch((error) => { - this.logger.error(`Sync failed: ${error ? error.message || '' : ''}`); - return error; - }); - } - - /** - * Recursively fetches content using the Sanity client. - * - * @param {string} id - * @param {string} query - * @param {ContentResult} result - * @param {{start: number, limit: number}} params - * @returns {Promise} Object with an 'entries' and an 'assets' array. - */ - async _fetchPages(id, query, result, params = { start: 0, limit: 100 }) { - const pageNum = params.start / params.limit || 0; - const q = - query + - '[' + - params.start + - '..' + - (params.start + params.limit - 1) + - ']'; - const p = {}; - - this.logger.debug(`Fetching page ${pageNum} of ${id}`); - - return this.client - .fetch(q, p) - .then((content) => { - if (!content || !content.length) { - // If we are combining files, we do that here. - if (this.config.mergePages) { - result.collate(id); - } - - // Empty result or no more pages left - return Promise.resolve(result); - } - - const fileName = `${id}-${pageNum - .toString() - .padStart(this.config.pageNumZeroPad, '0')}.json`; - - result.addDataFile(fileName, content); - result.addMediaDownloads(this._getMediaDownloads(content)); - - if ( - this.config.maxNumPages < 0 || - pageNum < this.config.maxNumPages - 1 - ) { - // Fetch next page - params.start = params.start || 0; - params.start += params.limit; - return this._fetchPages(id, query, result, params); - } else { - // Return combined entries + assets - return Promise.resolve(result); - } - }) - .catch((error) => { - this.logger.error( - chalk.red(`Could not fetch page: ${error ? error.message || '' : ''}`) - ); - return Promise.reject(error); - }); - } - - /** - * - * @param {unknown} content - * @return {Array} - */ - _getMediaDownloads(content) { - const downloads = []; - - // Get all raw URLs - const rawAssetUrls = jsonpath.query(content, '$..url'); - for (let contentUrl of rawAssetUrls) { - if (contentUrl.startsWith('/')) { - const url = new URL(contentUrl, this.config.baseUrl); - contentUrl = url.toString(); - } - downloads.push( - new MediaDownload({ - url: contentUrl - }) - ); - } - - // Get derivative image URLs for crops/hotspots/etc - const images = jsonpath.query(content, '$..*[?(@._type=="image")]'); - const builder = imageUrlBuilder(this.client); - for (const image of images) { - if (!('crop' in image)) { - // Only process images with crop properties - continue; - } - const urlBuilder = builder.image(image); - const urlStr = urlBuilder.url(); - const url = new URL(urlStr); - const task = new MediaDownload({ - url: urlStr - }); - task.localPath = FileUtils.addFilenameSuffix( - task.localPath, - `_${sanitize(url.search.replace('?', ''))}` - ); - - if (this.config.appendCroppedFilenames) { - image.launchpad = { - croppedFilename: path.basename(task.localPath) - }; - } - - downloads.push(task); - } - - return downloads; - } - - /** - * - * @param {SanityOptions} config - */ - _checkConfigDeprecations(config) { - if ('textConverters' in config) { - const exampleQuery = '\t"contentTransforms": {\n\t "$..*[?(@._type==\'block\')]": ["sanityToPlain", "sanityToHtml", "sanityToMarkdown"]\n\t}'; - this.logger.warn( - `The Sanity source "${chalk.yellow( - 'textConverters' - )}" feature has been deprecated. Please use the following query instead (select only one transform):\n${chalk.green( - exampleQuery - )}` - ); - } - } - - /** - * @private - * @param {SanityOptions} config - * @returns {SanityOptionsAssembled} - */ - static _assembleConfig(config) { - const creds = Credentials.getCredentials(config.id); - - if (creds) { - if (!SanitySource._validateCrendentials(creds)) { - throw new Error( - `Sanity credentials for source '${config.id}' are invalid.` - ); - } - - return { - ...SANITY_OPTION_DEFAULTS, - ...config, - ...creds - }; - } - - if (!SanitySource._validateCrendentials(config)) { - throw new Error( - `No Sanity credentials found for source '${config.id}' in credentials file or launchpad config.` - ); - } - - return { - ...SANITY_OPTION_DEFAULTS, - ...config - }; - } - - /** - * @private - * @param {unknown} creds - * @returns {creds is SanityCredentials} - */ - static _validateCrendentials(creds) { - return typeof creds === 'object' && creds !== null && 'apiToken' in creds; - } -} - -export default SanitySource; diff --git a/packages/content/lib/content-sources/strapi-source.js b/packages/content/lib/content-sources/strapi-source.js deleted file mode 100644 index c256fad3..00000000 --- a/packages/content/lib/content-sources/strapi-source.js +++ /dev/null @@ -1,484 +0,0 @@ -/** - * @module strapi-source - */ - -import chalk from 'chalk'; -import jsonpath from 'jsonpath'; -import ky from 'ky'; -import qs from 'qs'; - -import ContentSource from './content-source.js'; -import ContentResult, { MediaDownload } from './content-result.js'; -import Credentials from '../credentials.js'; -import { Logger } from '@bluecadet/launchpad-utils'; - -/** - * @typedef StrapiObjectQuery - * @property {string} contentType The content type to query - * @property {{pagination?: {page: number, pageSize: number}, [key: string]: unknown}} params Query parameters. Uses `qs` library to stringify. - */ - -/** - * @typedef StrapiPagination - * @property {number} start The index of the first item to fetch - * @property {number} limit The number of items to fetch - */ - -/** - * @typedef StrapiLoginCredentials - * @property {string} identifier Username or email. Should be configured via `./.env.local` - * @property {string} password Should be configured via `./.env.local` - * - * @typedef StrapiTokenCredentials - * @property {string} token Can be used instead of identifer/password if you previously generated one. Otherwise this will be automatically generated using the identifier or password. - * - * @typedef {StrapiLoginCredentials | StrapiTokenCredentials} StrapiCredentials - */ - -/** - * @typedef BaseStrapiOptions - * @property {'4' | '3'} [version] Versions `3` and `4` are supported. Defaults to `3`. - * @property {string} baseUrl The base url of your Strapi CMS (with or without trailing slash). - * @property {Array} queries Queries for each type of content you want to save. One per content type. Content will be stored as numbered, paginated JSONs. - * You can include all query parameters supported by Strapi. - * You can also pass an object with a `contentType` and `params` property, where `params` is an object of query parameters. - * @property {number} [limit] Max number of entries per page. Defaults to `100`. - * @property {number} [maxNumPages] Max number of pages. Use the default of `-1` for all pages. Defaults to `-1`. - * @property {number} [pageNumZeroPad] How many zeros to pad each json filename index with. Defaults to `0`. - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'strapi'> & BaseStrapiOptions & (StrapiCredentials | {})} StrapiOptions - */ - -/** - * @typedef {import('./content-source.js').SourceOptions<'strapi'> & Required & StrapiCredentials } StrapiOptionsAssembled - */ - -/** - * @satisfies {Partial} - */ -const STRAPI_OPTION_DEFAULTS = { - version: '3', - limit: 100, - maxNumPages: -1, - pageNumZeroPad: 0 -}; - -class StrapiVersionUtils { - /** - * @type {StrapiOptions} - * @protected - */ - config; - - /** - * @type {Logger | Console} - * @protected - */ - logger; - - /** - * @param {StrapiOptions} config - * @param {Logger | Console} logger - */ - constructor(config, logger) { - this.config = config; - this.logger = logger; - } - - /** - * @param {StrapiObjectQuery} query - * @param {StrapiPagination} [pagination] - * @returns {string} - */ - buildUrl(query, pagination) { - throw new Error('Not implemented'); - } - - /** - * @param {StrapiObjectQuery} query - * @returns {boolean} - */ - hasPaginationParams(query) { - throw new Error('Not implemented'); - } - - /** - * @param {unknown} result - * @returns {unknown[]} - */ - transformResult(result) { - if (!Array.isArray(result)) { - throw new Error('Expected result to be an array'); - } - - return result; - } - - /** - * @param {unknown} result - * @returns {boolean} - */ - canFetchMore(result) { - throw new Error('Not implemented'); - } - - /** - * @param {string} string - * @returns {StrapiObjectQuery} - */ - parseQuery(string) { - const url = new URL(string, this.config.baseUrl); - const params = qs.parse(url.search.slice(1)); - const contentType = url.pathname.split('/').pop(); - - if (contentType === undefined) { - throw new Error(`Could not parse content type from query '${string}'`); - } - - return { contentType, params }; - } -} - -class StrapiV4 extends StrapiVersionUtils { - /** - * @param {StrapiObjectQuery} query - * @param {StrapiPagination} [pagination] - * @returns {string} - */ - buildUrl(query, pagination) { - const url = new URL(query.contentType, this.config.baseUrl); - - let params = query.params; - - // only add pagination params if they arent't specified in the query object - if (!this.hasPaginationParams(query) && pagination) { - params = { - ...params, - pagination: { - page: (pagination.start / pagination.limit) + 1, - pageSize: pagination.limit, - ...params?.pagination - } - }; - } - - const search = qs.stringify(params, { - encodeValuesOnly: true, // prettify url - addQueryPrefix: true // add ? to beginning - }); - - url.search = search; - - return url.toString(); - } - - /** - * @param {StrapiObjectQuery} query - * @returns {boolean} - */ - hasPaginationParams(query) { - return query?.params?.pagination?.page !== undefined || query?.params?.pagination?.pageSize !== undefined; - } - - /** - * @param {{data: unknown[]}} result - * @returns {unknown[]} - */ - transformResult(result) { - return result.data; - } - - /** - * @param {{meta?: {pagination?: {page: number, pageCount: number}}}} result - * @returns {boolean} - */ - canFetchMore(result) { - if (result?.meta?.pagination) { - const { page, pageCount } = result.meta.pagination; - return page < pageCount; - } - - return false; - } -} - -class StrapiV3 extends StrapiVersionUtils { - /** - * @param {StrapiObjectQuery} query - * @param {StrapiPagination} [pagination] - * @returns {string} - */ - buildUrl(query, pagination) { - const url = new URL(query.contentType, this.config.baseUrl); - - let params = query.params; - - // only add pagination params if they arent't specified in the query object - if (!this.hasPaginationParams(query) && pagination) { - params = { - _start: pagination.start, - _limit: pagination.limit, - ...params - }; - } - - const search = qs.stringify(params, { - encodeValuesOnly: true, // prettify url - addQueryPrefix: true // add ? to beginning - }); - - url.search = search; - - return url.toString(); - } - - /** - * @param {StrapiObjectQuery} query - * @returns {boolean} - */ - hasPaginationParams(query) { - return query?.params?._start !== undefined || query?.params?._limit !== undefined; - } - - /** - * @param {unknown} result - * @returns {boolean} - */ - canFetchMore(result) { - // strapi v3 doesn't have any pagination info in the response, - // so we can't know if there are more results - return true; - } -} - -/** - * @extends {ContentSource} - */ -class StrapiSource extends ContentSource { - /** - * @type {StrapiVersionUtils} - * @private - */ - _versionUtils; - - /** - * - * @param {StrapiOptions} config - * @param {Logger} logger - */ - constructor(config, logger) { - super(StrapiSource._assembleConfig(config), logger); - - if (!this.config.version) { - throw new Error('Strapi version not specified'); - } else if (parseInt(this.config.version) === 3) { - this._versionUtils = new StrapiV3(this.config, this.logger); - } else if (parseInt(this.config.version) === 4) { - this._versionUtils = new StrapiV4(this.config, this.logger); - } else { - throw new Error(`Unsupported strapi version '${this.config.version}'`); - } - - if (!this.config.queries || !this.config.queries.length) { - throw new Error('No content queries defined'); - } - } - - /** - * @returns {Promise} - */ - async fetchContent() { - const result = new ContentResult(); - - /** - * @type {string | undefined} - */ - let token; - - if ('token' in this.config) { - token = this.config.token; - } else { - if (!this.config.identifier || !this.config.password) { - throw new Error('Either a token or an identifier and password must be provided for a Strapi source'); - } - token = await this._getJwt(this.config.identifier, this.config.password); - } - - for (const query of this.config.queries) { - await this._fetchPages(query, token, result, { - start: 0, - limit: this.config.limit - }); - } - - return result; - } - - /** - * Recursively fetches content using the Strapi client. - * - * @param {string | StrapiObjectQuery} query - * @param {string} jwt The JSON web token generated by Strapi - * @param {ContentResult} result - * @param {StrapiPagination} pagination - * @returns {Promise} Object with an 'entries' and an 'assets' array. - */ - async _fetchPages( - query, - jwt, - result, - pagination = { start: 0, limit: 100 } - ) { - /** - * @type {StrapiObjectQuery} - */ - let parsedQuery; - - if (typeof query === 'string') { - parsedQuery = this._versionUtils.parseQuery(query); - } else { - parsedQuery = query; - } - - const pageNum = pagination.start / pagination.limit; - - const fileName = `${parsedQuery.contentType}-${pageNum.toString().padStart(this.config.pageNumZeroPad, '0')}.json`; - - this.logger.debug(`Fetching page ${pageNum} of ${parsedQuery.contentType}`); - - return ky(this._versionUtils.buildUrl(parsedQuery, pagination), { - headers: { - Authorization: `Bearer ${jwt}` - } - }) - .json() - .then((content) => { - const transformedContent = this._versionUtils.transformResult(content); - if (!transformedContent || !transformedContent.length) { - // Empty result or no more pages left - return Promise.resolve(result); - } - - result.addDataFile(fileName, transformedContent); - - result.addMediaDownloads( - this._getMediaUrls(transformedContent).map(url => new MediaDownload({ url })) - ); - - if ( - !this._versionUtils.hasPaginationParams(parsedQuery) && - (this.config.maxNumPages < 0 || pageNum < this.config.maxNumPages - 1) && - this._versionUtils.canFetchMore(content) - ) { - // Fetch next page - pagination.start = pagination.start || 0; - pagination.start += pagination.limit; - return this._fetchPages(parsedQuery, jwt, result, pagination); - } else { - // Return combined entries + assets - return Promise.resolve(result); - } - }) - .catch((error) => { - this.logger.error(chalk.red(`Could not fetch page: ${error ? error.message || '' : ''}`)); - return Promise.reject(result); - }); - } - - /** - * @private - * @param {Object} content - * @return {Array} - */ - _getMediaUrls(content) { - const contentUrls = jsonpath.query(content, '$..url'); - const mediaUrls = []; - for (let contentUrl of contentUrls) { - if (contentUrl.startsWith('/')) { - const url = new URL(contentUrl, this.config.baseUrl); - contentUrl = url.toString(); - } - mediaUrls.push(contentUrl); - } - return mediaUrls; - } - - /** - * @private - * @param {string} identifier - * @param {string} password - * @returns {Promise} The JSON web token generated by Strapi - */ - async _getJwt(identifier, password) { - this.logger.info(chalk.gray(`Retrieving JWT for ${identifier}...`)); - - const url = new URL('/auth/local', this.config.baseUrl); - - return ky - .post(url.toString(), { - searchParams: { - identifier, - password - } - }) - .json() - .then(response => { - this.logger.info(chalk.green(`...retrieved JWT for ${chalk.white(identifier)}`)); - return response.jwt; - }) - .catch(error => { - this.logger.info(chalk.red(`Could not retrieve JWT for ${chalk.white(identifier)}`)); - this.logger.info(chalk.yellow(error)); - throw error; - }); - } - - /** - * @private - * @param {StrapiOptions} config - * @returns {StrapiOptionsAssembled} - */ - static _assembleConfig(config) { - const creds = Credentials.getCredentials(config.id); - - if (creds) { - if (!StrapiSource._validateCrendentials(creds)) { - throw new Error( - `Strapi credentials for source '${config.id}' are invalid.` - ); - } - - return { - ...STRAPI_OPTION_DEFAULTS, - ...config, - ...creds - }; - } - - if (!StrapiSource._validateCrendentials(config)) { - throw new Error( - `No Strapi credentials found for source '${config.id}' in credentials file or launchpad config.` - ); - } - - return { - ...STRAPI_OPTION_DEFAULTS, - ...config - }; - } - - /** - * @private - * @param {unknown} creds - * @returns {creds is StrapiCredentials} - */ - static _validateCrendentials(creds) { - if (typeof creds !== 'object' || creds === null) { return false; }; - - return ('identifier' in creds && 'password' in creds) || 'token' in creds; - } -} - -export default StrapiSource; diff --git a/packages/content/lib/content-transforms/content-transform.js b/packages/content/lib/content-transforms/content-transform.js deleted file mode 100644 index 728c43da..00000000 --- a/packages/content/lib/content-transforms/content-transform.js +++ /dev/null @@ -1,29 +0,0 @@ -class ContentTransform { - /** - * @param {string} content - * @returns {string} - */ - transform(content) { - return content; - } - - /** - * @param {unknown} content - * @returns {content is { _type: "block" }} - */ - static isBlockContent(content) { - // check if object - if (typeof content !== 'object' || content === null) { - return false; - } - - // check if block - if (!('_type' in content) || content._type !== 'block') { - return false; - } - - return true; - } -} - -export default ContentTransform; diff --git a/packages/content/lib/content-transforms/md-to-html-transform.js b/packages/content/lib/content-transforms/md-to-html-transform.js deleted file mode 100644 index 055321cc..00000000 --- a/packages/content/lib/content-transforms/md-to-html-transform.js +++ /dev/null @@ -1,36 +0,0 @@ -import sanitizeHtml from 'sanitize-html'; -import MarkdownIt from 'markdown-it'; - -import markdownItItalicBold from '../utils/markdown-it-italic-bold.js'; -import ContentTransform from './content-transform.js'; - -class MdToHtmlTransform extends ContentTransform { - simplified = false; - - constructor(simplified = false) { - super(); - this.simplified = simplified; - this.transform = this.transform.bind(this); - } - - /** - * @param {unknown} content - */ - transform(content) { - if (typeof content !== 'string') { - throw new Error('Can\'t convert a non-string content to html.'); - } - - const sanitizedStr = sanitizeHtml(content); - const md = new MarkdownIt(); - - if (this.simplified) { - md.use(markdownItItalicBold); - return md.renderInline(sanitizedStr); - } else { - return md.render(sanitizedStr); - } - } -} - -export default MdToHtmlTransform; diff --git a/packages/content/lib/content-transforms/sanity-to-html.js b/packages/content/lib/content-transforms/sanity-to-html.js deleted file mode 100644 index d266ffca..00000000 --- a/packages/content/lib/content-transforms/sanity-to-html.js +++ /dev/null @@ -1,23 +0,0 @@ -import { toHTML } from '@portabletext/to-html'; - -import ContentTransform from './content-transform.js'; - -class SanityToHtmlTransform extends ContentTransform { - constructor() { - super(); - this.transform = this.transform.bind(this); - } - - /** - * @param {unknown} content - */ - transform(content) { - if (!ContentTransform.isBlockContent(content)) { - throw new Error(`Content is not a valid Sanity text block: ${content}`); - } - - return toHTML(content); - } -} - -export default SanityToHtmlTransform; diff --git a/packages/content/lib/content-transforms/sanity-to-markdown.js b/packages/content/lib/content-transforms/sanity-to-markdown.js deleted file mode 100644 index 8c81a668..00000000 --- a/packages/content/lib/content-transforms/sanity-to-markdown.js +++ /dev/null @@ -1,24 +0,0 @@ -// @ts-ignore -import toMarkdown from '@sanity/block-content-to-markdown'; - -import ContentTransform from './content-transform.js'; - -class SanityToMarkdownTransform extends ContentTransform { - constructor() { - super(); - this.transform = this.transform.bind(this); - } - - /** - * @param {unknown} content - */ - transform(content) { - if (!ContentTransform.isBlockContent(content)) { - throw new Error(`Content is not a valid Sanity text block (must contain a "_type": "block" property): ${content}`); - } - - return toMarkdown(content); - } -} - -export default SanityToMarkdownTransform; diff --git a/packages/content/lib/content-transforms/sanity-to-plain.js b/packages/content/lib/content-transforms/sanity-to-plain.js deleted file mode 100644 index aca459e2..00000000 --- a/packages/content/lib/content-transforms/sanity-to-plain.js +++ /dev/null @@ -1,43 +0,0 @@ -import ContentTransform from './content-transform.js'; - -class SanityToPlainTransform extends ContentTransform { - constructor() { - super(); - this.transform = this.transform.bind(this); - } - - /** - * @param {unknown} content - */ - transform(content) { - if (!SanityToPlainTransform.isBlockWithChildren(content)) { - throw new Error(`Content is not a valid Sanity text block: ${content}`); - } - return content.children.map(child => child.text).join(''); - } - - /** - * @param {unknown} content - * @returns {content is { _type: "block", children: { text: string }[]}} - */ - static isBlockWithChildren(content) { - // check if object - if (!ContentTransform.isBlockContent(content)) { - return false; - } - - // check if children - if (!('children' in content) || !Array.isArray(content.children)) { - return false; - } - - // check if children are objects with 'text' property - if (!content.children.every(child => typeof child === 'object' && 'text' in child)) { - return false; - } - - return true; - } -} - -export default SanityToPlainTransform; diff --git a/packages/content/lib/credentials.js b/packages/content/lib/credentials.js deleted file mode 100644 index c92bddeb..00000000 --- a/packages/content/lib/credentials.js +++ /dev/null @@ -1,60 +0,0 @@ -import fsx from 'fs-extra'; -import chalk from 'chalk'; -import { Logger } from '@bluecadet/launchpad-utils'; - -/** - * @type {Record} - */ -let creds = {}; - -/** - * @deprecated Use .env for managing sensitive data instead - */ -class Credentials { - /** @type {Logger | Console} */ - static logger = console; - - /** - * @param {string} [credentialsPath] - * @param {Logger | Console} logger - * @deprecated Use .env for managing sensitive data instead - */ - static init(credentialsPath, logger = console) { - this.logger = logger; - - if (!credentialsPath) { - return; - } - - this.logger.warn(`${chalk.white('credentialsPath')} option is deprecated. Please use ${chalk.white('.env')}/${chalk.white('.env.local')} instead.`); - - try { - if (fsx.existsSync(credentialsPath)) { - this.logger.info(chalk.gray(`Loading credentials from '${chalk.white(credentialsPath)}'`)); - const rawdata = fsx.readFileSync(credentialsPath); - creds = JSON.parse(rawdata.toString()); - } else { - this.logger.warn(`No credentials file found at '${credentialsPath}'`); - } - } catch (err) { - if (err instanceof Error) { - this.logger.error(`Couldn't load credentials from '${chalk.white(credentialsPath)}'`, err.message); - } - } - } - - /** - * @param {string} id - * @deprecated Use .env for managing sensitive data instead - */ - static getCredentials(id) { - if (id in creds) { - return creds[id]; - } else { - this.logger.error(`Can't find credentials for '${id}'`); - return null; - } - } -} - -export default Credentials; diff --git a/packages/content/lib/launchpad-content.js b/packages/content/lib/launchpad-content.js index 1ffe0fe7..69139d95 100644 --- a/packages/content/lib/launchpad-content.js +++ b/packages/content/lib/launchpad-content.js @@ -1,504 +1,352 @@ import path from 'path'; -import jsonpath from 'jsonpath'; import chalk from 'chalk'; -import fs from 'fs-extra'; - -import Credentials from './credentials.js'; -import { CONTENT_OPTION_DEFAULTS, DOWNLOAD_PATH_TOKEN, TIMESTAMP_TOKEN, resolveContentOptions } from './content-options.js'; - -import ContentSource from './content-sources/content-source.js'; -import AirtableSource from './content-sources/airtable-source.js'; -import JsonSource from './content-sources/json-source.js'; -import ContentfulSource from './content-sources/contentful-source.js'; -import StrapiSource from './content-sources/strapi-source.js'; -import SanitySource from './content-sources/sanity-source.js'; - -import MediaDownloader from './utils/media-downloader.js'; -import FileUtils from './utils/file-utils.js'; -import MdToHtmlTransform from './content-transforms/md-to-html-transform.js'; -import ContentTransform from './content-transforms/content-transform.js'; - -import { LogManager, Logger, onExit } from '@bluecadet/launchpad-utils'; -import ContentResult from './content-sources/content-result.js'; -import SanityToPlainTransform from './content-transforms/sanity-to-plain.js'; -import SanityToHtmlTransform from './content-transforms/sanity-to-html.js'; -import SanityToMarkdownTransform from './content-transforms/sanity-to-markdown.js'; - -/** - * @enum {import('./content-options.js').AllSourceOptions['type']} - */ -export const ContentSourceTypes = { - /** @type {'json'} */ - json: 'json', - /** @type {'airtable'} */ - airtable: 'airtable', - /** @type {'contentful'} */ - contentful: 'contentful', - /** @type {'sanity'} */ - sanity: 'sanity', - /** @type {'strapi'} */ - strapi: 'strapi' -}; -export class LaunchpadContent { - /** - * Creates a new LaunchpadContent and downloads content using an optional user config object. - * @param {import('./content-options.js').ContentOptions} [config] - * @returns {Promise.} Promise that resolves with the new LaunchpadContent instance. - */ - static async createAndDownload(config) { - try { - const content = new LaunchpadContent(config); - await content.download(); - return content; - } catch (error) { - LogManager.getInstance().getLogger().error( - chalk.red('Could not download data:'), - error - ); - return Promise.reject(error); - } - } +import { DOWNLOAD_PATH_TOKEN, TIMESTAMP_TOKEN, resolveContentOptions } from './content-options.js'; +import * as FileUtils from './utils/file-utils.js'; +import { LogManager } from '@bluecadet/launchpad-utils'; +import PluginDriver from '@bluecadet/launchpad-utils/lib/plugin-driver.js'; +import { ContentError, ContentPluginDriver } from './content-plugin-driver.js'; +import { DataStore } from './utils/data-store.js'; +import { ok, err, ResultAsync, okAsync } from 'neverthrow'; + +export class LaunchpadContent { /** @type {import('./content-options.js').ResolvedContentOptions} */ _config; - - /** @type {Logger} */ - _logger; - /** @type {Array} */ - _sources = []; + /** @type {import('@bluecadet/launchpad-utils').Logger} */ + _logger; - /** @type {MediaDownloader} */ - _mediaDownloader; + /** @type {ContentPluginDriver} */ + _pluginDriver; - /** @type {Map} */ - _contentTransforms = new Map(); + /** @type {import('./content-options.js').ConfigContentSource[]} */ + _rawSources; /** @type {Date} */ _startDatetime = new Date(); + /** @type {DataStore} */ + _dataStore; + /** - * @param {import('./content-options.js').ContentOptions} [config] - * @param {Logger} [parentLogger] + * @param {import('./content-options.js').ConfigWithContent} [config] + * @param {import('@bluecadet/launchpad-utils').Logger} [parentLogger] + * @param {PluginDriver} [pluginDriver] */ - constructor(config, parentLogger) { - this._config = resolveContentOptions(config); + constructor(config, parentLogger, pluginDriver) { + this._config = resolveContentOptions(config?.content); this._logger = LogManager.getInstance().getLogger('content', parentLogger); - this._mediaDownloader = new MediaDownloader(this._logger); - this._contentTransforms.set('mdToHtml', new MdToHtmlTransform(false)); - this._contentTransforms.set('mdToHtmlSimplified', new MdToHtmlTransform(true)); - this._contentTransforms.set('markdownToHtml', new MdToHtmlTransform(false)); - this._contentTransforms.set('markdownToHtmlSimplified', new MdToHtmlTransform(true)); - this._contentTransforms.set('sanityToPlain', new SanityToPlainTransform()); - this._contentTransforms.set('sanityToHtml', new SanityToHtmlTransform()); - this._contentTransforms.set('sanityToMd', new SanityToMarkdownTransform()); - this._contentTransforms.set('sanityToMarkdown', new SanityToMarkdownTransform()); - - if (this._config.credentialsPath) { - try { - Credentials.init(this._config.credentialsPath, this._logger); - } catch (err) { - if (err instanceof Error) { - this._logger.warn('Could not load credentials:', err.message); + this._dataStore = new DataStore(); + + // create all sources + this._rawSources = this._config.sources; + + const basePluginDriver = pluginDriver || new PluginDriver(this._logger, config?.plugins ?? []); + + this._pluginDriver = new ContentPluginDriver( + basePluginDriver, + { + dataStore: this._dataStore, + options: this._config, + paths: { + getDownloadPath: this.getDownloadPath.bind(this), + getTempPath: this.getTempPath.bind(this), + getBackupPath: this.getBackupPath.bind(this) } } - } - - this.sources = this._createSources(this._config.sources); - - onExit(async () => { - this._mediaDownloader.abort(); - }); + ); } /** - * @param {Array?} sources - * @returns {Promise} + * @param {import('./content-options.js').ConfigContentSource[]?} rawSources + * @returns {ResultAsync} */ - async start(sources = null) { - sources = sources || this.sources; - if (!sources || sources.length <= 0) { + start(rawSources = null) { + rawSources = rawSources || this._rawSources; + if (!rawSources || rawSources.length <= 0) { this._logger.warn(chalk.yellow('No sources found to download')); - return Promise.resolve(); + return okAsync(undefined); } this._startDatetime = new Date(); - try { - this._logger.info(`Downloading ${chalk.cyan(sources.length)} sources`); - - if (this._config.backupAndRestore) { - this._logger.info(`Backing up ${chalk.cyan(sources.length)} sources`); - await this.backup(sources); - } - - let sourcesComplete = 0; - - for (const source of sources) { - const progress = (sourcesComplete + 1) + '/' + sources.length; - this._logger.info(`Downloading source ${chalk.cyan(progress)}: ${chalk.yellow(source)}`); - let result = await source.fetchContent(); - - result = await this._downloadMedia(source, result); - result = await this._applyContentTransforms(source, result); - result = await this._saveDataFiles(source, result); - - sourcesComplete++; - } - - this._logger.info( - chalk.green(`Finished downloading ${sources.length} sources`) + return this._createSourcesFromConfig(rawSources) + .andThrough(() => this._pluginDriver.runHookSequential('onContentFetchSetup')) + .andThen( + sources => this.backup(sources) + .andThen(() => this.clear(sources, { + temp: false, + backups: false, + downloads: true + })) + .andThen(() => this._fetchSources(sources)) + .andThrough(() => this._pluginDriver.runHookSequential('onContentFetchDone')) + .andThen(() => this._writeDataStoreToDisk(this._dataStore)) + .orElse(e => { + this._pluginDriver.runHookSequential('onContentFetchError', e); + this._logger.error('Error in content fetch process:', e); + this._logger.info('Restoring from backup...'); + return this.restore(sources).andThen(() => { + return err(new ContentError('Failed to download content. Restored from backup.')); + }); + }) + .andThen(() => this.clear(sources, { + temp: true, + backups: true, + downloads: false + })) ); - } catch (err) { - this._logger.error(chalk.red('Could not download all content:'), err); - if (this._config.backupAndRestore) { - this._logger.info(`Restoring ${chalk.cyan(sources.length)} sources`); - await this.restore(sources); - } - } - - try { - this._logger.debug('Cleaning up temp and backup files'); - await this.clear(sources, { - temp: true, - backups: true, - downloads: false - }); - } catch (err) { - this._logger.error('Could not clean up temp and backup files', err); - } - - return Promise.resolve(); } /** * Alias for start(source) - * @param {Array} sources - * @returns {Promise} + * @param {import('./content-options.js').ConfigContentSource[]?} rawSources */ - async download(sources = []) { - return this.start(sources); + async download(rawSources = null) { + return this.start(rawSources); } /** * Clears all cached content except for files that match config.keep. - * @param {Array} sources The sources you want to clear. If left undefined, this will clear all known sources. If no sources are passed, the entire downloads/temp/backup dirs are removed. + * @param {Array} sources The sources you want to clear. If left undefined, this will clear all known sources. If no sources are passed, the entire downloads/temp/backup dirs are removed. * * @param {object} options * @param {boolean} [options.temp] Clear the temp dir * @param {boolean} [options.backups] Clear the backup dir * @param {boolean} [options.downloads] Clear the download dir * @param {boolean} [options.removeIfEmpty] Remove each dir if it's empty after clearing + * @returns {ResultAsync} */ - async clear(sources = [], { + clear(sources = [], { temp = true, backups = true, downloads = true, removeIfEmpty = true } = {}) { - sources = sources || [null]; - for (const source of sources) { - const sourceLabel = source ? `source ${source}` : 'all sources'; + return ResultAsync.combine(sources.map(source => { + /** @type {ResultAsync[]} */ + const tasks = []; if (temp) { - this._logger.debug(`Clearing temp files of ${chalk.yellow(sourceLabel)}`); - await this._clearDir(this.getTempPath(source), { removeIfEmpty, ignoreKeep: true }); + tasks.push(this._clearDir(this.getTempPath(source.id), { removeIfEmpty, ignoreKeep: true })); } if (backups) { - this._logger.debug(`Clearing backup of ${chalk.yellow(sourceLabel)}`); - await this._clearDir(this.getBackupPath(source), { removeIfEmpty, ignoreKeep: true }); + tasks.push(this._clearDir(this.getBackupPath(source.id), { removeIfEmpty, ignoreKeep: true })); } if (downloads) { - this._logger.debug(`Clearing downloads of ${chalk.yellow(sourceLabel)}`); - await this._clearDir(this.getDownloadPath(source), { removeIfEmpty }); + tasks.push(this._clearDir(this.getDownloadPath(source.id), { removeIfEmpty })); } - } - - if (removeIfEmpty && temp) { - await FileUtils.removeDirIfEmpty(this.getTempPath()); - } - if (removeIfEmpty && backups) { - await FileUtils.removeDirIfEmpty(this.getBackupPath()); - } - if (removeIfEmpty && downloads) { - await FileUtils.removeDirIfEmpty(this.getDownloadPath()); - } - - return Promise.resolve(); + return ResultAsync.combine(tasks); + })).andThen(() => { + /** @type {ResultAsync[]} */ + const tasks = []; + if (removeIfEmpty) { + if (temp) tasks.push(FileUtils.removeDirIfEmpty(this.getTempPath())); + if (backups) tasks.push(FileUtils.removeDirIfEmpty(this.getBackupPath())); + if (downloads) tasks.push(FileUtils.removeDirIfEmpty(this.getDownloadPath())); + } + return ResultAsync.combine(tasks); + }) + .map(() => undefined) // return void instead of void[] + .mapErr(error => new ContentError(`Failed to clear directories: ${error instanceof Error ? error.message : String(error)}`)); } /** * Backs up all downloads of source to a separate backup dir. - * @param {Array} sources + * @param {Array} sources + * @returns {ResultAsync} */ - async backup(sources = []) { - for (const source of sources) { - try { - const downloadPath = this.getDownloadPath(source); - const backupPath = this.getBackupPath(source); - if (!fs.existsSync(downloadPath)) { - throw new Error(`No downloads found at ${downloadPath}`); - } - this._logger.debug(`Backing up ${source}`); - await fs.copy(downloadPath, backupPath, { preserveTimestamps: true }); - } catch (err) { - this._logger.warn(`Couldn't back up ${source}:`, err); - } - } + backup(sources = []) { + return ResultAsync.combine(sources.map(source => { + const downloadPath = this.getDownloadPath(source.id); + const backupPath = this.getBackupPath(source.id); + + return FileUtils.pathExists(downloadPath) + .andThen((exists) => { + if (!exists) { + this._logger.warn(`Skipping backup for ${source.id}: No downloads found at ${downloadPath}`); + return ok(undefined); + } + this._logger.debug(`Backing up ${source}`); + return FileUtils.copy(downloadPath, backupPath); + }); + })) + .mapErr(e => new ContentError(`Failed to backup sources: ${e}`)) + .map(() => undefined); // return void instead of void[] } /** * Restores all downloads of source from its backup dir if it exists. - * @param {Array} sources + * @param {Array} sources * @param {boolean} removeBackups + * @returns {ResultAsync} */ - async restore(sources = [], removeBackups = true) { - for (const source of sources) { - try { - const downloadPath = this.getDownloadPath(source); - const backupPath = this.getBackupPath(source); - if (!fs.existsSync(backupPath)) { - throw new Error(`No backups found at ${backupPath}`); + restore(sources = [], removeBackups = true) { + return ResultAsync.combine(sources.map(source => { + const downloadPath = this.getDownloadPath(source.id); + const backupPath = this.getBackupPath(source.id); + + return FileUtils.pathExists(backupPath).andThrough((exists) => { + if (!exists) { + return err(`No backups found at ${backupPath}`); } + return ok(undefined); + }).andTee(() => { this._logger.info(`Restoring ${source} from backup`); - await fs.copy(backupPath, downloadPath, { preserveTimestamps: true }); + }).andThen(() => { + return FileUtils.copy(backupPath, downloadPath, { preserveTimestamps: true }); + }).andThen(() => { if (removeBackups) { this._logger.debug(`Removing backup for ${source}`); - await fs.remove(backupPath); + return FileUtils.remove(backupPath); } - } catch (err) { - this._logger.error(`Couldn't restore ${source}:`, err); - } - } + + return okAsync(undefined); + }).mapErr(e => new ContentError(`Failed to restore source ${source.id}: ${e}`)); + })).map(() => undefined); // return void instead of void[] } /** - * @param {ContentSource} [source] + * @param {string} [sourceId] * @returns {string} */ - getDownloadPath(source) { - if (source) { - return path.resolve(path.join(this._config.downloadPath, source.config.id)); + getDownloadPath(sourceId) { + if (sourceId) { + return path.resolve(path.join(this._config.downloadPath, sourceId)); } else { return path.resolve(this._config.downloadPath); } } /** - * @param {ContentSource} [source] + * @param {string} [sourceId] + * @param {string} [pluginName] * @returns {string} */ - getTempPath(source) { + getTempPath(sourceId, pluginName) { const downloadPath = this._config.downloadPath; const tokenizedPath = this._config.tempPath; - const detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); - if (source) { - return path.join(detokenizedPath, source.config.id); - } else { - return detokenizedPath; + let detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); + + if (pluginName) { + detokenizedPath = path.join(detokenizedPath, pluginName); } + + if (sourceId) { + detokenizedPath = path.join(detokenizedPath, sourceId); + } + + return detokenizedPath; } /** - * @param {ContentSource} [source] + * @param {string} [sourceId] * @returns {string} */ - getBackupPath(source) { + getBackupPath(sourceId) { const downloadPath = this._config.downloadPath; const tokenizedPath = this._config.backupPath; const detokenizedPath = this._getDetokenizedPath(tokenizedPath, downloadPath); - if (source) { - return path.join(detokenizedPath, source.config.id); + if (sourceId) { + return path.join(detokenizedPath, sourceId); } else { return detokenizedPath; } } /** - * @param {import('./content-options.js').ContentOptions['sources']} sourceConfigs - * @returns {Array} + * @param {import('./content-options.js').ConfigContentSource[]} rawSources */ - _createSources(sourceConfigs) { - if (!sourceConfigs || (Array.isArray(sourceConfigs) && sourceConfigs.length === 0)) { - this._logger.warn('No content sources found in config.'); - return []; - } - - const sources = []; - - /** - * @type {(import('./content-options.js').AllSourceOptions)[]} - */ - let sourceConfigArray = []; - - if (!Array.isArray(sourceConfigs)) { - // Backwards compatibility for key/value-based - // configs where the key is the source ID - const entries = Object.entries(sourceConfigs); - for (const [id, config] of entries) { - sourceConfigArray.push({ - ...config, - id - }); - } - } else { - sourceConfigArray = sourceConfigs; - } - - for (const sourceConfig of sourceConfigArray) { - try { - /** - * @type {ContentSource} - */ - let source; - - switch (sourceConfig.type) { - case ContentSourceTypes.airtable: - source = new AirtableSource(sourceConfig, this._logger); - break; - case ContentSourceTypes.contentful: - source = new ContentfulSource(sourceConfig, this._logger); - break; - case ContentSourceTypes.strapi: - source = new StrapiSource(sourceConfig, this._logger); - break; - case ContentSourceTypes.sanity: - source = new SanitySource(sourceConfig, this._logger); - break; - case ContentSourceTypes.json: - default: - if (sourceConfig.type !== ContentSourceTypes.json) { - // @ts-expect-error - user may have passed in a custom type that we don't know about - if ((sourceConfig).type) { - // @ts-expect-error - this._logger.warn(`Unknown source type '${sourceConfig.type}'. Defaulting ${sourceConfig.id} to '${ContentSourceTypes.json}'.`); - } else { - // @ts-expect-error - this._logger.info(`Defaulting source '${sourceConfig.id}' to 'json'.`); - } - } - source = new JsonSource(sourceConfig, this._logger); - break; + _createSourcesFromConfig(rawSources) { + return ResultAsync.combine(rawSources.map(source => + ResultAsync.fromPromise( + // wrap source in promise to ensure it's awaited + Promise.resolve(source), + error => new ContentError(error instanceof Error ? error.message : String(error)) + ).andThen(awaited => { + if ('value' in awaited || 'error' in awaited) { + return awaited.mapErr(e => new ContentError(e instanceof Error ? e.message : String(e))); } - - sources.push(source); - } catch (err) { - this._logger.error('Could not create content source:', err); - } - } - - return sources; - } - - /** - * Downloads media files from a content result with rollback capabilities. - * - * @param {ContentSource} source - * @param {ContentResult} result - * @returns {Promise} - */ - async _downloadMedia(source, result) { - await this._mediaDownloader.sync(result.mediaDownloads, { - ...this._config, - downloadPath: this.getDownloadPath(source), - tempPath: this.getTempPath(source) + return ok(awaited); + }) + )).orElse(e => { + this._pluginDriver.runHookSequential('onSetupError', e); + return err(e); }); - - return result; } /** - * Saves a result's data file to a local path - * @param {ContentSource} source - * @param {ContentResult} result - * @returns {Promise} + * @param {Array} sources + * @returns {ResultAsync} */ - async _saveDataFiles(source, result) { - for (const resultData of result.dataFiles) { - try { - const encodeRegex = new RegExp(`[${this._config.encodeChars}]`, 'g'); - const filePath = path.join( - this._config.downloadPath, - source.config.id, - resultData.localPath - ).replace(encodeRegex, encodeURIComponent); - await FileUtils.saveJson(resultData.content, filePath); - } catch (error) { - this._logger.error(`Could not save json ${resultData.localPath}`); - this._logger.error(error); - } - } - return Promise.resolve(result); + _fetchSources(sources) { + return ResultAsync.combine( + sources.map((source) => { + const sourceLogger = LogManager.getInstance().getLogger(`source:${source.id}`); + + return source.fetch({ + logger: sourceLogger, + dataStore: this._dataStore + }) + .asyncAndThen(calls => { + return ResultAsync.combine(calls.map(call => call.dataPromise)); + }) + .mapErr(e => new ContentError(`Failed to fetch source ${source.id}: ${e instanceof Error ? e.message : String(e)}`)) + .andThrough(fetchResults => { + /** @type {Map} */ + const map = new Map(); + + for (const result of fetchResults.flat()) { + map.set(result.id, result.data); + } + + return this._dataStore.createNamespaceFromMap(source.id, map).mapErr(e => new ContentError(`Unable to create namespace for source ${source.id}: ${e}`)); + }); + })) + .map(() => undefined); // return void instead of void[] } /** - * Saves a result's data file to a local path - * @param {ContentSource} source - * @param {ContentResult} result - * @returns {Promise} + * @param {import('./utils/data-store.js').DataStore} dataStore + * @returns {ResultAsync} */ - async _applyContentTransforms(source, result) { - /** - * @type {any} - */ - const transforms = 'contentTransforms' in source.config ? source.config.contentTransforms : this._config.contentTransforms; - - for (const resultData of result.dataFiles) { - if (!resultData.content) { - continue; - } - - for (const [path, transformIds] of Object.entries(transforms)) { - for (const transformId of transformIds) { - if (!this._contentTransforms.has(transformId)) { - this._logger.error(`Unsupported content transform: '${transformId}'`); - continue; - } - - const transformIdStr = chalk.yellow(transformId); - const pathStr = chalk.yellow(path); - const localPathStr = chalk.yellow(resultData.localPath); - const transformer = this._contentTransforms.get(transformId); - - try { - if (!transformer) { - throw new Error(`Could not find content transform '${transformId}'`); - } - - this._logger.debug( - chalk.gray(`Applying content transform ${transformIdStr} to '${pathStr}' in ${localPathStr}`)); - jsonpath.apply(resultData.content, path, transformer.transform); - } catch (error) { - this._logger.error( - chalk.red(`Could not apply content transform ${transformIdStr} to '${pathStr}' in ${localPathStr}`) - ); - this._logger.error(error); - } - } - } - } - - return Promise.resolve(result); + _writeDataStoreToDisk(dataStore) { + return ResultAsync.combine( + Array.from(dataStore.namespaces()).flatMap(namespace => + Array.from(namespace.documents()).map(document => { + const encodeRegex = new RegExp(`[${this._config.encodeChars}]`, 'g'); + const filePath = path.join( + this._config.downloadPath, + namespace.id, + document.id + ).replace(encodeRegex, encodeURIComponent); + return FileUtils.saveJson(document.data, filePath); + }) + )) + .mapErr(e => new ContentError(`Failed to write data store to disk: ${e}`)) + .map(() => undefined); // return void instead of void[] } /** * @param {string} dirPath + * @param {object} options + * @param {boolean} [options.removeIfEmpty] + * @param {boolean} [options.ignoreKeep] + * @returns {ResultAsync} */ - async _clearDir(dirPath, { - removeIfEmpty = true, - ignoreKeep = false - } = {}) { - try { - if (!fs.existsSync(dirPath)) { - return; - } - FileUtils.removeFilesFromDir(dirPath, ignoreKeep ? undefined : this._config.keep); - if (removeIfEmpty) { - await FileUtils.removeDirIfEmpty(dirPath); - } - } catch (err) { - this._logger.error(chalk.red(`Could not clear ${chalk.yellow(dirPath)} (make sure dir is not in use):`), err); - } + _clearDir(dirPath, { removeIfEmpty = true, ignoreKeep = false } = {}) { + return FileUtils.pathExists(dirPath) + .andThen(exists => { + if (!exists) return okAsync(undefined); + return FileUtils.removeFilesFromDir(dirPath, ignoreKeep ? undefined : this._config.keep) + .andThen(() => { + if (removeIfEmpty) { + return FileUtils.removeDirIfEmpty(dirPath); + } + + return okAsync(undefined); + }); + }).mapErr(e => new ContentError(e)); } /** diff --git a/packages/content/lib/plugins/__tests__/md-to-html.test.js b/packages/content/lib/plugins/__tests__/md-to-html.test.js new file mode 100644 index 00000000..b7bb2746 --- /dev/null +++ b/packages/content/lib/plugins/__tests__/md-to-html.test.js @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest'; +import mdToHtml from '../md-to-html.js'; +import { createTestPluginContext } from './plugins.test-utils.js'; + +describe('mdToHtml plugin', () => { + it('should convert markdown to html', () => { + const ctx = createTestPluginContext(); + ctx.data.insert('test', 'doc1', { content: '# Hello\n\nThis is **bold** and *italic*.' }); + + const plugin = mdToHtml({ path: '$.content' }); + plugin.hooks.onContentFetchDone(ctx); + + const result = ctx.data.get('test', 'doc1')._unsafeUnwrap(); + expect(result.data.content).toBe('

Hello

\n

This is bold and italic.

\n'); + }); + + it('should convert markdown to simplified html when simplified=true', () => { + const ctx = createTestPluginContext(); + ctx.data.insert('test', 'doc1', { content: 'This is **bold** and *italic*.' }); + + const plugin = mdToHtml({ path: '$.content', simplified: true }); + plugin.hooks.onContentFetchDone(ctx); + + const result = ctx.data.get('test', 'doc1')._unsafeUnwrap(); + expect(result.data.content).toBe('This is bold and italic.'); + }); + + it('should only transform specified keys', () => { + const ctx = createTestPluginContext(); + ctx.data.createNamespace('skip'); + ctx.data.insert('test', 'doc1', { content: '# Hello' }); + ctx.data.insert('skip', 'doc2', { content: '# Hello' }); + + const plugin = mdToHtml({ path: '$.content', keys: ['test'] }); + plugin.hooks.onContentFetchDone(ctx); + + const transformed = ctx.data.get('test', 'doc1')._unsafeUnwrap(); + const skipped = ctx.data.get('skip', 'doc2')._unsafeUnwrap(); + + expect(transformed.data.content).toBe('

Hello

\n'); + expect(skipped.data.content).toBe('# Hello'); + }); + + it('should sanitize html in markdown content', () => { + const ctx = createTestPluginContext(); + ctx.data.insert('test', 'doc1', { content: 'Hello ' }); + + const plugin = mdToHtml({ path: '$.content' }); + plugin.hooks.onContentFetchDone(ctx); + + const result = ctx.data.get('test', 'doc1')._unsafeUnwrap(); + expect(result.data.content).not.toContain('